Slide 1

Slide 1 text

© 2022 Wantedly, Inc. Hands-on Native ESM Understanding Node.js module discrepancy Nov. 26 2022 - Masaki Hara https://github.com/qnighy https://www.wantedly.com/id/qnighy

Slide 2

Slide 2 text

The aim of this workshop ● Understand how CJS and ESM (does not smoothly) interoperate in Node.js ● Understand how it becomes more difficult when module budler is involved © 2022 Wantedly, Inc. It does not cover the entire solution, but it will nonetheless help you migrating to Native ESM.

Slide 3

Slide 3 text

Related materials ● 実践 Node.js Native ESM — Wantedlyでのアプリケー ション移行事例[1] ● Node.jsのネイティブES Modulesサポートが抱える問題を 解決するBabelプラグインを書いた[2] ● Native ESM + TypeScript 拡張子問題: 歯にものが挟まっ たようなスッキリしない書き流し[3] © 2022 Wantedly, Inc. [1] https://www.wantedly.com/companies/wantedly/post_articles/410531 [2] https://zenn.dev/qnighy/articles/6267716578c76d [3] https://zenn.dev/qnighy/articles/19603f11d5f264

Slide 4

Slide 4 text

Advertisement We are looking for JS ecosystem enthusiasts! © 2022 Wantedly, Inc. WebpackやBabelの設定を通じて開発を加速させたい人募集 ! - Wantedly[1] [1] https://www.wantedly.com/projects/1145492

Slide 5

Slide 5 text

Advertisement Our frontend team also welcomes you! © 2022 Wantedly, Inc. 今見ているWantedlyのWebフロントエンドをもっと良くしませんか? - Wantedly[1] [1] https://www.wantedly.com/projects/1159802

Slide 6

Slide 6 text

Hands-on guide ● Materials can be found at: https://github.com/wantedly/hands-on-native- esm-2022 ● Requires Node.js ≧ 16 ● I will demonstrate chapters 1 to 4. ● Read and exercise the remaining chapters (5 to 11) if you are interested. © 2022 Wantedly, Inc.

Slide 7

Slide 7 text

1: Let’s try ESM! © 2022 Wantedly, Inc.

Slide 8

Slide 8 text

Try Native ESM ● Node.js has two module types: ○ ESM = ES Modules (*.mjs, *.js) ○ CJS = CommonJS Modules (*.cjs, *.js) ● They interoperate badly 😵 ● You usually write JS in ESM but execute it as CJS. © 2022 Wantedly, Inc. (as opposed to Native ESM)

Slide 9

Slide 9 text

Try Native ESM Try ESM import/export © 2022 Wantedly, Inc. export default function(x) { return x * x; } import square from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs

Slide 10

Slide 10 text

Try Native ESM Try ESM import/export © 2022 Wantedly, Inc. export default function(x) { return x * x; } import square from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs $ node app.mjs 4

Slide 11

Slide 11 text

Try Native ESM Open https://babeljs.io/repl © 2022 Wantedly, Inc.

Slide 12

Slide 12 text

Try Native ESM Try CJS transpilation © 2022 Wantedly, Inc. "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = _default; function _default(x) { return x * x; } "use strict"; var _a = _interopRequireDefault(require("./a.cjs")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.log((0, _a.default)(2)); lib.cjs (before Babel) app.cjs (before Babel) export default function(x) { return x * x; } import square from "./lib.cjs"; console.log(square(2));

Slide 13

Slide 13 text

Try Native ESM Try CJS transpilation © 2022 Wantedly, Inc. "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = _default; function _default(x) { return x * x; } "use strict"; var _a = _interopRequireDefault(require("./lib.cjs")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.log((0, _a.default)(2)); lib.cjs app.cjs

Slide 14

Slide 14 text

Try Native ESM Try CJS transpilation © 2022 Wantedly, Inc. "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = _default; function _default(x) { return x * x; } "use strict"; var _a = _interopRequireDefault(require("./lib.cjs")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.log((0, _a.default)(2)); lib.cjs app.cjs $ node app.cjs 4

Slide 15

Slide 15 text

Try Native ESM CJS → ESM © 2022 Wantedly, Inc. "use strict"; var _a = _interopRequireDefault(require("./lib.mjs")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.log((0, _a.default)(2)); app.cjs export default function(x) { return x * x; } lib.mjs

Slide 16

Slide 16 text

Try Native ESM CJS → ESM © 2022 Wantedly, Inc. "use strict"; var _a = _interopRequireDefault(require("./lib.mjs")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.log((0, _a.default)(2)); app.cjs export default function(x) { return x * x; } lib.mjs $ node app.cjs node:internal/modules/cjs/loader:1031 throw new ERR_REQUIRE_ESM(filename, true); ^ Error [ERR_REQUIRE_ESM]: require() of ES Module lib.mjs not supported. Instead change the require of lib.mjs to a dynamic import() which is available in all CommonJS modules.

Slide 17

Slide 17 text

Try Native ESM ESM → CJS © 2022 Wantedly, Inc. "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = _default; function _default(x) { return x * x; } lib.cjs import square from "./lib.cjs"; console.log(square(2)); app.mjs

Slide 18

Slide 18 text

Try Native ESM ESM → CJS © 2022 Wantedly, Inc. "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = _default; function _default(x) { return x * x; } lib.cjs import square from "./lib.cjs"; console.log(square(2)); app.mjs $ node app.mjs app.mjs:3 console.log(square(2)); ^ TypeError: square is not a function at app.mjs:3:13

Slide 19

Slide 19 text

There is no easy path ● Library-first upgrade: essential difficulty ○ Synchronous vs. Asynchronous ● Application-first upgrade: avoidable pitfalls ○ Default import problem ● Dual package: difficulty in some packages ○ State sharing problem © 2022 Wantedly, Inc.

Slide 20

Slide 20 text

But we must do it ● Some libraries are already in Native ESM format. ○ node-fetch ○ d3 ○ etc. ● Depending applications are (almost) forced to do the migration. © 2022 Wantedly, Inc.

Slide 21

Slide 21 text

2: Module detection © 2022 Wantedly, Inc.

Slide 22

Slide 22 text

How Node.js determines module types ● Node.js determines[1] module types using: ○ File extensions (*.cjs / *.mjs) ○ Package metadata (“type”: “module”) if it is *.js © 2022 Wantedly, Inc. *.cjs *.js *.mjs “type”: “commonjs” CJS CJS ESM no “type” field CJS CJS ESM “type”: “module” CJS ESM ESM Conversely, they are not taken into account: ● File contents ● How the import had been routed (e.g. exports map) [1] https://nodejs.org/api/packages.html#packagejson-and-file-extensions

Slide 23

Slide 23 text

How Node.js determines module types Check module types © 2022 Wantedly, Inc. if (typeof await /0/["test"] === "function") { console.log("I'm ESM"); } else if (typeof require === "function") { console.log("I'm CJS"); } else { console.log("I'm other"); } module-type.js

Slide 24

Slide 24 text

Check module types © 2022 Wantedly, Inc. if (typeof await /0/["test"] === "function") { console.log("I'm ESM"); } else if (typeof require === "function") { console.log("I'm CJS"); } else { console.log("I'm other"); } module-type.js $ node module-type.js I’m CJS $ node module-type.cjs I’m CJS $ node module-type.mjs I’m ESM How Node.js determines module types

Slide 25

Slide 25 text

Module type depends on package.json © 2022 Wantedly, Inc. { "type": "module" } package.json How Node.js determines module types

Slide 26

Slide 26 text

Module type depends on package.json © 2022 Wantedly, Inc. { "type": "module" } package.json $ node module-type.js I’m ESM $ node module-type.cjs I’m CJS $ node module-type.mjs I’m ESM How Node.js determines module types

Slide 27

Slide 27 text

3: Semantics of execution © 2022 Wantedly, Inc. Module asynchrony

Slide 28

Slide 28 text

Semantics of execution ● Modules are executed ○ synchronously in CJS ○ asynchronously in ESM ● Consequence👉 CJS cannot import ESM[1] © 2022 Wantedly, Inc. [1]: Precisely speaking, it can wait for the imported module to finish.

Slide 29

Slide 29 text

Do a time-consuming initialization © 2022 Wantedly, Inc. import { setTimeout } from "node:timers/promises"; await setTimeout(3000); // For Node.js < 16.0.0 // await new Proise((r) => setTimeout(r, 3000)); console.log("slept 1s"); sleep.mjs Explicit asynchrony: top-level await

Slide 30

Slide 30 text

Dependent module may also be asynchronous © 2022 Wantedly, Inc. import { setTimeout } from "node:timers/promises"; await setTimeout(3000); export const value = 42; sleep-lib.mjs Implied asynchrony

Slide 31

Slide 31 text

Dependent module may also be asynchronous © 2022 Wantedly, Inc. import { value } from "./sleep-lib.mjs"; console.log("value =", value); sleep-app.mjs Implied asynchrony

Slide 32

Slide 32 text

Dependent module may also be asynchronous © 2022 Wantedly, Inc. const { value } = await import("./sleep-lib.mjs"); console.log("value =", value); sleep-app.mjs (alternative) Expanding implied asynchrony

Slide 33

Slide 33 text

Require — this is a mere function © 2022 Wantedly, Inc. const { value } = require("./sleep-lib.mjs"); console.log("value =", value); sleep-app.cjs Discrepancy in asynchrony Error!

Slide 34

Slide 34 text

Require — this is a mere function © 2022 Wantedly, Inc. const { value } = require("./sleep-lib.mjs"); console.log("value =", value); sleep-app.cjs Discrepancy in asynchrony Error! $ node sleep-app.cjs node:internal/modules/cjs/loader:1031 throw new ERR_REQUIRE_ESM(filename, true); ^ Error [ERR_REQUIRE_ESM]: require() of ES Module sleep-lib.mjs not supported.

Slide 35

Slide 35 text

TLA — this is not possible © 2022 Wantedly, Inc. const { value } = await import("./sleep-lib.mjs"); console.log("value =", value); sleep-app.cjs Discrepancy in asynchrony Error!

Slide 36

Slide 36 text

TLA — this is not possible © 2022 Wantedly, Inc. const { value } = await import("./sleep-lib.mjs"); console.log("value =", value); sleep-app.cjs Discrepancy in asynchrony Error! $ node sleep-app.cjs sleep-app.cjs:1 const { value } = await import("./sleep-lib.mjs"); ^^^^^ SyntaxError: await is only valid in async functions and the top level bodies of modules

Slide 37

Slide 37 text

Import-then © 2022 Wantedly, Inc. import("./sleep-lib.mjs").then(({ value }) => { console.log("value =", value); }); sleep-app.cjs Discrepancy in asynchrony

Slide 38

Slide 38 text

Import-then: it’s too late for exporting © 2022 Wantedly, Inc. import("./sleep-lib.mjs").then(({ value }) => { console.log("value =", value); exports.value2 = value * 2; }); sleep-app.cjs Discrepancy in asynchrony Too late!

Slide 39

Slide 39 text

4: Default export semantics © 2022 Wantedly, Inc.

Slide 40

Slide 40 text

Default export semantics ● Default export is ○ fancy namespace export in CJS ○ special named export in ESM ● Consequence👉 incompatible translations ● My library node-cjs-interop[1] helps here. © 2022 Wantedly, Inc. [1] https://github.com/qnighy/node-cjs-interop

Slide 41

Slide 41 text

Default export semantics © 2022 Wantedly, Inc. Default Named export default f; export { f }; module.exports = f; exports.f = f;

Slide 42

Slide 42 text

Default export semantics from importer’s viewpoint © 2022 Wantedly, Inc. import * as ns from ""; ✅ Namespace ❌ Default ✅ Named import f from ""; import { f } from ""; const ns = require(""); const f = require(""); const { f } = require(""); = ∈

Slide 43

Slide 43 text

Default export semantics from importer’s viewpoint © 2022 Wantedly, Inc. import * as ns from ""; ✅ Namespace ❌ Default ✅ Named import { default as f } from ""; import { f } from ""; const ns = require(""); const f = require(""); const { f } = require(""); = ∈

Slide 44

Slide 44 text

Default export interop 1: Node.js rule (CJS-preserving) © 2022 Wantedly, Inc. import * as ns from ""; import f from ""; import { f } from ""; const ns = require(""); const f = require(""); const { f } = require(""); = ∈ (except “default”) (except “default”)

Slide 45

Slide 45 text

Default export interop 2: simple mapping (ESM-preserving) © 2022 Wantedly, Inc. import * as ns from ""; import f from ""; import { f } from ""; const ns = require(""); const f = require(""); const { f } = require(""); = ∈ (Loss of information)

Slide 46

Slide 46 text

Default export interop 3: Babel mapping © 2022 Wantedly, Inc. var _react = _interopRequireDefault(require("react")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } Simple mapping (ESM-preserving)

Slide 47

Slide 47 text

Default export interop 3: Babel mapping © 2022 Wantedly, Inc. var _react = _interopRequireDefault(require("react")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } Node.js rule (CJS-preserving)

Slide 48

Slide 48 text

Default export interop – tool support © 2022 Wantedly, Inc. Node.js rule Babel rule (hybrid) Simple mapping Node.js ✅ ❌ but see: node-cjs-interop[1] ❌ Webpack javascript/esm javascript/auto ❌ Babel interop = node interop = babel interop = none TypeScript ❌ esModuleInterop = true esModuleInterop = false [1] https://github.com/qnighy/node-cjs-interop

Slide 49

Slide 49 text

4.1: Default export semantics (1) © 2022 Wantedly, Inc. Default export in ESM

Slide 50

Slide 50 text

Default export in ESM Default export and default import in ESM © 2022 Wantedly, Inc. export default function(x) { return x * x; } import square from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs

Slide 51

Slide 51 text

Default export in ESM Default export and default import in ESM © 2022 Wantedly, Inc. export default function(x) { return x * x; } import square from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs $ node app.mjs 4

Slide 52

Slide 52 text

Default export in ESM Default import is a syntax sugar © 2022 Wantedly, Inc. export default function(x) { return x * x; } import { default as square } from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs

Slide 53

Slide 53 text

Default export in ESM Default import is a syntax sugar © 2022 Wantedly, Inc. export default function(x) { return x * x; } import { default as square } from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs $ node app.mjs 4

Slide 54

Slide 54 text

Default export in ESM Default export is a syntax sugar © 2022 Wantedly, Inc. function square(x) { return x * x; } export { square as default }; import square from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs

Slide 55

Slide 55 text

Default export in ESM Default export is a syntax sugar © 2022 Wantedly, Inc. function square(x) { return x * x; } export { square as default }; import square from "./lib.mjs"; console.log(square(2)); lib.mjs app.mjs $ node app.mjs 4

Slide 56

Slide 56 text

4.2: Default export semantics (2) © 2022 Wantedly, Inc. Default export in CJS

Slide 57

Slide 57 text

Default export in CJS Default export and default import in CJS © 2022 Wantedly, Inc. module.exports = 42; const x = require("./lib.cjs"); console.log(x); lib.cjs app.cjs

Slide 58

Slide 58 text

Default export in CJS Default export and default import in CJS © 2022 Wantedly, Inc. module.exports = 42; const x = require("./lib.cjs"); console.log(x); lib.cjs app.cjs $ node app.mjs 42

Slide 59

Slide 59 text

Node.js rule for default export Default export interop © 2022 Wantedly, Inc. module.exports = 42; import x from "./app.cjs"; console.log(x); lib.cjs app.mjs

Slide 60

Slide 60 text

Node.js rule for default export Default export interop © 2022 Wantedly, Inc. module.exports = 42; import x from "./app.cjs"; console.log(x); lib.cjs app.mjs $ node app.mjs 42

Slide 61

Slide 61 text

4.3: Default export semantics (3) © 2022 Wantedly, Inc. Named exports in CJS

Slide 62

Slide 62 text

Named exports in CJS Named exports in CJS © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; const m = require("./lib.cjs"); console.log(m); lib.cjs app.cjs

Slide 63

Slide 63 text

Named exports in CJS Named exports in CJS © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; const m = require("./lib.cjs"); console.log(m); lib.cjs app.cjs $ node app.cjs { foo: 1, bar: 2 }

Slide 64

Slide 64 text

Node.js rule for named exports Named exports interop © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); lib.cjs app.mjs

Slide 65

Slide 65 text

Node.js rule for named exports Named exports interop © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); lib.cjs app.mjs $ node app.mjs { foo: 1, bar: 2 } [Module: null prototype] { bar: 2, default: { foo: 1, bar: 2 }, foo: 1 }

Slide 66

Slide 66 text

Node.js rule for named “default” exports Named “default” exports interop © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); lib.cjs app.mjs

Slide 67

Slide 67 text

Node.js rule for named “default” exports Named “default” exports interop © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); lib.cjs app.mjs $ node app.mjs { foo: 1, bar: 2, default: 3 } [Module: null prototype] { bar: 2, default: { foo: 1, bar: 2, default: 3 }, foo: 1 }

Slide 68

Slide 68 text

4.4: Default export semantics (4) © 2022 Wantedly, Inc. Babel-style interop

Slide 69

Slide 69 text

Mixing CJS named and default exports Named + default exports in CJS © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; module.exports = 3; lib.cjs import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); app.mjs

Slide 70

Slide 70 text

Mixing CJS named and default exports Named + default exports in CJS © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; module.exports = 3; lib.cjs import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); app.mjs $ node app.cjs 3 [Module: null prototype] { bar: undefined, default: 3, foo: undefined }

Slide 71

Slide 71 text

Babel-style interop Special flag for simple mapping © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; exports.__esModule = true; lib.cjs app.mjs import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2);

Slide 72

Slide 72 text

Babel-style interop Special flag for simple mapping © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; exports.__esModule = true; var m2 = _interopRequireWildcard( require("./lib.cjs")); console.log(m2.default); console.log(m2); lib.cjs app.cjs (transpiled from app.mjs)

Slide 73

Slide 73 text

Babel-style interop Special flag for simple mapping © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; exports.__esModule = true; "use strict"; var m2 = _interopRequireWildcard(require("./lib.cjs")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } console.log(m2.default); console.log(m2); lib.cjs app.cjs (transpiled from app.mjs)

Slide 74

Slide 74 text

Babel-style interop Special flag for simple mapping © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; exports.__esModule = true; "use strict"; var m2 = _interopRequireWildcard(require("./lib.cjs")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } console.log(m2.default); console.log(m2); lib.cjs app.cjs (transpiled from app.mjs) $ node app.cjs 3 { foo: 1, bar: 2, default: 3, __esModule: true }

Slide 75

Slide 75 text

Babel-style interop Node.js does not support __esModule © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; exports.__esModule = true; lib.cjs import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); app.mjs

Slide 76

Slide 76 text

Babel-style interop Node.js does not support __esModule © 2022 Wantedly, Inc. exports.foo = 1; exports.bar = 2; exports.default = 3; exports.__esModule = true; lib.cjs import m1 from "./lib.cjs"; import * as m2 from "./lib.cjs"; console.log(m1); console.log(m2); app.mjs $ node app.mjs { foo: 1, bar: 2, default: 3 } [Module: null prototype] { __esModule: true, bar: 2, default: { foo: 1, bar: 2, default: 3 }, foo: 1 }

Slide 77

Slide 77 text

End of demonstration Enjoy the remaining exercises if you are interested in. © 2022 Wantedly, Inc.

Slide 78

Slide 78 text

5: Export analysis © 2022 Wantedly, Inc.

Slide 79

Slide 79 text

Export analysis ● Static analysis is mandatory in Native ESM, and it also applies to CJS imported from ESM. ● It sometimes fails to detect exports in CJS. © 2022 Wantedly, Inc.

Slide 80

Slide 80 text

Export analysis Node.js analyzes ESM exports before execution © 2022 Wantedly, Inc. console.log("Initializing..."); export const foo = 1; lib.mjs import { bar } from "./lib.mjs"; console.log(bar); app.mjs

Slide 81

Slide 81 text

Export analysis Node.js analyzes ESM exports before execution © 2022 Wantedly, Inc. console.log("Initializing..."); export const foo = 1; lib.mjs import { bar } from "./lib.mjs"; console.log(bar); app.mjs $ node app.mjs app.mjs:1 import { bar } from "./lib.mjs"; ^^^ SyntaxError: The requested module './lib.mjs' does not provide an export named 'bar'

Slide 82

Slide 82 text

Export analysis Node.js analyzes ESM exports before execution © 2022 Wantedly, Inc. console.log("Initializing..."); export const foo = 1; lib.mjs import { bar } from "./lib.mjs"; console.log(bar); app.mjs $ node app.mjs app.mjs:1 import { bar } from "./lib.mjs"; ^^^ SyntaxError: The requested module './lib.mjs' does not provide an export named 'bar' No “Initializing…” printed

Slide 83

Slide 83 text

Export analysis It analyzes CJS too (when imported from ESM) © 2022 Wantedly, Inc. console.log("Initializing..."); exports.foo = 1; lib.cjs import { bar } from "./lib.cjs"; console.log(bar); app.mjs

Slide 84

Slide 84 text

Export analysis It analyzes CJS too (when imported from ESM) © 2022 Wantedly, Inc. console.log("Initializing..."); exports.foo = 1; lib.cjs import { bar } from "./lib.cjs"; console.log(bar); app.mjs $ node app.mjs app.mjs:1 import { bar } from "./lib.cjs"; ^^^ SyntaxError: Named export 'bar' not found. The requested module './lib.cjs' is a CommonJS module, which may not support all module.exports as named exports.

Slide 85

Slide 85 text

Export analysis It analyzes CJS too (when imported from ESM) © 2022 Wantedly, Inc. console.log("Initializing..."); exports.bar = 2; lib.cjs import { bar } from "./app.cjs"; console.log(bar); app.mjs

Slide 86

Slide 86 text

Export analysis It analyzes CJS too (when imported from ESM) © 2022 Wantedly, Inc. console.log("Initializing..."); exports.bar = 2; lib.cjs import { bar } from "./app.cjs"; console.log(bar); app.mjs $ node app.mjs Initializing... 2

Slide 87

Slide 87 text

Export analysis It analyzes CJS too (when imported from ESM) © 2022 Wantedly, Inc. console.log("Initializing..."); exports["bar"] = 2; lib.cjs import { bar } from "./lib.cjs"; console.log(bar); app.mjs

Slide 88

Slide 88 text

Export analysis It analyzes CJS too (when imported from ESM) © 2022 Wantedly, Inc. console.log("Initializing..."); exports["bar"] = 2; lib.cjs import { bar } from "./lib.cjs"; console.log(bar); app.mjs $ node app.mjs Initializing... 2

Slide 89

Slide 89 text

Export analysis Not all exports can be analyzed © 2022 Wantedly, Inc. console.log("Initializing..."); exports["ba" + "r"] = 2; lib.cjs import { bar } from "./lib.cjs"; console.log(bar); app.mjs

Slide 90

Slide 90 text

Export analysis Not all exports can be analyzed © 2022 Wantedly, Inc. console.log("Initializing..."); exports["ba" + "r"] = 2; lib.cjs import { bar } from "./lib.cjs"; console.log(bar); app.mjs $ node app.mjs app.mjs:1 import { bar } from "./lib.cjs"; ^^^ SyntaxError: Named export 'bar' not found. The requested module './lib.cjs' is a CommonJS module, which may not support all module.exports as named exports.

Slide 91

Slide 91 text

Export analysis rules ● Rules can be found at cjs-module-lexer[1] © 2022 Wantedly, Inc. [1] https://github.com/nodejs/cjs-module-lexer exports.foo = 1; module.exports.foo = 1; exports["foo"] = 1; module.exports = { foo }; module.exports = { foo: bar }; Object.defineProperty(exports, "foo", ); { value: 1 } { enumerable: true, value: 1 } { get() { return bar; } } { get: function() { return bar; } } { get() { return m.bar; } } { get() { return m["bar"]; } }

Slide 92

Slide 92 text

Export analysis rules ● Rules can be found at cjs-module-lexer[1] © 2022 Wantedly, Inc. [1] https://github.com/nodejs/cjs-module-lexer module.exports = require("mod"); module.exports = { ...require("mod") }; __export(require("mod")); __exportStar(require("mod"), exports); var m = require("mod"); const m = require("mod"); let m = require("mod"); var m = _interopRequireWildcard (require("mod")); Object.keys(m).forEach(...); +

Slide 93

Slide 93 text

6: Live exports © 2022 Wantedly, Inc.

Slide 94

Slide 94 text

Live binding of exports ● In ESM, imported variables reference the latest value of the original variable. ● This is not the case with CJS interop in Node.js. © 2022 Wantedly, Inc.

Slide 95

Slide 95 text

Live binding of exports counter evaluates to the latest value © 2022 Wantedly, Inc. export let counter = 0; export function countUp() { counter++; } lib.mjs import { counter, countUp } from "./lib.cjs"; console.log(counter); countUp(); console.log(counter); app.mjs

Slide 96

Slide 96 text

Live binding of exports counter evaluates to the latest value © 2022 Wantedly, Inc. export let counter = 0; export function countUp() { counter++; } lib.mjs import { counter, countUp } from "./lib.cjs"; console.log(counter); countUp(); console.log(counter); app.mjs $ node app.mjs 0 1

Slide 97

Slide 97 text

Live binding of exports counter evaluates to the value when CJS is imported © 2022 Wantedly, Inc. exports.counter = 0; exports.countUp = function() { exports.counter++; } lib.cjs import { counter, countUp } from "./lib.cjs"; console.log(counter); countUp(); console.log(counter); app.mjs

Slide 98

Slide 98 text

Live binding of exports counter evaluates to the value when CJS is imported © 2022 Wantedly, Inc. exports.counter = 0; exports.countUp = function() { exports.counter++; } lib.cjs import { counter, countUp } from "./lib.cjs"; console.log(counter); countUp(); console.log(counter); app.mjs $ node app.mjs 0 0

Slide 99

Slide 99 text

7: Module path resolution © 2022 Wantedly, Inc.

Slide 100

Slide 100 text

Module path resolution ● import() no longer automatically adds extensions. ● It no longer automatically resolves directories as “index.js”. ● It no longer resolves “folder main”. ● *.cjs is not added for either CJS or ESM. © 2022 Wantedly, Inc.

Slide 101

Slide 101 text

Module path resolution “.js” is added in CJS © 2022 Wantedly, Inc. console.log("imported"); lib.js require("./lib"); app.cjs

Slide 102

Slide 102 text

Module path resolution “.js” is added in CJS © 2022 Wantedly, Inc. console.log("imported"); lib.js require("./lib"); app.cjs $ node app.cjs imported

Slide 103

Slide 103 text

Module path resolution “.js” is no longer added in ESM © 2022 Wantedly, Inc. console.log("imported"); lib.js import "./lib"; app.mjs

Slide 104

Slide 104 text

Module path resolution “.js” is no longer added in ESM © 2022 Wantedly, Inc. console.log("imported"); lib.js import "./lib"; app.mjs $ node app.mjs node:internal/process/esm_loader:97 internalBinding('errors').triggerUncaughtE xception( ^ Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'lib' imported from app.mjs Did you mean to import ../lib.js?

Slide 105

Slide 105 text

Module path resolution ./index.js is added in CJS © 2022 Wantedly, Inc. console.log("imported"); lib/index.js require("./lib"); app.cjs

Slide 106

Slide 106 text

Module path resolution ./index.js is added in CJS © 2022 Wantedly, Inc. console.log("imported"); lib/index.js require("./lib"); app.cjs $ node app.cjs imported

Slide 107

Slide 107 text

Module path resolution ./index.js is no longer added in ESM © 2022 Wantedly, Inc. console.log("imported"); lib/index.js import "./lib"; app.mjs

Slide 108

Slide 108 text

Module path resolution ./index.js is no longer added in ESM © 2022 Wantedly, Inc. console.log("imported"); lib/index.js import "./lib"; app.mjs $ node app.mjs node:internal/process/esm_loader:97 internalBinding('errors').triggerUncaughtExcep tion( ^ Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import 'lib' is not supported resolving ES modules imported from app.mjs Did you mean to import ../lib/index.js?

Slide 109

Slide 109 text

Module path resolution “Folder main” in CJS © 2022 Wantedly, Inc. console.log("imported"); lib/main.js require("./lib"); app.cjs { "main": "./main.js" } lib/package.json

Slide 110

Slide 110 text

Module path resolution “Folder main” in CJS © 2022 Wantedly, Inc. console.log("imported"); lib/main.js require("./lib"); app.cjs { "main": "./main.js" } lib/package.json $ node app.cjs imported

Slide 111

Slide 111 text

Module path resolution “Folder main” is no longer available © 2022 Wantedly, Inc. console.log("imported"); lib/main.js import "./lib"; app.mjs { "main": "./main.js" } lib/package.json

Slide 112

Slide 112 text

Module path resolution “Folder main” is no longer available © 2022 Wantedly, Inc. console.log("imported"); lib/main.js import "./lib"; app.mjs { "main": "./main.js" } lib/package.json $ node app.mjs node:internal/process/esm_loader:97 internalBinding('errors').triggerUncaughtExcep tion( ^ Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import 'lib' is not supported resolving ES modules imported from app.mjs Did you mean to import ../lib/main.js?

Slide 113

Slide 113 text

Module path resolution “Package main” is still valid © 2022 Wantedly, Inc. console.log("imported"); node_modules/lib/main.js import "lib"; app.mjs { "main": "./main.js" } node_modules/lib/package.json

Slide 114

Slide 114 text

Module path resolution “Package main” is still valid © 2022 Wantedly, Inc. console.log("imported"); node_modules/lib/main.js import "lib"; app.mjs { "main": "./main.js" } node_modules/lib/package.json $ node app.mjs imported

Slide 115

Slide 115 text

Module path resolution “.cjs” is not added even from CJS © 2022 Wantedly, Inc. console.log("imported"); lib.cjs require("./lib"); app.cjs

Slide 116

Slide 116 text

Module path resolution “.cjs” is not added even from CJS © 2022 Wantedly, Inc. console.log("imported"); lib.cjs require("./lib"); app.cjs $ node app.mjs node:internal/modules/cjs/loader:988 throw err; ^ Error: Cannot find module './lib

Slide 117

Slide 117 text

8: Module metadata © 2022 Wantedly, Inc.

Slide 118

Slide 118 text

Module metadata ● These CJS variables are usually available in to-be-transpiled ESM code: ○ require ○ exports ○ module ○ __filename ○ __dirname ● This is no longer the case in Native ESM. © 2022 Wantedly, Inc.

Slide 119

Slide 119 text

Module metadata CJS metavariables are not available © 2022 Wantedly, Inc. console.log(`I'm ${__filename}`); export {}; meta.mjs

Slide 120

Slide 120 text

Module metadata CJS metavariables are not available © 2022 Wantedly, Inc. console.log(`I'm ${__filename}`); export {}; meta.mjs $ node meta.mjs file:////meta.mjs:1 console.log(`I'm ${__filename}`); ^ ReferenceError: __filename is not defined in ES module scope

Slide 121

Slide 121 text

Module metadata They are usually transpiled as-is © 2022 Wantedly, Inc. "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); console.log(`I'm ${__filename}`); meta.cjs (transpiled from meta.mjs)

Slide 122

Slide 122 text

Module metadata They are usually transpiled as-is © 2022 Wantedly, Inc. "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); console.log(`I'm ${__filename}`); meta.cjs (transpiled from meta.mjs) $ node meta.cjs I’m /meta.cjs

Slide 123

Slide 123 text

Module metadata Use import.meta instead in Native ESM © 2022 Wantedly, Inc. console.log(`I'm ${import.meta.url}`); export {}; meta.mjs

Slide 124

Slide 124 text

Module metadata Use import.meta instead in Native ESM © 2022 Wantedly, Inc. console.log(`I'm ${import.meta.url}`); export {}; meta.mjs $ node meta.mjs I’m file:///meta.mjs

Slide 125

Slide 125 text

9: Dual package © 2022 Wantedly, Inc.

Slide 126

Slide 126 text

Dual package ● Export maps allow packages to provide different module types at once. ● There is a pitfall when the package is stateful (or impure in some sense). © 2022 Wantedly, Inc.

Slide 127

Slide 127 text

Dual package Native ESM dual package © 2022 Wantedly, Inc. { "name": "esm-test", "exports": { "import": "./lib.mjs", "require": "./lib.cjs", } } package.json console.log("ESM entrypoint"); lib.mjs console.log("CJS entrypoint"); lib.cjs

Slide 128

Slide 128 text

Dual package Using import and require © 2022 Wantedly, Inc. import "esm-test"; app.mjs require("esm-test"); app.cjs package.json lib.mjs lib.cjs

Slide 129

Slide 129 text

Dual package Using import and require © 2022 Wantedly, Inc. import "esm-test"; app.mjs require("esm-test"); app.cjs package.json lib.mjs lib.cjs $ node app.mjs ESM entrypoint $ node app.cjs CJS entrypoint

Slide 130

Slide 130 text

Dual package Double side effects © 2022 Wantedly, Inc. import "esm-test"; import "./app.cjs"; app.mjs require("esm-test"); app.cjs package.json lib.mjs lib.cjs

Slide 131

Slide 131 text

Dual package Double side effects © 2022 Wantedly, Inc. import "esm-test"; import "./app.cjs"; app.mjs require("esm-test"); app.cjs package.json lib.mjs lib.cjs $ node app.mjs ESM entrypoint CJS entrypoint Imagine this is state initialization. There are two different states now!

Slide 132

Slide 132 text

10: Modules in Webpack © 2022 Wantedly, Inc.

Slide 133

Slide 133 text

Modules in bundlers ● ESM story in Node.js gets more complicated when module bundlers are involved. ● In this workshop, we use Webpack (version 5) as an example because it is still the most influential bundler. © 2022 Wantedly, Inc.

Slide 134

Slide 134 text

Modules in Webpack ● Webpack generally adheres to Node.js behavior. ● One big difference: Webpack has two different ESM modes. ● It complicates the matter in default export interop. © 2022 Wantedly, Inc.

Slide 135

Slide 135 text

Autodetected ESM (Fake ESM) ● Webpack has “auto detection” module mode. ● It semantically behaves like CJS, even if the module is detected to be ESM. © 2022 Wantedly, Inc. *.cjs *.js *.mjs “type”: “commonjs” CJS CJS ESM no “type” field CJS Auto ESM “type”: “module” CJS ESM ESM In Webpack terminology: ● CJS = javascript/dynamic ● Auto = javascript/auto ● ESM = javascript/esm

Slide 136

Slide 136 text

Autodetected ESM (Fake ESM) ● Webpack has “auto detection” module mode. ● It semantically behaves like CJS, even if the module is detected to be ESM. © 2022 Wantedly, Inc. *.cjs *.js *.mjs “type”: “commonjs” CJS CJS ESM no “type” field CJS Auto ESM “type”: “module” CJS ESM ESM *.cjs *.js *.mjs “type”: “commonjs” CJS CJS ESM no “type” field CJS CJS ESM “type”: “module” CJS ESM ESM Recap: Node.js behavior

Slide 137

Slide 137 text

Resolution for autodetected ESM ● Special lookup rule to detect Webpack © 2022 Wantedly, Inc. { "module": "esm/main.js", "main": "main.js", "exports": { "module": "./esm/main.js", "default": "./main.js" } } package.json This is usually CJS This is usually an ESM module to be auto-detected by Webpack

Slide 138

Slide 138 text

Resolution for autodetected ESM ● Two types of dual package © 2022 Wantedly, Inc. { "exports": { "module": "./esm/main.js", "default": "./main.js" } } package.json { "exports": { "import": "./esm/main.mjs", "require": "./main.cjs" } } package.json Webpack uses ESM. Node.js always uses CJS. Webpack uses ESM. Node.js (import) uses ESM. Node.js (require) uses CJS.

Slide 139

Slide 139 text

Default export semantics in Node.js © 2022 Wantedly, Inc. CJS from Babel (*.js, *.cjs) Native ESM (*.js, *.mjs) Breaks default export

Slide 140

Slide 140 text

Default export semantics in Webpack © 2022 Wantedly, Inc. Autodetected ESM (*.js) CJS from Babel (*.js, *.cjs) Native ESM (*.js, *.mjs) Preserves default export Preserves default export Breaks default export

Slide 141

Slide 141 text

Set up Webpack Install Webpack © 2022 Wantedly, Inc. {} package.json $ yarn add --dev webpack webpack-cli Or: npm install --dev webpack webpack-cli

Slide 142

Slide 142 text

Set up Webpack Install Webpack © 2022 Wantedly, Inc. { "devDependencies": { "webpack": "^5.75.0", "webpack-cli": "^5.0.0" } } package.json (result)

Slide 143

Slide 143 text

Set up Webpack Check import result © 2022 Wantedly, Inc. exports.default = 1; exports.__esModule = true; lib1.js import a from "./lib1.js"; console.log("a =", a); export default 1; lib2.js import b from "./lib1.js"; import c from "./lib2.js"; console.log("b =", b); console.log("c =", c); app.mjs

Slide 144

Slide 144 text

Set up Webpack Check import result © 2022 Wantedly, Inc. exports.default = 1; exports.__esModule = true; lib1.js import a from "./lib1.js"; console.log("a =", a); export default 1; lib2.js import b from "./lib1.js"; import c from "./lib2.js"; console.log("b =", b); console.log("c =", c); app.mjs $ yarn webpack ./app.mjs --mode production --target node -o . (..snip..) webpack 5.75.0 compiled successfully in 122 ms ✨ Done in 0.50s.

Slide 145

Slide 145 text

Set up Webpack Check import result © 2022 Wantedly, Inc. exports.default = 1; exports.__esModule = true; lib1.js import a from "./lib1.js"; console.log("a =", a); export default 1; lib2.js import b from "./lib1.js"; import c from "./lib2.js"; console.log("b =", b); console.log("c =", c); app.mjs $ node main.js a = 1 b = { default: 1, __esModule: true } c = 1

Slide 146

Slide 146 text

11: Meta APIs for modules © 2022 Wantedly, Inc.

Slide 147

Slide 147 text

Meta APIs for modules ● Node.js implements `import` and `require` separately. ○ Not just the parser, but the whole module lifecycle is implemented differently. ● Consequently, they expose different APIs for ESM and CJS. © 2022 Wantedly, Inc.

Slide 148

Slide 148 text

Loader Hook: CJS ● require.extensions[2] exists for this. ● Isn’t it deprecated? Yes, but even @babel/register continues using it. ● Things could easily go wrong with this API. Use the pirate[2] package for reliability. © 2022 Wantedly, Inc. [1] https://nodejs.org/api/modules.html#requireextensions [2] https://www.npmjs.com/package/pirates

Slide 149

Slide 149 text

Loader Hook: CJS © 2022 Wantedly, Inc. const oldLoader = require.extensions[".js"]; require.extensions[".js"] = function(mod, filename) { const oldCompile = mod._compile; mod._compile = function(code, filename) { const newCode = `console.log(__filename);` + code; return oldCompile.call(this, newCode, filename); }; oldLoader(mod, filename); }; register.cjs

Slide 150

Slide 150 text

Loader Hook: CJS © 2022 Wantedly, Inc. module.exports = 42; lib.cjs const x = require("./lib.cjs"); console.log(x); app.cjs register.cjs

Slide 151

Slide 151 text

Loader Hook: CJS © 2022 Wantedly, Inc. module.exports = 42; lib.cjs const x = require("./lib.cjs"); console.log(x); app.cjs register.cjs $ node -r ./register.cjs app.cjs app.cjs lib.cjs 42

Slide 152

Slide 152 text

Loader Hook: ESM ● Conversely, Node.js has a (truly) public loader API[1] for ESM. ● It is still experimental and subject to change. © 2022 Wantedly, Inc. [1] https://nodejs.org/api/esm.html#loaders

Slide 153

Slide 153 text

Loader Hook: ESM © 2022 Wantedly, Inc. export async function load(url, context, nextLoad) { const m = await nextLoad(url, context); if (m.format !== "module") return m; if (typeof m.source !== "string") { m.source = new TextDecoder().decode(m.source); } m.source = `console.log(import.meta.url); ${m.source}`; return m; } loader.mjs

Slide 154

Slide 154 text

Loader Hook: ESM © 2022 Wantedly, Inc. export default 42; lib.mjs import x from "./lib.mjs"; console.log(x); app.mjs loader.mjs

Slide 155

Slide 155 text

Loader Hook: ESM © 2022 Wantedly, Inc. export default 42; lib.mjs import x from "./lib.cjs"; console.log(x); app.mjs loader.mjs $ node --experimental-loader ./loader.mjs ./app.mjs file:///app.cjs file:///lib.cjs 42

Slide 156

Slide 156 text

Parsing modules ● Want to re-implement module loader? ○ Jest does this ● For CJS, Function suffices. ● For ESM, we need vm.Module (experimental). © 2022 Wantedly, Inc.

Slide 157

Slide 157 text

Parsing CJS modules © 2022 Wantedly, Inc. const fs = require("node:fs"); const m = { exports: {} }; const f = new Function( "exports", "require", "module", "__filename", "__dirname", fs.readFileSync("lib.cjs", "utf-8")); f(m.exports, null, m, "", ""); console.log(m.exports); app.cjs

Slide 158

Slide 158 text

Parsing CJS modules © 2022 Wantedly, Inc. app.cjs module.exports = 42; lib.cjs

Slide 159

Slide 159 text

Parsing CJS modules © 2022 Wantedly, Inc. app.cjs module.exports = 42; lib.cjs $ node app.cjs 42

Slide 160

Slide 160 text

Parsing ESM modules © 2022 Wantedly, Inc. import fs from "node:fs"; import vm from "node:vm"; const m = new vm.SourceTextModule( fs.readFileSync("lib.mjs", "utf-8")); await m.link(() => {}); await m.evaluate(); console.log(m.namespace.default); app.mjs

Slide 161

Slide 161 text

Parsing ESM modules © 2022 Wantedly, Inc. app.mjs export default 42; lib.mjs

Slide 162

Slide 162 text

Parsing ESM modules © 2022 Wantedly, Inc. app.mjs export default 42; lib.mjs $ node --experimental-vm-modules app.cjs 42 (node:10167) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time

Slide 163

Slide 163 text

Key takeaways © 2022 Wantedly, Inc.

Slide 164

Slide 164 text

Key takeaways ● There are dialects of ES Modules: ○ transpiled to CJS and then run ○ fed to Node.js as-is ○ fed to Webpack as autodected ESM ● Lots of things complicate interoperation between CJS and ESM ● But now your hands remember what is happening behind the scenes. Nothing to fear. © 2022 Wantedly, Inc.

Slide 165

Slide 165 text

Appendix A package.json © 2022 Wantedly, Inc.

Slide 166

Slide 166 text

package.json roles in Node.js © 2022 Wantedly, Inc. Fields Which package.json to use Module type detection type Nearest ancestor of the resolved module Package exports exports main `segment` or `@scope/segment` part of the request Folder main (only in CJS/require) main The requested directory Package imports imports Nearest ancestor of the requesting module Self name exports Nearest ancestor of the requesting module

Slide 167

Slide 167 text

Appendix B Module polyglot © 2022 Wantedly, Inc.

Slide 168

Slide 168 text

Recap Snippet to check module types © 2022 Wantedly, Inc. if (typeof await /0/["test"] === "function") { console.log("I'm ESM"); } else if (typeof require === "function") { console.log("I'm CJS"); } else { console.log("I'm other"); } module-type.js

Slide 169

Slide 169 text

How ESM check works © 2022 Wantedly, Inc. typeof await /0/["test"] === "function" typeof (await (/0/["test"])) === "function" ((typeof await) / 0) / ["test"] === "function" = RegExp.prototype.test no-op variable (nonexistent) = “undefined” cast to NaN ESM: [~Yield, +Await, ~Return] CJS: [~Yield, ~Await, +Return]

Slide 170

Slide 170 text

Advanced application (not recommended for production use!) Module polyglot © 2022 Wantedly, Inc. "use strict"; typeof await /[//]/; export default /* ]; module.exports = /**/ 42; module-polyglot.js