1 of 90

Native ES Modules - something almost, but not quite entirely unlike CommonJS

Gil Tayar, November 2017

@giltayar

2 of 90

He had found a Nutri-Matic machine which had provided him with a plastic cup […].

The way it functioned was very interesting. When the Drink button was pressed it made an instant but highly detailed examination of the subject's taste buds, a spectroscopic analysis of the subject's metabolism and then sent tiny experimental signals down the neural pathways to the taste centers of the subject's brain to see what was likely to go down well…

3 of 90

… However, no one knew quite why it did this because it invariably delivered a cupful of liquid that was almost, but not quite, entirely unlike tea.”

— Douglas Adams, The Hitchhiker's Guide to the Galaxy

4 of 90

About Me

  • My developer experience goes all the way back to the ‘80s.
  • Am, was, and always will be a developer
  • Testing the code I write is my passion
  • Currently evangelist and architect
  • Applitools
  • We write Visual Testing tools: �If you’re serious about testing, checkout Applitools Eyes

  • Sometimes my arms bend back
  • But the gum I like is coming back in style

5 of 90

ES Modules are coming to NodeJS!

6 of 90

$ node main.js

main.js

const {handle, spout, tea} = � require('./kettle')

console.log(handle)

console.log(spout)

console.log(tea)

7 of 90

$ node --experimental-modules main.mjs

main.mjs

import {handle, spout, tea}

from './kettle'

console.log(handle)

console.log(spout)

console.log(tea)

And they are so similar to CommonJS!

8 of 90

9 of 90

Similarities and Differences

10 of 90

Named exports

11 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

12 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

13 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

14 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

Asynchronous vs Synchronous

15 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

16 of 90

The Rules of ES Modules

  • A module is ESM if and only if extension is “.mjs”
  • A module is CJS if and only if extension is “.js”
  • Only ESM is allowed to use export/import
  • Only CJS is allowed to use require

17 of 90

The Rules of ES Modules

  • A module is ESM if and only if extension is “.mjs”
  • A module is CJS if and only if extension is “.js”
  • Only ESM is allowed to use export/import
  • Only CJS is allowed to use require

Michael Jackson Script!

18 of 90

Binding

19 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export let tea = 'hot tea'

export const heatUp = () =>

(tea = 'scalding tea')

main.mjs

import {handle, spout, tea, heatUp}

from './kettle'

console.log(handle) // the handle

console.log(spout) // the spout

heatUp(); console.log(tea) // scalding tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

module.exports.heatUp = () =>

(module.exports.tea = 'scalding tea')

main.js

const {handle, spout, tea, heatUp} =

require('./kettle')

console.log(handle) // the handle

console.log(spout) // the spout

heatUp(); console.log(tea) // hot tea

20 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export let tea = 'hot tea'

export const heatUp = () =>

(tea = 'scalding tea')

main.mjs

import {handle, spout, tea, heatUp}

from './kettle'

console.log(handle) // the handle

console.log(spout) // the spout

heatUp(); console.log(tea) // scalding tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

module.exports.heatUp = () =>

(module.exports.tea = 'scalding tea')

main.js

const {handle, spout, tea, heatUp} =

require('./kettle')

console.log(handle) // the handle

console.log(spout) // the spout

heatUp(); console.log(tea) // hot tea

21 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export let tea = 'hot tea'

export const heatUp = () =>

(tea = 'scalding tea')

main.mjs

import {handle, spout, tea, heatUp}

from './kettle'

console.log(handle) // the handle

console.log(spout) // the spout

heatUp(); console.log(tea) // scalding tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

module.exports.heatUp = () =>

(module.exports.tea = 'scalding tea')

main.js

const {handle, spout, tea, heatUp} =

require('./kettle')

console.log(handle) // the handle

console.log(spout) // the spout

heatUp(); console.log(tea) // hot tea

22 of 90

Default export

23 of 90

kettle.js

module.exports = scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

main.js

const kettleMaker = require('./kettle')

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

console.log(kettle.spout) // the spout

console.log(kettle.tea) // hot tea

kettle.mjs

export default scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

main.mjs

import kettleMaker from './kettle'

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

console.log(kettle.spout) // the spout

console.log(kettle.tea) // hot tea

24 of 90

kettle.mjs

export default scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

main.mjs

import kettleMaker from './kettle'

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

console.log(kettle.spout) // the spout

console.log(kettle.tea) // hot tea

kettle.js

module.exports = scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

main.js

const kettleMaker = require('./kettle')

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

console.log(kettle.spout) // the spout

console.log(kettle.tea) // hot tea

25 of 90

kettle.mjs

export default scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

main.mjs

import kettleMaker from './kettle'

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

console.log(kettle.spout) // the spout

console.log(kettle.tea) // hot tea

kettle.js

module.exports = scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

main.js

const kettleMaker = require('./kettle')

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

console.log(kettle.spout) // the spout

console.log(kettle.tea) // hot tea

26 of 90

Default + named exports

27 of 90

kettle.js

module.exports = scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

module.exports.potColor = 'black'

main.js

const kettleMaker = require('./kettle')

const {potColor} = kettleMaker

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

// ...

console.log('the pot is', potColor) // the pot is black

kettle.mjs

export default scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

export const potColor = 'black'

main.mjs

import kettleMaker, {potColor} from './kettle'

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

// ...

console.log('the pot is', potColor) // the pot is black

28 of 90

kettle.js

module.exports = scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

module.exports.potColor = 'black'

main.js

const kettleMaker = require('./kettle')

const {potColor} = kettleMaker

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

// ...

console.log('the pot is', potColor) // the pot is black

kettle.mjs

export default scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

export const potColor = 'black'

main.mjs

import kettleMaker, {potColor} from './kettle'

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

// ...

console.log('the pot is', potColor) // the pot is black

29 of 90

kettle.js

module.exports = scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

module.exports.potColor = 'black'

main.js

const kettleMaker = require('./kettle')

const {potColor} = kettleMaker

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

// ...

console.log('the pot is', potColor) // the pot is black

kettle.mjs

export default scalding => {

return {

handle: 'the handle',

spout: 'the spout',

tea: scalding ? 'scalding tea' : 'hot tea',

}

}

export const potColor = 'black'

main.mjs

import kettleMaker, {potColor} from './kettle'

const kettle = kettleMaker(false)

console.log(kettle.handle) // the handle

// ...

console.log('the pot is', potColor) // the pot is black

30 of 90

Renaming exports

31 of 90

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle: handoru,

spout: sosogiguchi, tea: お茶} =

require('./kettle')

console.log(handoru) // ==> the handle

console.log(sosogiguchi) // ==> the spout

console.log(お茶) // ==> hot tea

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle as handoru,

spout as sosogiguchi, tea as お茶}

from './kettle'

console.log(handoru) // ==> the handle

console.log(sosogiguchi) // ==> the spout

console.log(お茶) // ==> hot tea

32 of 90

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle: handoru,

spout: sosogiguchi, tea: お茶} =

require('./kettle')

console.log(handoru) // ==> the handle

console.log(sosogiguchi) // ==> the spout

console.log(お茶) // ==> hot tea

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle as handoru,

spout as sosogiguchi, tea as お茶}

from './kettle'

console.log(handoru) // ==> the handle

console.log(sosogiguchi) // ==> the spout

console.log(お茶) // ==> hot tea

33 of 90

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle: handoru,

spout: sosogiguchi, tea: お茶} =

require('./kettle')

console.log(handoru) // ==> the handle

console.log(sosogiguchi) // ==> the spout

console.log(お茶) // ==> hot tea

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle as handoru,

spout as sosogiguchi, tea as お茶}

from './kettle'

console.log(handoru) // ==> the handle

console.log(sosogiguchi) // ==> the spout

console.log(お茶) // ==> hot tea

Very cool!

34 of 90

File Resolution - index

35 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

Remember?

36 of 90

kettle/index.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle/index.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

37 of 90

File Resolution - package.json

38 of 90

kettle/kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

kettle/package.json

{"main": "kettle.mjs"}

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

kettle/kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

kettle/package.json

{"main": "kettle.js"}

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

39 of 90

File Resolution - node_modules

40 of 90

node_modules/kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from 'kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

node_modules/kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

41 of 90

node_modules/kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

import {handle, spout, tea} from 'kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

node_modules/kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

const {handle, spout, tea} = require('kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

42 of 90

Dynamic import

43 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

async function main() {

const {tea} =

await import('./kettle.mjs')

console.log(tea)

}

main()

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

function main() {

const {tea} =

require('./kettle.js')

console.log(tea)

}

main()

44 of 90

kettle.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.mjs

async function main() {

const {tea} =

await import('./kettle.mjs')

console.log(tea)

}

main()

kettle.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

main.js

function main() {

const {tea} =

require('./kettle.js')

console.log(tea)

}

main()

45 of 90

Module dirname

46 of 90

main.js

const path = require('path')

const fs = require('fs')

console.log(

fs.readFileSync(

path.join(__dirname, 'hello.txt'),

'utf-8'))

main.mjs

import path from 'path'

import fs from 'fs'

import url from 'url'

const __dirname = path.dirname(

new url.URL(import.meta.url).pathname)

console.log(

fs.readFileSync(

path.join(__dirname, 'hello.txt'),

'utf-8'))

47 of 90

main.js

const path = require('path')

const fs = require('fs')

console.log(

fs.readFileSync(

path.join(__dirname, 'hello.txt'),

'utf-8'))

main.mjs

import path from 'path'

import fs from 'fs'

import url from 'url'

const __dirname = path.dirname(

new url.URL(import.meta.url).pathname)

console.log(

fs.readFileSync(

path.join(__dirname, 'hello.txt'),

'utf-8'))

48 of 90

49 of 90

Interoperability

50 of 90

Remember The Rules of ESM?

  • A module is ESM if and only if extension is “.mjs”
  • A module is CJS if and only if extension is “.js”
  • Only ESM is allowed to use export/import
  • Only CJS is allowed to use require

51 of 90

Two separate worlds

.mjs

.mjs

import

.js

.js

require

52 of 90

The Rules of Interoperability

  • MJS can import JS
    • But only default import
  • JS can import MJS
    • But only using dynamic import

53 of 90

Bridge over troubled waters

.mjs

.mjs

import

.js

.js

require

default import

dynamic import

54 of 90

The Rules of Interoperability

  • MJS can import JS
    • But only default import
  • JS can import MJS
    • But only using dynamic import

55 of 90

kettle.js

module.exports = 'short and stout (js)'

main.mjs

import kettle from './kettle'

console.log(kettle) // short and stout (js)

56 of 90

kettle.js

module.exports = 'short and stout (js)'

main.mjs

import kettle from './kettle'

console.log(kettle) // short and stout (js)

57 of 90

The Rules of Interoperability

  • MJS can import JS
    • But only default import
  • JS can import MJS
    • But only using dynamic import

.mjs

.mjs

import

.js

import

58 of 90

kettle.js

module.exports.kettle = 'short and stout (js)'

main.mjs

import {kettle} from './kettle'

console.log(kettle)

Fails with error!

59 of 90

kettle.js

module.exports.kettle = 'short and stout (js)'

main.mjs

import {kettle} from './kettle'

console.log(kettle)

Fails with error!

(only default import)

60 of 90

kettle.js

module.exports.kettle = 'short and stout (js)'

main.mjs

import kettleModule from './kettle'

console.log(kettleModule.kettle) // short and stout (js)

Works!

61 of 90

The Rules of Interoperability

  • MJS can import JS
    • But only default import
  • JS can import MJS
    • But only using dynamic import

.mjs

.mjs

import

.js

import

62 of 90

The Rules of Interoperability

  • MJS can import JS
    • But only default import
  • JS can import MJS
    • But only using dynamic import

.js

.mjs

require

.js

dyamic import

63 of 90

kettle.mjs

export const kettle = 'short and stout (mjs)'

main.js

const {kettle} = require('./kettle')

console.log(kettle)

Fails with error!�(cannot require mjs)

64 of 90

kettle.mjs

export const kettle = 'short and stout (mjs)'

main.js

const {kettle} = require('./kettle')

console.log(kettle)

Fails with error!

Because mjs loading is an async task

65 of 90

kettle.mjs

export const kettle = 'short and stout (mjs)'

main.js

import {kettle} from './kettle'

console.log(kettle)

Fails with error!�(import not allowed in js)

66 of 90

kettle.mjs

export const kettle = 'short and stout (mjs)'

main.js

async function main() {

const {kettle} = await import('./kettle')

console.log(kettle) // short and stout (mjs)

}

main()

Works!

67 of 90

The Rules of Interoperability

  • MJS can import JS
    • But only default import
  • JS can import MJS
    • But only using dynamic import

.mjs

.mjs

import

.js

.js

require

import

dynamic import

68 of 90

Migrating from CJS

69 of 90

Migrating

  • As application developers
  • As library developers

70 of 90

Migrating as application developers

  • Stop using babel!
  • Rename all js to mjs.
  • Change all require-s to imports�If it’s a CJS, then default imports only!
  • Change dynamic require-s to await import()�only if they are really dynamic!

71 of 90

Migrating

  • As application developers
  • As library developers

72 of 90

The Rules of Migration

  • require('no-extension')
    • Only ‘.js’
  • import … from 'no-extension'
    • First ‘.mjs’ (as ESM)
    • Then ‘.js’ (as CJS)

73 of 90

Dual-Mode Libraries

74 of 90

an-esm-module.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

a-cjs-module.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

75 of 90

main.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

main.js

module.exports.spout = 'the spout'

module.exports.handle = 'the handle'

module.exports.tea = 'hot tea'

76 of 90

I have to write my code twice?!

77 of 90

kettle/entry.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

kettle/package.json

{

"name": "kettle",

"main": "entry",

"scripts": {

"build": "babel *.mjs **/*.mjs --out-dir ."

},

"devDependencies": {

"babel-cli": "^6.26.0",

"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",

"babel-plugin-dynamic-import-node": "^1.1.0"

}, ...

78 of 90

kettle/entry.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

kettle/package.json

{

"name": "kettle",

"main": "entry",

"scripts": {

"build": "babel *.mjs **/*.mjs --out-dir ."

},

"devDependencies": {

"babel-cli": "^6.26.0",

"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",

"babel-plugin-dynamic-import-node": "^1.1.0"

}, ...

You write your file mjs-style

79 of 90

kettle/entry.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

kettle/package.json

{

"name": "kettle",

"main": "entry",

"scripts": {

"build": "babel *.mjs **/*.mjs --out-dir ."

},

"devDependencies": {

"babel-cli": "^6.26.0",

"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",

"babel-plugin-dynamic-import-node": "^1.1.0"

}, ...

kettle/.babelrc

{

"plugins": [

"transform-es2015-modules-commonjs",

"dynamic-import-node"

]

}

You write your file mjs-style

And babelize all mjs to js

80 of 90

kettle/.gitignore

*.js

Don’t forget to .gitignore all js files!

81 of 90

main.mjs

import {handle, spout, tea} from './kettle'

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

main.js

const {handle, spout, tea} = require('./kettle')

console.log(handle) // ==> the handle

console.log(spout) // ==> the spout

console.log(tea) // ==> hot tea

82 of 90

Dual-Mode library, bitches!

83 of 90

Summary

84 of 90

ES Modules are coming to NodeJS!

85 of 90

The Rules of ES Modules

  • A module is ESM if and only if extension is “.mjs”
  • A module is CJS if and only if extension is “.js”
  • Only ESM is allowed to use export/import
  • Only CJS is allowed to use require

86 of 90

The Rules of Interoperability

  • MJS can import JS
    • But only default import
  • JS can import MJS
    • But only using dynamic import

.mjs

.mjs

import

.js

.js

require

import

dynamic import

87 of 90

The Rules of Migration

  • require('no-extension')
    • Only ‘.js’
  • import … from ('no-extension')
    • First ‘.mjs’ (as ESM)
    • Then ‘.js’ (as CJS)

88 of 90

Migrating as application developers

  • Stop using babel!
  • Rename all js to mjs.
  • Change all require-s to imports
    • If it’s a CJS, then default imports only!
  • Change dynamic require-s to await import()
    • only if they are really dynamic!

89 of 90

kettle/entry.mjs

export const spout = 'the spout'

export const handle = 'the handle'

export const tea = 'hot tea'

kettle/package.json

{

"name": "kettle",

"main": "entry",

"scripts": {

"build": "babel *.mjs **/*.mjs --out-dir ."

},

"devDependencies": {

"babel-cli": "^6.26.0",

"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",

"babel-plugin-dynamic-import-node": "^1.1.0"

}, ...

kettle/.babelrc

{

"plugins": [

"transform-es2015-modules-commonjs",

"dynamic-import-node"

]

}

You write your file mjs-style

And babelize all mjs to js

90 of 90

Thank You!

Twitter: @giltayar

https://github.com/giltayar/node-esm-tea