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,120