Anatomy of ES Modules
About me
Andrei Cacio
Senior Software Developer
8x8 Inc.
The journey of modules in JavaScript
I. Pre-transpilers era
- module pattern
- commonjs
A few words on modular programming
What do we want to achieve?
- Decoupling
- Encapsulation
- Code sharing
I. Pre-transpilers era
What do I mean by this?
- no tooling
- no transpilers (babel, traceur, rollup etc)
- no build step
- pure browser
I. Pre-transpilers era
So how did client-side code looked before tools?
I. Pre-transpilers era
Module pattern
// 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;
}
};
})();
I. Pre-transpilers era
CommonJS
... a module format to solve JavaScript scope issues by making sure each module is executed in its own namespace
webpack docs
I. Pre-transpilers era
CommonJS
Syntax
- require() - loading a module
- module.exports - exporting from a module
I. Pre-transpilers era
CommonJS
- nodejs
- browserify
- webpack
Implementations
- Modules are memoized
- Data passed by value or reference
- Synchronous parsing
- Dynamic module structure
Main characteristics
I. Pre-transpilers era
CommonJS
Data passed by value or reference
// counter.js
let counter = 0;
const increaseCounter = () => counter++;
const decreaseCounter = () => counter--;
const resetCounter = () => counter = 0;
module.exports = {
increaseCounter,
decreaseCounter,
resetCounter,
get counter() {
return counter;
}
};
CommonJS
Dynamic module structure
console.log(3);
console.log(2);
console.log(1);
setTimeout(() => require('./module2'), 3000);
if (false) {
require('./module3');
}
require('./module');
index.js
module.js
module2.js
module3.js
I. Pre-transpilers era
- 1
- 2
$ node index.js
CommonJS
Dynamic module structure
I. Pre-transpilers era
module.js
node.js runtime
index.js
module2.js
CommonJS
Dynamic module structure
console.log(3);
console.log(2);
console.log(1);
setTimeout(() => require('./module2'), 3000);
if (false) {
require('./module3');
}
require('./module');
index.js
module2.js
module3.js
module.js
I. Pre-transpilers era
CommonJS
Dynamic module structure
Same result with Webpack
The journey of modules in JavaScript
I. Pre-transpilers era
- module pattern
- commonjs
II. Post-transpilers era
- ES Modules
II. Post-transpilers era
ES Modules - Syntax
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 { 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 ...;
II. Post-transpilers era
ES Modules
- webpack+babel
- rollup
Implementations
- Modules are memoized
- Synchronous/dynamic parsing
- Exports live bindings
- Static module structure
Main characteristics
II. Post-transpilers era
ES Modules
Live bindings
let counter = 0;
const incrementCounter = () => counter++;
export { counter, incrementCounter };
counter.js
import { counter, incrementCounter } from './counter';
console.log(counter);
incrementCounter();
console.log(counter);
import { counter, incrementCounter } from './counter';
console.log(counter); // 0
incrementCounter();
console.log(counter); // 1
II. Post-transpilers era
ES Modules
Static module structure
- all import and export statements must be declared at top level
- all import statements are hoisted
- import names are string literals
- all imports are read only views on the exports
II. Post-transpilers era
ES Modules
Read only imports
export default 1+1;
export const a = { b: 2 };
import * as module from './module';
module.a.b = 3;
module.js
import { a } from './module';
a = 2
import * as module from './module';
module.a.b = 3; //SyntaxError: Cannot assign to read-only object
import { a } from './module';
a = 2 // 'a' is read-only
The journey of modules in JavaScript
I. Pre-transpilers era
- module pattern
- commonjs
II. Post-transpilers era
- ES Modules
III. Native era
III. Native era
ES Modules
- Chrome >60, Safari >10.1, Edge (flag), Firefox (flag)
- node.js 9.0.0 ( --experimental-modules flag)
Implementations
III. Native era
ES Modules - Browsers
Syntax I
<script type="module">
import { counter, incrementCounter } from './counter.js';
document.body.append(`${counter}\n`);
incrementCounter();
document.body.append(`${counter}\n`);
</script>
import keyword for static parsing
- new module script type
- <script nomodule /> fallback for bundled apps
III. Native era
ES Modules - Browsers
Syntax II
import('./counter.js').then(counterModule => {
console.log(counterModule.counter); // 0
counterModule.incrementCounter();
console.log(counterModule.counter); // 1
});
* stage 3 proposal (ES2018?)
import function for dynamic parsing
III. Native era
ES Modules - Browsers
Main characteristics
- modules are parsed under "strict mode" pragma
- every module has their own scopes (no more global variable leaks)
- module script tags are defered
- each import is a separate request
- only absolute URLs are supported: ../ ./ /
III. Native era
ES Modules
OK but ... the ECMAScript spec (ecma-262) only talks about syntax ...
enter WHATWG
The Web Hypertext Application Technology Working Group
How the module graph gets processed by the browser
credits: Domenic Denicola
construction
instantiation
evaluation
- resolving of module specifiers: "./a.js" "../b.js"
- imports/exports get wired up
- hoisting kicks in
- modules get evaluated
The journey of modules in JavaScript
I. Pre-transpilers era
- module pattern
- commonjs
II. Post-transpilers era
- ES Modules
III. Native era
The journey of modules in JavaScript
IV. ...
IV. Web Assembly Era
The journey of modules in JavaScript
WASM Modules
Thank you!
Resources and references:
https://github.com/andrei-cacio/es-modules
Anatomy of ES Module
By Andrei Cacio
Anatomy of ES Module
- 2,329