An overview of modules in javascript.
import
/export
import()
System.import()
import
support in the browser [1]
// counter.js
const counterModule = (() => {
let counter = 0;
const increaseCounter = () => counter++;
const decreaseCounter = () => counter--;
const resetCounter = () => counter = 0;
return {
increaseCounter,
decreaseCounter,
resetCounter,
get counter() {
return counter;
}
};
})();
// index.js
counterModule.increaseCounter();
counterModule.decreaseCounter();
console.log(counterModule.counter); // 0
- nodejs
- browserify (for browser usage)
- webpack
require()
function- the
module
object
- once a module is required, it is parsed and kept in memory
- variables are passed by value
// module.js
const a = 2;
module.exports = { a };
// index.js
const a = require('./module').a;
- all required dependencies are loaded synchronous
- async implementations for better browser support:
require.ensure()
(webpack code splitting)
- all dependencies injected using
require()
are handled at runtime.
//module.js
console.log('module1 required');
//module2.js
console.log('module2 required');
//module3.js
console.log('module3 required');
//index.js
setTimeout(() => require('./module2'), 3000);
if (false) {
require('./module3');
}
require('./module');
// counter.js
let counter = 0;
const increaseCounter = () => counter++;
const decreaseCounter = () => counter--;
const resetCounter = () => counter = 0;
module.exports = {
increaseCounter,
decreaseCounter,
resetCounter,
get counter() {
return counter;
}
};
// index.js
const counter = require('./counter');
function main() {
counterModule.increaseCounter();
counterModule.decreaseCounter();
}
module.exports = main;
- webpack + babel
- rollup
import[5]
import defaultMember from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name"
import "module-name";
export[6]
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
// Stage 1 proposal - Lee Byron
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export default from ...;
export { default } from ...;
- once a module is required, it is parsed and kept in memory
- all
import
's andexport
's must be declared top level import
's are hoisted- all
import
's are read only views on exports [13]
// module.js
export default 1+1;
export const a = { b: 2 };
//index.js
import * as module from './module';
module.a.b = 3; //SyntaxError: Cannot assign to read-only object
or
import { a } from './module';
a = 2 //SyntaxError
- benefits:
- dead code elimination
- preparation for HTTP2
- variable checking (linting, etc.)
- faster property lookups [16]
var lib = require('lib');
lib.someFunc();
import * as lib from 'lib';
lib.someFunc();
Unlike commonjs, ES modules imports are live bindings to the original primitives. (Objects are passed by references so they will be mutated anyways).
//counter.js
let counter = 0;
const incrementCounter = () => counter++;
export { counter, incrementCounter };
//index.js
import { counter, incrementCounter };
console.log(counter); // 0
incrementCounter();
console.log(counter); //1
There are multiple strategies of testing modules. We can split the module types into three categories:
- simple/independent modules (no dependencies)
- modules which have local dependencies
- modules which have 3rd party dependencies
- browser support:
<script type="module"></script>
- are parsed under
use strict
mode - scoped environment
- are parsed under
- module script tags are differed
- only paths like '/' or './' are supported
- each import does a new fetch for the file
<script type="module">
import { counter, incrementCounter } from './counter.js';
document.body.append(`${counter}\n`);
incrementCounter();
document.body.append(`${counter}\n`);
</script>
- stage 3 proposal[8]
import('./counter.js').then(counterModule => {
console.log(counterModule.counter); // 0
counterModule.incrementCounter();
console.log(counterModule.counter); // 1
});
- ECMAScript modules in browsers - Jake Archibald
- ES6 Modules in Depth - Nicolás Bevacqua
- Exploring ES6 - Chapter 16 "Modules" - Dr. Axel Rauschmayer
- ES6 In Depth: Modules - Jason Orendorff
- import - MDN docs
- export - MDN docs
- Cyclic deps - Nodejs docs
- ES Proposals: import() - Domenic Denicola
- ES Proposals: export ns from - Lee Byron
- ES Proposals: export * as ns from - Lee Byron
- The Revealing Module - Addy Osmani
- Writing Modular JavaScript With AMD, CommonJS & ES Harmony - Addy Osmani
- Exploring ES6 - 16.3.5 Imports are read-only views on exports - Dr. Axel Rauschmayer
- ES Modules and NodeJS: Hard Choices - Rod Vagg
- ES6 Modules in Chrome Canary M60
- PICing on Javascript for fun and profit - CHRIS LEARY
- Optimizing Dynamically-Typed Object-Oriented Languages With Polymorphic Inline Caches Paper
- EcmaScript Specifications 7th Edition June 2016 - Modules section
- WHATWG Integration with the ES Spec
- ES Modules interview with Bradley Farias