SlideShare une entreprise Scribd logo
1  sur  103
Télécharger pour lire hors ligne
@NicoloRibaudo
Migrating Babel from
CommonJS to ESM
NICOLÒ RIBAUDO
@nicolo-ribaudo
@NicoloRibaudo hello@nicr.dev
@NicoloRibaudo 2
@NicoloRibaudo
Once upon a time …
3
@NicoloRibaudo
… there was a little JavaScript library, written using the shiny new
import / export syntax and published to npm compiled to CommonJS.
Once upon a time …
4
import { parse } from "babylon";
export function transform(code) {
let ast = parse(code);
return es6to5(ast);
}
"use strict";
exports.__esModule = true;
exports.transform = transform;
var _babylon = require("babylon");
function transform(code) {
let ast = _babylon.parse(code);
return es6to5(ast);
}
@NicoloRibaudo
Until one day, suddenly …
5
@NicoloRibaudo
Until one day, suddenly …
6
@NicoloRibaudo
Until one day, suddenly …
7
@NicoloRibaudo
Our adventure begins here.
8
@NicoloRibaudo
Chapter 1:
Introduction
9
@NicoloRibaudo
What is Babel?
10
@NicoloRibaudo
What is Babel?
11
Babel is a build-time
compiler
JavaScript
@NicoloRibaudo
What is Babel?
12
Babel is a build-time
compiler
JavaScript
@NicoloRibaudo
What is Babel?
13
Babel is a build-time
devtool
@NicoloRibaudo
What is Babel?
14
Babel is a build-time
devtool
configurable
@NicoloRibaudo 15
// babel.config.js
module.exports = function () {
return {
targets: [
"last 3 versions",
"not ie"
],
ignore: ["**/*.test.js"]
};
};
$ npx babel src --out-dir lib
"Generate code that is compatible
with these browsers"
"Don't compile test files"
@NicoloRibaudo
What is Babel?
16
Babel is a build-time
devtool
pluggable
@NicoloRibaudo 17
// babel-plugin-hello.js
const { types: t } = require("@babel/core");
module.exports = () => ({
visitor: {
StringLiteral(path) {
path.replaceWith(
t.stringLiteral("Hello World!")
);
},
},
});
$ npx babel src --out-dir lib
Apply some transformation
to the parsed code,
potentially relying on
Babel-provided AST utilities
@NicoloRibaudo
What is Babel?
18
Babel is a build-time
devtool
n embeddable
@NicoloRibaudo 19
// webpack.config.js
module.exports = {
entry: "./src/main.js",
output: "./out/bundle.js",
module: {
rules: [
{
test: /.js|.ts|.jsx/,
use: "babel-loader",
},
],
},
};
// rollup.config.js
import {
babel
} from "@rollup/plugin-babel";
export default {
input: "src/index.js",
output: {
dir: "output",
format: "es",
},
plugins: [ babel() ],
};
● Webpack
● Rollup
● Parcel
● Vite
● ESLint
● Prettier
● …
@NicoloRibaudo
What is Babel?
20
Babel is a build-time
library
JavaScript
@NicoloRibaudo 21
const babel = require("@babel/core");
const fs = require("fs/promises");
babel
.transformFileAsync("./main.js")
.then(({ code }) =>
fs.writeFile("./main.out.js", code)
).catch(err =>
console.error("Cannot compile!", err)
);
const parser = require("@babl/parser");
const generator = require("@babl/generator");
const ast = parser.parse("const a = 1;");
ast.program.body[0].declarations[0]
.id.name = "b";
const code = generator.default(ast);
console.log(code); // "const b = 1;"
@NicoloRibaudo
ESM challenges
22
@NicoloRibaudo
ESM challenges
23
#1 — It cannot be synchronously imported from CommonJS in Node.js
// SyntaxError in CommonJS
import "./module.mjs";
// Error [ERR_REQUIRE_ESM]: require() of ES Module not supported
require("./module.mjs");
// This works, but it's asynchronous
import("./module.mjs"); // Promise
@NicoloRibaudo
ESM challenges
24
#2 — It cannot be synchronously imported dynamically or lazily
// CommonJS
function loadIt() {
require("./module.cjs");
}
// ES Modules - SyntaxError // ES Modules - async
function loadIt() { async function loadIt() {
import "./module.cjs"; await import("./module.cjs");
} }
@NicoloRibaudo
ESM challenges
25
#3 — ESM-compiled-to-CJS has a different interface from native ESM
import obj from "./esm-compiled-to-cjs.js";
console.log(obj);
// { __esModule: true, default: [Function: A], namedExport: "foo" }
@NicoloRibaudo
ESM challenges
26
#4 — It doesn't integrate with tools that virtualize require and CJS loading
● mocking
● on-the-fly transpilation
● other bad things
🧙
@NicoloRibaudo
Internal vs External
code
27
@NicoloRibaudo
Internal vs External
28
Babel's source
● Written by Babel contributors
● Used by all Babel users
Babel's tests
● Written by Babel contributors
● Used by Babel contributors
Configuration files
● Written by (almost) all Babel users
Plugins
● Written by a limited number of
developers
@NicoloRibaudo
Internal vs External
29
@NicoloRibaudo
Internal vs External
30
@NicoloRibaudo
Chapter 2:
Making Babel
asynchronous
31
@NicoloRibaudo
The async/await virus
32
function transform(code, opts) {
const config = loadFullConfig(opts);
// ...
function loadFullConfig(inputOpts) {
const result = loadPrivatePartialConfig(inputOpts);
// ...
function loadPrivatePartialConfig(inputOpts) {
// ...
const configChain = buildRootChain(args, context);
// ...
function buildRootChain(opts, context) {
// ...
configFile = loadOneConfig(ROOT_FILENAMES, dirname);
// ...
function loadOneConfig(filenames, dirname) {
// ...
const config = readConfigCode(filepath);
// ...
function readConfigCode(filepath) {
// ...
options = loadCodeDefault(filepath);
// ...
function loadCodeDefault(filepath) {
const module = require(filepath);
// ...
@NicoloRibaudo
The async/await virus
33
function transform(code, opts) {
const config = loadFullConfig(opts);
// ...
function loadFullConfig(inputOpts) {
const result = loadPrivatePartialConfig(inputOpts);
// ...
function loadPrivatePartialConfig(inputOpts) {
// ...
const configChain = buildRootChain(args, context);
// ...
function buildRootChain(opts, context) {
// ...
configFile = loadOneConfig(ROOT_FILENAMES, dirname);
// ...
function loadOneConfig(filenames, dirname) {
// ...
const config = readConfigCode(filepath);
// ...
function readConfigCode(filepath) {
// ...
options = loadCodeDefault(filepath);
// ...
function loadCodeDefault(filepath) {
const module = require(filepath);
const module = await import(filepath);
// ...
@NicoloRibaudo
The async/await virus
34
function transform(code, opts) {
const config = loadFullConfig(opts);
// ...
function loadFullConfig(inputOpts) {
const result = loadPrivatePartialConfig(inputOpts);
// ...
function loadPrivatePartialConfig(inputOpts) {
// ...
const configChain = buildRootChain(args, context);
// ...
function buildRootChain(opts, context) {
// ...
configFile = loadOneConfig(ROOT_FILENAMES, dirname);
// ...
function loadOneConfig(filenames, dirname) {
// ...
const config = readConfigCode(filepath);
// ...
function readConfigCode(filepath) {
// ...
options = loadCodeDefault(filepath);
// ...
async function loadCodeDefault(filepath) {
const module = require(filepath);
const module = await import(filepath);
// ...
@NicoloRibaudo
The async/await virus
35
function transform(code, opts) {
const config = loadFullConfig(opts);
// ...
function loadFullConfig(inputOpts) {
const result = loadPrivatePartialConfig(inputOpts);
// ...
function loadPrivatePartialConfig(inputOpts) {
// ...
const configChain = buildRootChain(args, context);
// ...
function buildRootChain(opts, context) {
// ...
configFile = loadOneConfig(ROOT_FILENAMES, dirname);
// ...
function loadOneConfig(filenames, dirname) {
// ...
const config = readConfigCode(filepath);
// ...
function readConfigCode(filepath) {
// ...
options = await loadCodeDefault(filepath);
// ...
async function loadCodeDefault(filepath) {
const module = require(filepath);
const module = await import(filepath);
// ...
@NicoloRibaudo
The async/await virus
36
async function transform(code, opts) {
const config = await loadFullConfig(opts);
// ...
async function loadFullConfig(inputOpts) {
const result = await loadPrivatePartialConfig(inputOpts);
// ...
async function loadPrivatePartialConfig(inputOpts) {
// ...
const configChain = await buildRootChain(args, context);
// ...
async function buildRootChain(opts, context) {
// ...
configFile = await loadOneConfig(ROOT_FILENAMES, dirname);
// ...
async function loadOneConfig(filenames, dirname) {
// ...
const config = await readConfigCode(filepath);
// ...
async function readConfigCode(filepath) {
// ...
options = await loadCodeDefault(filepath);
// ...
async function loadCodeDefault(filepath) {
const module = require(filepath);
const module = await import(filepath);
// ...
@NicoloRibaudo
Preserving Babel's sync API
37
● Preserves backward compatibility
● The asynchronous API is only necessary when loading ESM files
function transform(code, opts) async function transformAsync(code, opts)
@NicoloRibaudo
Preserving Babel's sync API
38
function transform(code, opts)
function loadFullConfig(inputOpts) {
function loadPrivatePartialConfig(inputOpts) {
function buildRootChain(opts, context) {
function loadOneConfig(filenames, dirname) {
function readConfigCode(filepath) {
function loadCodeDefault(filepath) {
if (isCommonJS(filepath)) {
module = require(filepath);
} else {
throw new Error("Unsupported ESM config!");
}
async function transformAsync(code, opts)
async function loadFullConfig(inputOpts) {
async function loadPrivatePartialConfig(inputOpts)
async function buildRootChain(opts, context) {
async function loadOneConfig(filenames, dirname) {
async function readConfigCode(filepath) {
async function loadCodeDefault(filepath) {
if (isCommonJS(filepath)) {
module = require(filepath);
} else {
module = await import(filepath);
}
{
@NicoloRibaudo
Preserving Babel's sync API
39
Can we have a single implementation, capable of running both synchronously
and asynchronously?
@NicoloRibaudo
Preserving Babel's sync API
40
Can we have a single implementation, capable of running both synchronously
and asynchronously?
Callbacks?
function loadCodeDefault(filepath, callback) {
if (isCommonJS(filepath)) {
try { callback(null, require(filepath)); }
catch (err) { callback(err); }
} else {
import(filepath).then(
module => callback(null, module),
err => callback(err)
);
}
}
@NicoloRibaudo
gensync: abstracting the
41
gensync: abstracting the ???
Me trying to
prepare these
slides
Me spending too
much time looking
for a word
@NicoloRibaudo
gensync: abstracting the ???
42
https://github.com/loganfsmyth/gensync
@NicoloRibaudo
gensync: abstracting the ???
43
const readJSON = async function (filepath) {
const contents = await readFile(filepath, "utf8");
return JSON.parse(contents);
};
@NicoloRibaudo
gensync: abstracting the ???
44
const readJSON = async function (filepath) {
const contents = await readFile(filepath, "utf8");
return JSON.parse(contents);
};
const readJSON = gensync(function* (filepath) {
const contents = yield* readFile(filepath, "utf8");
return JSON.parse(contents);
});
@NicoloRibaudo
gensync: abstracting the ???
45
const readJSON = async function (filepath) {
const contents = await readFile(filepath, "utf8");
return JSON.parse(contents);
};
const readJSON = gensync(function* (filepath) {
const contents = yield* readFile(filepath, "utf8");
return JSON.parse(contents);
});
const json = readJSON.sync("package.json");
// or
const json = await readJSON.async("package.json");
@NicoloRibaudo
gensync: abstracting the ???
46
const readJSON = async function (filepath) {
const contents = await readFile(filepath, "utf8");
return JSON.parse(contents);
};
const readJSON = gensync(function* (filepath) {
const contents = yield* readFile(filepath, "utf8");
return JSON.parse(contents);
});
const json = readJSON.sync("package.json");
// or
const json = await readJSON.async("package.json");
const readFile = gensync({
sync: fs.readFileSync,
errback: fs.readFile,
});
@NicoloRibaudo
gensync: abstracting the ???
47
const readJSON = async function (filepath) {
const contents = await readFile(filepath, "utf8");
return JSON.parse(contents);
};
const readJSON = gensync(function* (filepath) {
const contents = yield* readFile(filepath, "utf8");
return JSON.parse(contents);
});
const json = readJSON.sync("package.json");
// or
const json = await readJSON.async("package.json");
const readFile = gensync({
sync: fs.readFileSync,
errback: fs.readFile,
});
You can define any utility that
branches on the execution model:
const isAsync = gensync({
sync: () => false,
async: async () => true,
});
@NicoloRibaudo 48
const transform = gensync(function* (code, opts) {
const config = yield* loadFullConfig(opts);
// ...
function* loadFullConfig(inputOpts) {
const result = yield* loadPrivatePartialConfig(inputOpts);
// ...
function* loadPrivatePartialConfig(opts) {
// ...
const configChain = yield* buildRootChain(args, context);
// ...
function* buildRootChain(opts, context) {
// ...
configFile = yield* loadOneConfig(ROOT_FILENAMES, dirname);
// ...
function* loadOneConfig(filenames, dirname) {
// ...
const config = yield* readConfigCode(filepath);
// ...
}
function* readConfigCode(filepath) {
// ...
options = yield* loadCodeDefault(filepath);
// ...
}
gensync: abstracting the ???
@NicoloRibaudo 49
const transform = gensync(function* (code, opts) {
const config = yield* loadFullConfig(opts);
// ...
export const transform = transform.errback;
export const transformSync = transform.sync;
export const transformAsync = transform.async;
gensync: abstracting the ???
@NicoloRibaudo 50
gensync: abstracting the ???
function loadCodeDefault(filepath) {
if (isCommonJS(filepath)) {
module = require(filepath);
} else {
throw new Error("Unsupported ESM!");
}
async function loadCodeDefault(filepath) {
if (isCommonJS(filepath)) {
module = require(filepath);
} else {
module = await import(filepath);
}
@NicoloRibaudo 51
gensync: abstracting the ???
function loadCodeDefault(filepath) {
if (isCommonJS(filepath)) {
module = require(filepath);
} else {
throw new Error("Unsupported ESM!");
}
async function loadCodeDefault(filepath) {
if (isCommonJS(filepath)) {
module = require(filepath);
} else {
module = await import(filepath);
}
function* loadCodeDefault(filepath) {
if (isCommonJS(filepath)) {
module = require(filepath);
} else if (yield* isAsync()) {
module = yield* wait(import(filepath));
} else {
throw new Error("Unsupported ESM!");
}
// ...
}
const wait = gensync({
sync: x => x,
async: x => x,
});
@NicoloRibaudo 52
gensync: abstracting the ???
@NicoloRibaudo
.mjs config files and plugins :)
53
@NicoloRibaudo
Chapter 3:
Making Babel
synchronous again
54
@NicoloRibaudo
Making Babel synchronous again
55
Sometimes you can force😇 asynchronous APIs on your
users — sometimes you can't.
@NicoloRibaudo
Making Babel synchronous again
56
Sometimes you can force😇 asynchronous APIs on your
users — sometimes you can't.
@babel/eslint-parser is an ESLint parser to support experimental syntax;
@babel/register hooks into Node.js' require() to compile files on-the-fly.
@NicoloRibaudo
Worker and Atomics to the rescue
57
@NicoloRibaudo
Worker and Atomics to the rescue
Main thread
function doSomethingSync() {
const signal = new Int32Array(new SharedArrayBuffer(4));
const { port1, port2 } = new MessageChannel();
// sleep
Atomics.wait(signal, 0, 0);
const { result } = receiveMessageOnPort(port2);
return result;
}
58
Worker thread
addListener("message", () => {
let result =
await doSomethingAsync();
port1.postMessage({ result });
Atomics.store(signal, 0, 1);
Atomics.notify(signal, 0);
});
signal, port1
{ payload: /* ... */ }
{ result: /* ... */ }
@NicoloRibaudo
Worker and Atomics to the rescue
59
More details:
https://giuseppegurgone.com/synchronizing-async-functions
@NicoloRibaudo
Worker and Atomics to the rescue
Main thread
function doSomethingSync() {
● Wait (sleep) until SAB's
contents change
● Read the received result
return result;
}
60
Worker thread
addListener("message", () => {
let result =
await doSomethingAsync();
● Change SAB's contents
● Wake up the main thread
});
SharedArrayBuffer[ 0x00 0x00 0x00 0x00 ]
{ payload: /* ... */ }
{ result: /* ... */ }
@NicoloRibaudo
Worker and Atomics to the rescue
Main thread
1. Create the worker
const { Worker, SHARE_ENV } = require("worker_threads");
const worker = new Worker("./path/to/worker.js", {
env: SHARE_ENV,
});
61
@NicoloRibaudo
Worker and Atomics to the rescue
Main thread, doSomethingSync
2. Delegate the task to the worker
const { MessageChannel } = require("worker_threads");
const signal = new Int32Array(new SharedArrayBuffer(4));
const { port1, port2 } = new MessageChannel();
worker.postMessage({ signal, port: port1, payload }, [port1]);
Atomics.wait(signal, 0, 0);
62
@NicoloRibaudo
Worker and Atomics to the rescue
Worker thread, "message" listener
3. Perform the task and send the result to the main thread
const result = await doSomethingAsync();
port.postMessage({ result });
port.close();
Atomics.store(signal, 0, 1);
Atomics.notify(signal, 0);
63
@NicoloRibaudo
Worker and Atomics to the rescue
Main thread, doSomethingSync
4. After waking up, read the result and return it
const { receiveMessageOnPort } = require("worker_threads");
... const { port1, port2 } = new MessageChannel(); ...
... Atomics.wait(signal, 0, 0); ...
const { result } = receiveMessageOnPort(port2);
return result;
64
@NicoloRibaudo
Production code? Error handling
65
In case of an error, we
must manually report it
to the main thread.
@NicoloRibaudo
Chapter 4:
When reality hits hard
— running Babel's tests as native ESM
66
@NicoloRibaudo
Running Babel's tests as native ESM
Problem #1
Problem #2
67
ESM compiled to CommonJS behaves differently
from ESM that runs natively in Node.js
Our test runner, Jest, didn't properly
support running ESM
@NicoloRibaudo
The __esModule convention
68
// src/main.js
import circ from "./math.js";
circ(2); // 12.56
// src/math.js
export const PI = 3.14;
export default function (r) {
return 2 * PI * r;
}
// dist/main.js
var _math = require("./math.js");
_math.default(2); // 12.56
// dist/math.js
const PI = 3.14; exports.PI = PI;
function _default(r) {
return 2 * PI * r;
}
exports.default = _default;
@NicoloRibaudo
The __esModule convention
69
// src/main.js
import circ from "../libs/math.cjs";
circ(2); // 12.56
// src/math.js
export const PI = 3.14;
export default function (r) {
return 2 * PI * r;
}
// dist/main.js
var _math = require("../libs/math.cjs");
_math(2); // 12.56
// dist/math.js
const PI = 3.14; exports.PI = PI;
function _default(r) {
return 2 * PI * r;
}
exports.default = _default;
// libs/math.cjs
module.exports = function (r) {
return 2 * PI * r;
};
No more .default!
@NicoloRibaudo
The __esModule convention
70
// src/main.js
import circ from "../libs/math.cjs";
circ(2); // 12.56
// src/math.js
export const PI = 3.14;
export default function (r) {
return 2 * PI * r;
}
// dist/main.js
var _math = {
default: require("../libs/math.cjs"),
};
_math.default(2); // 12.56
// dist/math.js
const PI = 3.14; exports.PI = PI;
function _default(r) {
return 2 * PI * r;
}
exports.default = _default;
// libs/math.cjs
module.exports = function (r) {
return 2 * PI * r;
};
@NicoloRibaudo
The __esModule convention
71
// src/main.js
import circ from "./math.js";
circ(2); // 12.56
// dist/main.js
var _math = _interopRequireDefault(
require("./math.js")
);
_math.default(2); // 12.56
function _interopRequireDefault(obj) {
return obj is ESM compiled to CommonJS ? obj : { default: obj };
}
@NicoloRibaudo
The __esModule convention
72
// src/math.js
export const PI = 3.14;
export default function (r) {
return 2 * PI * r;
}
// dist/math.js
exports.__esModule = true;
const PI = 3.14; exports.PI = PI;
function _default(r) {
return 2 * PI * r;
}
exports.default = _default;
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
"This was ESM, compiled to CJS"
@NicoloRibaudo
The __esModule convention
73
@NicoloRibaudo
The __esModule convention
74
● Babel
● Traceur
● tsc (TypeScript)
● SWC
● …
● Webpack
● Rollup (@rollup/plugin-commonjs
)
● Parcel
● Vite
● ESBuild
● …
It's supported by every JavaScript transpiler and bundler:
@NicoloRibaudo
The __esModule convention
75
It's supported by every JavaScript transpiler and bundler.
It's not supported by Node.js:
// main.js
import circ from "./math.cjs";
console.log(circ);
// math.cjs
exports.__esModule = true;
const PI = 3.14; exports.PI = PI;
function _default(r) {
return 2 * PI * r;
}
exports.default = _default;
Always logs
{ __esModule: true,
PI: 3.14,
default: [function] }
@NicoloRibaudo
Converting some files to ESM
76
// math.js
export default function (r) {
return 2 * PI * r;
}
// test.js
import circ from "./math.js";
assert(circ(2) === 12.56);
// math.js
exports.__esModule = true;
exports.default = function (r) {
return 2 * PI * r; };
// test.js
var _math = _interopRequireDefault(
require("./math.js") );
assert(_math.default(2) === 12.56);
✅
✅
@NicoloRibaudo
// test.js
import circ from "./math.js";
assert(circ(2) === 12.56);
Converting some files to ESM
77
// math.js
exports.__esModule = true;
exports.default = function (r) {
return 2 * PI * r; }; // test.js
var _math = _interopRequireDefault(
require("./math.js") );
assert(_math.default(2) === 12.56);
❌
✅
@NicoloRibaudo
// test.js
import circ from "./math.js";
assert(circ.default(2) === 12.56);
Converting some files to ESM
78
// math.js
exports.__esModule = true;
exports.default = function (r) {
return 2 * PI * r; }; // test.js
var _math = _interopRequireDefault(
require("./math.js") );
assert(_math.default.default(2) === 12.56);
✅
❌
@NicoloRibaudo
// test.js
import circ from "./math.js";
assert(circ.default(2) === 12.56);
Converting some files to ESM
79
// math.js
exports.__esModule = true;
exports.default = function (r) {
return 2 * PI * r; }; // test.js
var _math = {
default: require("./math.js") };
assert(_math.default.default(2) === 12.56);
✅
✅
@NicoloRibaudo
importInterop: "node"
"This import should be compiled to match Node.js' behavior,
without checking the __esModule flag."
80
@NicoloRibaudo
importInterop: "node"
"This import should be compiled to match Node.js' behavior,
without checking the __esModule flag."
81
// test/index.js
// Standard __esModule interop
import helper from "./helper.js";
// importInterop: "node"
import dep from "dep";
["@babel/transform-modules-commonjs", {
importInterop(source) {
if (source.startsWith(".")) {
return "babel";
}
return "node";
}
}]
babel-core
|- test
| |- index.js
| - helper.js
- node_modules
- dep
- index.js
@NicoloRibaudo
importInterop: "node"
82
@NicoloRibaudo
Running Babel's tests as native ESM
Problem #1
Problem #2
83
ESM compiled to CommonJS behaves differently
from ESM that runs natively in Node.js
Our test runner, Jest, didn't properly
support running ESM
Solved ✓
@NicoloRibaudo
Jest support for native ESM
84
@NicoloRibaudo
Jest support for native ESM
Jest runs every test in a virtualized context, using Node.js' vm module, to:
● isolate every test file, so that failing or misbehaving tests don't affect
other tests
● abstract and control the linking process between modules, to intercept all
requires/imports and:
○ allow mocking dependencies
○ transpile modules on-the-fly
85
@NicoloRibaudo
Jest support for native ESM
Node.js support for ESM in virtual vm contexts is still… rough.
86
@NicoloRibaudo
jest-light-runner
Jest supports implementing custom test runners,
to define how to execute a test.
87
https://github.com/nicolo-ribaudo/jest-light-runner
@NicoloRibaudo
jest-light-runner
88
Blazing fast!
@NicoloRibaudo
Running Babel's tests as native ESM
Problem #1
Problem #2
89
ESM compiled to CommonJS behaves differently
from ESM that runs natively in Node.js
Our test runner, Jest, didn't properly
support running ESM
Solved ✓
Solved ✓
@NicoloRibaudo
Chapter 5:
Getting ready for an
ESM release
90
@NicoloRibaudo
Dual packages
91
Node.js supports packages with multiple implementations, that are
conditionally required/imported.
// package.json
{
"name": "your-package",
"exports": {
"import": "./esm-dist/index.mjs",
"require": "./cjs-dist/index.js"
}
}
@NicoloRibaudo
Dual packages
Converting to a "dual package" while preserving compatibility with all the
Node.js versions and various tools is incredibly complex.
92
@NicoloRibaudo
Without breaking changes
Dual packages
93
@NicoloRibaudo
Dual packages
Very high risk of breaking changes: the complete migration
is deferred to the next major release (Babel 8)
94
@NicoloRibaudo
Dual packages development
● During development, wether it's ESM or ESM-compiled-to-CJS should just
be a compilation detail
● Our codebase should always be valid in both modes
make use-cjs make use-esm
● We test both ESM and ESM-compiled-to-CJS on CI
● We are always ready to publish an ESM release, by simply flipping a flag
95
@NicoloRibaudo
Maximizing backwards compatibility
"ESM cannot be synchronously imported from CommonJS in Node.js"
~ me, many slides ago
96
const babel = require("@babel/core");
const fs = require("fs/promises");
babel.transformAsync(inputCode)
.then(({ code }) =>
fs.writeFile("./src/output.js", code)
);
const { types: t } = require("@babel/core");
module.exports = function myPlugin() {
return {
visitor: {
NumericLiteral(path) {
path.replaceWith(
t.stringLiteral("foo"),
);
} } };
};
How do we preserve compatibility with existing CommonJS Babel usages?
@NicoloRibaudo
Babel consumers
1. require() Babel
2. Call one of the Babel API entry points,
such as transformSync,
transformAsync, parseAsync, etc.
97
Babel plugins
1. Babel is loaded by someone else
2. Babel loads the plugin
3. The plugin require()s Babel and uses
its utilities
const {
types: t,
template,
} = require("@babel/core");
Maximizing backwards compatibility
@NicoloRibaudo
Instead of duplicating the implementation in CommonJS and ESM files,
CommonJS can act as a "proxy" over the ESM implementation.
There must still be an asynchronous step somewhere, but for libraries that
already offered an async API this should be good enough.
98
CommonJS proxies
@NicoloRibaudo
Babel consumers
1. require() Babel
2. Call one of the Babel API entry points,
such as transformSync,
transformAsync, parseAsync, etc.
99
CommonJS proxies
// @babel/core/index.mjs
export async function transformAsync() {
/* ... */
}
export function transformSync() {
/* ... */
}
// @babel/core/index.cjs
let babel;
exports.transformAsync = async function () {
babel ??= await import("@babel/core");
return babel.transformAsync();
};
exports.transformSync = function () {
if (!babel) throw new Error("Not loaded yet");
return babel.transformSync();
};
@NicoloRibaudo
Babel plugins
1. Babel is loaded by someone else
2. Babel loads the plugin
3. The plugin require()s Babel and uses
its utilities
const {
types: t,
template,
} = require("@babel/core");
100
CommonJS proxies
// @babel/core/index.mjs
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const cjsProxy = require("./index.cjs");
import * as thisFile from "./index.mjs";
cjsProxy.__initialize(thisFile);
// @babel/core/index.cjs
let babel;
exports.transformAsync = function () { /*..*/ };
exports.__initialize = function (b) {
babel = b;
exports.types = b.types;
exports.template = b.template;
/* ... */
};
@NicoloRibaudo
The end
101
@NicoloRibaudo
One more thing!
102
@NicoloRibaudo 103
@nicolo-ribaudo @liuxingbaoyu
@JLHwung
Babel's development is entirely funded by donations.
If you rely on Babel at work, talk to your company to get them to
sponsor the project!
One more thing!
https://opencollective.com/babel
Need help talking to your company? team@babeljs.io

Contenu connexe

Similaire à Migrating Babel from CommonJS to ESM

Construire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradleConstruire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradleThierry Wasylczenko
 
How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?reallavalamp
 
What's New in ES6 for Web Devs
What's New in ES6 for Web DevsWhat's New in ES6 for Web Devs
What's New in ES6 for Web DevsRami Sayar
 
Coroutines for Kotlin Multiplatform in Practise
Coroutines for Kotlin Multiplatform in PractiseCoroutines for Kotlin Multiplatform in Practise
Coroutines for Kotlin Multiplatform in PractiseChristian Melchior
 
Firefox OS learnings & visions, WebAPIs - budapest.mobile
Firefox OS learnings & visions, WebAPIs - budapest.mobileFirefox OS learnings & visions, WebAPIs - budapest.mobile
Firefox OS learnings & visions, WebAPIs - budapest.mobileRobert Nyman
 
Blocks & GCD
Blocks & GCDBlocks & GCD
Blocks & GCDrsebbe
 
ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]Guillermo Paz
 
05 pig user defined functions (udfs)
05 pig user defined functions (udfs)05 pig user defined functions (udfs)
05 pig user defined functions (udfs)Subhas Kumar Ghosh
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsIván López Martín
 
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6Dmitry Soshnikov
 
Serializing EMF models with Xtext
Serializing EMF models with XtextSerializing EMF models with Xtext
Serializing EMF models with Xtextmeysholdt
 
¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?jaespinmora
 
JavaScript - Agora nervoso
JavaScript - Agora nervosoJavaScript - Agora nervoso
JavaScript - Agora nervosoLuis Vendrame
 

Similaire à Migrating Babel from CommonJS to ESM (20)

JavaScript Core
JavaScript CoreJavaScript Core
JavaScript Core
 
Construire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradleConstruire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradle
 
How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?
 
Sprockets
SprocketsSprockets
Sprockets
 
What's New in ES6 for Web Devs
What's New in ES6 for Web DevsWhat's New in ES6 for Web Devs
What's New in ES6 for Web Devs
 
The Beauty of Java Script
The Beauty of Java ScriptThe Beauty of Java Script
The Beauty of Java Script
 
Coroutines for Kotlin Multiplatform in Practise
Coroutines for Kotlin Multiplatform in PractiseCoroutines for Kotlin Multiplatform in Practise
Coroutines for Kotlin Multiplatform in Practise
 
Firefox OS learnings & visions, WebAPIs - budapest.mobile
Firefox OS learnings & visions, WebAPIs - budapest.mobileFirefox OS learnings & visions, WebAPIs - budapest.mobile
Firefox OS learnings & visions, WebAPIs - budapest.mobile
 
Blocks & GCD
Blocks & GCDBlocks & GCD
Blocks & GCD
 
Node intro
Node introNode intro
Node intro
 
ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]
 
05 pig user defined functions (udfs)
05 pig user defined functions (udfs)05 pig user defined functions (udfs)
05 pig user defined functions (udfs)
 
Txjs
TxjsTxjs
Txjs
 
Day 1
Day 1Day 1
Day 1
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut Configurations
 
Future of NodeJS
Future of NodeJSFuture of NodeJS
Future of NodeJS
 
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
HelsinkiJS meet-up. Dmitry Soshnikov - ECMAScript 6
 
Serializing EMF models with Xtext
Serializing EMF models with XtextSerializing EMF models with Xtext
Serializing EMF models with Xtext
 
¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?
 
JavaScript - Agora nervoso
JavaScript - Agora nervosoJavaScript - Agora nervoso
JavaScript - Agora nervoso
 

Plus de Igalia

A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?Igalia
 
Building End-user Applications on Embedded Devices with WPE
Building End-user Applications on Embedded Devices with WPEBuilding End-user Applications on Embedded Devices with WPE
Building End-user Applications on Embedded Devices with WPEIgalia
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Automated Testing for Web-based Systems on Embedded Devices
Automated Testing for Web-based Systems on Embedded DevicesAutomated Testing for Web-based Systems on Embedded Devices
Automated Testing for Web-based Systems on Embedded DevicesIgalia
 
Embedding WPE WebKit - from Bring-up to Maintenance
Embedding WPE WebKit - from Bring-up to MaintenanceEmbedding WPE WebKit - from Bring-up to Maintenance
Embedding WPE WebKit - from Bring-up to MaintenanceIgalia
 
Optimizing Scheduler for Linux Gaming.pdf
Optimizing Scheduler for Linux Gaming.pdfOptimizing Scheduler for Linux Gaming.pdf
Optimizing Scheduler for Linux Gaming.pdfIgalia
 
Running JS via WASM faster with JIT
Running JS via WASM      faster with JITRunning JS via WASM      faster with JIT
Running JS via WASM faster with JITIgalia
 
To crash or not to crash: if you do, at least recover fast!
To crash or not to crash: if you do, at least recover fast!To crash or not to crash: if you do, at least recover fast!
To crash or not to crash: if you do, at least recover fast!Igalia
 
Implementing a Vulkan Video Encoder From Mesa to GStreamer
Implementing a Vulkan Video Encoder From Mesa to GStreamerImplementing a Vulkan Video Encoder From Mesa to GStreamer
Implementing a Vulkan Video Encoder From Mesa to GStreamerIgalia
 
8 Years of Open Drivers, including the State of Vulkan in Mesa
8 Years of Open Drivers, including the State of Vulkan in Mesa8 Years of Open Drivers, including the State of Vulkan in Mesa
8 Years of Open Drivers, including the State of Vulkan in MesaIgalia
 
Introducción a Mesa. Caso específico dos dispositivos Raspberry Pi por Igalia
Introducción a Mesa. Caso específico dos dispositivos Raspberry Pi por IgaliaIntroducción a Mesa. Caso específico dos dispositivos Raspberry Pi por Igalia
Introducción a Mesa. Caso específico dos dispositivos Raspberry Pi por IgaliaIgalia
 
2023 in Chimera Linux
2023 in Chimera                    Linux2023 in Chimera                    Linux
2023 in Chimera LinuxIgalia
 
Building a Linux distro with LLVM
Building a Linux distro        with LLVMBuilding a Linux distro        with LLVM
Building a Linux distro with LLVMIgalia
 
turnip: Update on Open Source Vulkan Driver for Adreno GPUs
turnip: Update on Open Source Vulkan Driver for Adreno GPUsturnip: Update on Open Source Vulkan Driver for Adreno GPUs
turnip: Update on Open Source Vulkan Driver for Adreno GPUsIgalia
 
Graphics stack updates for Raspberry Pi devices
Graphics stack updates for Raspberry Pi devicesGraphics stack updates for Raspberry Pi devices
Graphics stack updates for Raspberry Pi devicesIgalia
 
Delegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOS
Delegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOSDelegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOS
Delegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOSIgalia
 
MessageFormat: The future of i18n on the web
MessageFormat: The future of i18n on the webMessageFormat: The future of i18n on the web
MessageFormat: The future of i18n on the webIgalia
 
Replacing the geometry pipeline with mesh shaders
Replacing the geometry pipeline with mesh shadersReplacing the geometry pipeline with mesh shaders
Replacing the geometry pipeline with mesh shadersIgalia
 
I'm not an AMD expert, but...
I'm not an AMD expert, but...I'm not an AMD expert, but...
I'm not an AMD expert, but...Igalia
 
Status of Vulkan on Raspberry
Status of Vulkan on RaspberryStatus of Vulkan on Raspberry
Status of Vulkan on RaspberryIgalia
 

Plus de Igalia (20)

A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Building End-user Applications on Embedded Devices with WPE
Building End-user Applications on Embedded Devices with WPEBuilding End-user Applications on Embedded Devices with WPE
Building End-user Applications on Embedded Devices with WPE
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Automated Testing for Web-based Systems on Embedded Devices
Automated Testing for Web-based Systems on Embedded DevicesAutomated Testing for Web-based Systems on Embedded Devices
Automated Testing for Web-based Systems on Embedded Devices
 
Embedding WPE WebKit - from Bring-up to Maintenance
Embedding WPE WebKit - from Bring-up to MaintenanceEmbedding WPE WebKit - from Bring-up to Maintenance
Embedding WPE WebKit - from Bring-up to Maintenance
 
Optimizing Scheduler for Linux Gaming.pdf
Optimizing Scheduler for Linux Gaming.pdfOptimizing Scheduler for Linux Gaming.pdf
Optimizing Scheduler for Linux Gaming.pdf
 
Running JS via WASM faster with JIT
Running JS via WASM      faster with JITRunning JS via WASM      faster with JIT
Running JS via WASM faster with JIT
 
To crash or not to crash: if you do, at least recover fast!
To crash or not to crash: if you do, at least recover fast!To crash or not to crash: if you do, at least recover fast!
To crash or not to crash: if you do, at least recover fast!
 
Implementing a Vulkan Video Encoder From Mesa to GStreamer
Implementing a Vulkan Video Encoder From Mesa to GStreamerImplementing a Vulkan Video Encoder From Mesa to GStreamer
Implementing a Vulkan Video Encoder From Mesa to GStreamer
 
8 Years of Open Drivers, including the State of Vulkan in Mesa
8 Years of Open Drivers, including the State of Vulkan in Mesa8 Years of Open Drivers, including the State of Vulkan in Mesa
8 Years of Open Drivers, including the State of Vulkan in Mesa
 
Introducción a Mesa. Caso específico dos dispositivos Raspberry Pi por Igalia
Introducción a Mesa. Caso específico dos dispositivos Raspberry Pi por IgaliaIntroducción a Mesa. Caso específico dos dispositivos Raspberry Pi por Igalia
Introducción a Mesa. Caso específico dos dispositivos Raspberry Pi por Igalia
 
2023 in Chimera Linux
2023 in Chimera                    Linux2023 in Chimera                    Linux
2023 in Chimera Linux
 
Building a Linux distro with LLVM
Building a Linux distro        with LLVMBuilding a Linux distro        with LLVM
Building a Linux distro with LLVM
 
turnip: Update on Open Source Vulkan Driver for Adreno GPUs
turnip: Update on Open Source Vulkan Driver for Adreno GPUsturnip: Update on Open Source Vulkan Driver for Adreno GPUs
turnip: Update on Open Source Vulkan Driver for Adreno GPUs
 
Graphics stack updates for Raspberry Pi devices
Graphics stack updates for Raspberry Pi devicesGraphics stack updates for Raspberry Pi devices
Graphics stack updates for Raspberry Pi devices
 
Delegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOS
Delegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOSDelegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOS
Delegated Compositing - Utilizing Wayland Protocols for Chromium on ChromeOS
 
MessageFormat: The future of i18n on the web
MessageFormat: The future of i18n on the webMessageFormat: The future of i18n on the web
MessageFormat: The future of i18n on the web
 
Replacing the geometry pipeline with mesh shaders
Replacing the geometry pipeline with mesh shadersReplacing the geometry pipeline with mesh shaders
Replacing the geometry pipeline with mesh shaders
 
I'm not an AMD expert, but...
I'm not an AMD expert, but...I'm not an AMD expert, but...
I'm not an AMD expert, but...
 
Status of Vulkan on Raspberry
Status of Vulkan on RaspberryStatus of Vulkan on Raspberry
Status of Vulkan on Raspberry
 

Dernier

How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?Antenna Manufacturer Coco
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessPixlogix Infotech
 

Dernier (20)

How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 

Migrating Babel from CommonJS to ESM

  • 1. @NicoloRibaudo Migrating Babel from CommonJS to ESM NICOLÒ RIBAUDO @nicolo-ribaudo @NicoloRibaudo hello@nicr.dev
  • 4. @NicoloRibaudo … there was a little JavaScript library, written using the shiny new import / export syntax and published to npm compiled to CommonJS. Once upon a time … 4 import { parse } from "babylon"; export function transform(code) { let ast = parse(code); return es6to5(ast); } "use strict"; exports.__esModule = true; exports.transform = transform; var _babylon = require("babylon"); function transform(code) { let ast = _babylon.parse(code); return es6to5(ast); }
  • 11. @NicoloRibaudo What is Babel? 11 Babel is a build-time compiler JavaScript
  • 12. @NicoloRibaudo What is Babel? 12 Babel is a build-time compiler JavaScript
  • 13. @NicoloRibaudo What is Babel? 13 Babel is a build-time devtool
  • 14. @NicoloRibaudo What is Babel? 14 Babel is a build-time devtool configurable
  • 15. @NicoloRibaudo 15 // babel.config.js module.exports = function () { return { targets: [ "last 3 versions", "not ie" ], ignore: ["**/*.test.js"] }; }; $ npx babel src --out-dir lib "Generate code that is compatible with these browsers" "Don't compile test files"
  • 16. @NicoloRibaudo What is Babel? 16 Babel is a build-time devtool pluggable
  • 17. @NicoloRibaudo 17 // babel-plugin-hello.js const { types: t } = require("@babel/core"); module.exports = () => ({ visitor: { StringLiteral(path) { path.replaceWith( t.stringLiteral("Hello World!") ); }, }, }); $ npx babel src --out-dir lib Apply some transformation to the parsed code, potentially relying on Babel-provided AST utilities
  • 18. @NicoloRibaudo What is Babel? 18 Babel is a build-time devtool n embeddable
  • 19. @NicoloRibaudo 19 // webpack.config.js module.exports = { entry: "./src/main.js", output: "./out/bundle.js", module: { rules: [ { test: /.js|.ts|.jsx/, use: "babel-loader", }, ], }, }; // rollup.config.js import { babel } from "@rollup/plugin-babel"; export default { input: "src/index.js", output: { dir: "output", format: "es", }, plugins: [ babel() ], }; ● Webpack ● Rollup ● Parcel ● Vite ● ESLint ● Prettier ● …
  • 20. @NicoloRibaudo What is Babel? 20 Babel is a build-time library JavaScript
  • 21. @NicoloRibaudo 21 const babel = require("@babel/core"); const fs = require("fs/promises"); babel .transformFileAsync("./main.js") .then(({ code }) => fs.writeFile("./main.out.js", code) ).catch(err => console.error("Cannot compile!", err) ); const parser = require("@babl/parser"); const generator = require("@babl/generator"); const ast = parser.parse("const a = 1;"); ast.program.body[0].declarations[0] .id.name = "b"; const code = generator.default(ast); console.log(code); // "const b = 1;"
  • 23. @NicoloRibaudo ESM challenges 23 #1 — It cannot be synchronously imported from CommonJS in Node.js // SyntaxError in CommonJS import "./module.mjs"; // Error [ERR_REQUIRE_ESM]: require() of ES Module not supported require("./module.mjs"); // This works, but it's asynchronous import("./module.mjs"); // Promise
  • 24. @NicoloRibaudo ESM challenges 24 #2 — It cannot be synchronously imported dynamically or lazily // CommonJS function loadIt() { require("./module.cjs"); } // ES Modules - SyntaxError // ES Modules - async function loadIt() { async function loadIt() { import "./module.cjs"; await import("./module.cjs"); } }
  • 25. @NicoloRibaudo ESM challenges 25 #3 — ESM-compiled-to-CJS has a different interface from native ESM import obj from "./esm-compiled-to-cjs.js"; console.log(obj); // { __esModule: true, default: [Function: A], namedExport: "foo" }
  • 26. @NicoloRibaudo ESM challenges 26 #4 — It doesn't integrate with tools that virtualize require and CJS loading ● mocking ● on-the-fly transpilation ● other bad things 🧙
  • 28. @NicoloRibaudo Internal vs External 28 Babel's source ● Written by Babel contributors ● Used by all Babel users Babel's tests ● Written by Babel contributors ● Used by Babel contributors Configuration files ● Written by (almost) all Babel users Plugins ● Written by a limited number of developers
  • 32. @NicoloRibaudo The async/await virus 32 function transform(code, opts) { const config = loadFullConfig(opts); // ... function loadFullConfig(inputOpts) { const result = loadPrivatePartialConfig(inputOpts); // ... function loadPrivatePartialConfig(inputOpts) { // ... const configChain = buildRootChain(args, context); // ... function buildRootChain(opts, context) { // ... configFile = loadOneConfig(ROOT_FILENAMES, dirname); // ... function loadOneConfig(filenames, dirname) { // ... const config = readConfigCode(filepath); // ... function readConfigCode(filepath) { // ... options = loadCodeDefault(filepath); // ... function loadCodeDefault(filepath) { const module = require(filepath); // ...
  • 33. @NicoloRibaudo The async/await virus 33 function transform(code, opts) { const config = loadFullConfig(opts); // ... function loadFullConfig(inputOpts) { const result = loadPrivatePartialConfig(inputOpts); // ... function loadPrivatePartialConfig(inputOpts) { // ... const configChain = buildRootChain(args, context); // ... function buildRootChain(opts, context) { // ... configFile = loadOneConfig(ROOT_FILENAMES, dirname); // ... function loadOneConfig(filenames, dirname) { // ... const config = readConfigCode(filepath); // ... function readConfigCode(filepath) { // ... options = loadCodeDefault(filepath); // ... function loadCodeDefault(filepath) { const module = require(filepath); const module = await import(filepath); // ...
  • 34. @NicoloRibaudo The async/await virus 34 function transform(code, opts) { const config = loadFullConfig(opts); // ... function loadFullConfig(inputOpts) { const result = loadPrivatePartialConfig(inputOpts); // ... function loadPrivatePartialConfig(inputOpts) { // ... const configChain = buildRootChain(args, context); // ... function buildRootChain(opts, context) { // ... configFile = loadOneConfig(ROOT_FILENAMES, dirname); // ... function loadOneConfig(filenames, dirname) { // ... const config = readConfigCode(filepath); // ... function readConfigCode(filepath) { // ... options = loadCodeDefault(filepath); // ... async function loadCodeDefault(filepath) { const module = require(filepath); const module = await import(filepath); // ...
  • 35. @NicoloRibaudo The async/await virus 35 function transform(code, opts) { const config = loadFullConfig(opts); // ... function loadFullConfig(inputOpts) { const result = loadPrivatePartialConfig(inputOpts); // ... function loadPrivatePartialConfig(inputOpts) { // ... const configChain = buildRootChain(args, context); // ... function buildRootChain(opts, context) { // ... configFile = loadOneConfig(ROOT_FILENAMES, dirname); // ... function loadOneConfig(filenames, dirname) { // ... const config = readConfigCode(filepath); // ... function readConfigCode(filepath) { // ... options = await loadCodeDefault(filepath); // ... async function loadCodeDefault(filepath) { const module = require(filepath); const module = await import(filepath); // ...
  • 36. @NicoloRibaudo The async/await virus 36 async function transform(code, opts) { const config = await loadFullConfig(opts); // ... async function loadFullConfig(inputOpts) { const result = await loadPrivatePartialConfig(inputOpts); // ... async function loadPrivatePartialConfig(inputOpts) { // ... const configChain = await buildRootChain(args, context); // ... async function buildRootChain(opts, context) { // ... configFile = await loadOneConfig(ROOT_FILENAMES, dirname); // ... async function loadOneConfig(filenames, dirname) { // ... const config = await readConfigCode(filepath); // ... async function readConfigCode(filepath) { // ... options = await loadCodeDefault(filepath); // ... async function loadCodeDefault(filepath) { const module = require(filepath); const module = await import(filepath); // ...
  • 37. @NicoloRibaudo Preserving Babel's sync API 37 ● Preserves backward compatibility ● The asynchronous API is only necessary when loading ESM files function transform(code, opts) async function transformAsync(code, opts)
  • 38. @NicoloRibaudo Preserving Babel's sync API 38 function transform(code, opts) function loadFullConfig(inputOpts) { function loadPrivatePartialConfig(inputOpts) { function buildRootChain(opts, context) { function loadOneConfig(filenames, dirname) { function readConfigCode(filepath) { function loadCodeDefault(filepath) { if (isCommonJS(filepath)) { module = require(filepath); } else { throw new Error("Unsupported ESM config!"); } async function transformAsync(code, opts) async function loadFullConfig(inputOpts) { async function loadPrivatePartialConfig(inputOpts) async function buildRootChain(opts, context) { async function loadOneConfig(filenames, dirname) { async function readConfigCode(filepath) { async function loadCodeDefault(filepath) { if (isCommonJS(filepath)) { module = require(filepath); } else { module = await import(filepath); } {
  • 39. @NicoloRibaudo Preserving Babel's sync API 39 Can we have a single implementation, capable of running both synchronously and asynchronously?
  • 40. @NicoloRibaudo Preserving Babel's sync API 40 Can we have a single implementation, capable of running both synchronously and asynchronously? Callbacks? function loadCodeDefault(filepath, callback) { if (isCommonJS(filepath)) { try { callback(null, require(filepath)); } catch (err) { callback(err); } } else { import(filepath).then( module => callback(null, module), err => callback(err) ); } }
  • 41. @NicoloRibaudo gensync: abstracting the 41 gensync: abstracting the ??? Me trying to prepare these slides Me spending too much time looking for a word
  • 42. @NicoloRibaudo gensync: abstracting the ??? 42 https://github.com/loganfsmyth/gensync
  • 43. @NicoloRibaudo gensync: abstracting the ??? 43 const readJSON = async function (filepath) { const contents = await readFile(filepath, "utf8"); return JSON.parse(contents); };
  • 44. @NicoloRibaudo gensync: abstracting the ??? 44 const readJSON = async function (filepath) { const contents = await readFile(filepath, "utf8"); return JSON.parse(contents); }; const readJSON = gensync(function* (filepath) { const contents = yield* readFile(filepath, "utf8"); return JSON.parse(contents); });
  • 45. @NicoloRibaudo gensync: abstracting the ??? 45 const readJSON = async function (filepath) { const contents = await readFile(filepath, "utf8"); return JSON.parse(contents); }; const readJSON = gensync(function* (filepath) { const contents = yield* readFile(filepath, "utf8"); return JSON.parse(contents); }); const json = readJSON.sync("package.json"); // or const json = await readJSON.async("package.json");
  • 46. @NicoloRibaudo gensync: abstracting the ??? 46 const readJSON = async function (filepath) { const contents = await readFile(filepath, "utf8"); return JSON.parse(contents); }; const readJSON = gensync(function* (filepath) { const contents = yield* readFile(filepath, "utf8"); return JSON.parse(contents); }); const json = readJSON.sync("package.json"); // or const json = await readJSON.async("package.json"); const readFile = gensync({ sync: fs.readFileSync, errback: fs.readFile, });
  • 47. @NicoloRibaudo gensync: abstracting the ??? 47 const readJSON = async function (filepath) { const contents = await readFile(filepath, "utf8"); return JSON.parse(contents); }; const readJSON = gensync(function* (filepath) { const contents = yield* readFile(filepath, "utf8"); return JSON.parse(contents); }); const json = readJSON.sync("package.json"); // or const json = await readJSON.async("package.json"); const readFile = gensync({ sync: fs.readFileSync, errback: fs.readFile, }); You can define any utility that branches on the execution model: const isAsync = gensync({ sync: () => false, async: async () => true, });
  • 48. @NicoloRibaudo 48 const transform = gensync(function* (code, opts) { const config = yield* loadFullConfig(opts); // ... function* loadFullConfig(inputOpts) { const result = yield* loadPrivatePartialConfig(inputOpts); // ... function* loadPrivatePartialConfig(opts) { // ... const configChain = yield* buildRootChain(args, context); // ... function* buildRootChain(opts, context) { // ... configFile = yield* loadOneConfig(ROOT_FILENAMES, dirname); // ... function* loadOneConfig(filenames, dirname) { // ... const config = yield* readConfigCode(filepath); // ... } function* readConfigCode(filepath) { // ... options = yield* loadCodeDefault(filepath); // ... } gensync: abstracting the ???
  • 49. @NicoloRibaudo 49 const transform = gensync(function* (code, opts) { const config = yield* loadFullConfig(opts); // ... export const transform = transform.errback; export const transformSync = transform.sync; export const transformAsync = transform.async; gensync: abstracting the ???
  • 50. @NicoloRibaudo 50 gensync: abstracting the ??? function loadCodeDefault(filepath) { if (isCommonJS(filepath)) { module = require(filepath); } else { throw new Error("Unsupported ESM!"); } async function loadCodeDefault(filepath) { if (isCommonJS(filepath)) { module = require(filepath); } else { module = await import(filepath); }
  • 51. @NicoloRibaudo 51 gensync: abstracting the ??? function loadCodeDefault(filepath) { if (isCommonJS(filepath)) { module = require(filepath); } else { throw new Error("Unsupported ESM!"); } async function loadCodeDefault(filepath) { if (isCommonJS(filepath)) { module = require(filepath); } else { module = await import(filepath); } function* loadCodeDefault(filepath) { if (isCommonJS(filepath)) { module = require(filepath); } else if (yield* isAsync()) { module = yield* wait(import(filepath)); } else { throw new Error("Unsupported ESM!"); } // ... } const wait = gensync({ sync: x => x, async: x => x, });
  • 55. @NicoloRibaudo Making Babel synchronous again 55 Sometimes you can force😇 asynchronous APIs on your users — sometimes you can't.
  • 56. @NicoloRibaudo Making Babel synchronous again 56 Sometimes you can force😇 asynchronous APIs on your users — sometimes you can't. @babel/eslint-parser is an ESLint parser to support experimental syntax; @babel/register hooks into Node.js' require() to compile files on-the-fly.
  • 58. @NicoloRibaudo Worker and Atomics to the rescue Main thread function doSomethingSync() { const signal = new Int32Array(new SharedArrayBuffer(4)); const { port1, port2 } = new MessageChannel(); // sleep Atomics.wait(signal, 0, 0); const { result } = receiveMessageOnPort(port2); return result; } 58 Worker thread addListener("message", () => { let result = await doSomethingAsync(); port1.postMessage({ result }); Atomics.store(signal, 0, 1); Atomics.notify(signal, 0); }); signal, port1 { payload: /* ... */ } { result: /* ... */ }
  • 59. @NicoloRibaudo Worker and Atomics to the rescue 59 More details: https://giuseppegurgone.com/synchronizing-async-functions
  • 60. @NicoloRibaudo Worker and Atomics to the rescue Main thread function doSomethingSync() { ● Wait (sleep) until SAB's contents change ● Read the received result return result; } 60 Worker thread addListener("message", () => { let result = await doSomethingAsync(); ● Change SAB's contents ● Wake up the main thread }); SharedArrayBuffer[ 0x00 0x00 0x00 0x00 ] { payload: /* ... */ } { result: /* ... */ }
  • 61. @NicoloRibaudo Worker and Atomics to the rescue Main thread 1. Create the worker const { Worker, SHARE_ENV } = require("worker_threads"); const worker = new Worker("./path/to/worker.js", { env: SHARE_ENV, }); 61
  • 62. @NicoloRibaudo Worker and Atomics to the rescue Main thread, doSomethingSync 2. Delegate the task to the worker const { MessageChannel } = require("worker_threads"); const signal = new Int32Array(new SharedArrayBuffer(4)); const { port1, port2 } = new MessageChannel(); worker.postMessage({ signal, port: port1, payload }, [port1]); Atomics.wait(signal, 0, 0); 62
  • 63. @NicoloRibaudo Worker and Atomics to the rescue Worker thread, "message" listener 3. Perform the task and send the result to the main thread const result = await doSomethingAsync(); port.postMessage({ result }); port.close(); Atomics.store(signal, 0, 1); Atomics.notify(signal, 0); 63
  • 64. @NicoloRibaudo Worker and Atomics to the rescue Main thread, doSomethingSync 4. After waking up, read the result and return it const { receiveMessageOnPort } = require("worker_threads"); ... const { port1, port2 } = new MessageChannel(); ... ... Atomics.wait(signal, 0, 0); ... const { result } = receiveMessageOnPort(port2); return result; 64
  • 65. @NicoloRibaudo Production code? Error handling 65 In case of an error, we must manually report it to the main thread.
  • 66. @NicoloRibaudo Chapter 4: When reality hits hard — running Babel's tests as native ESM 66
  • 67. @NicoloRibaudo Running Babel's tests as native ESM Problem #1 Problem #2 67 ESM compiled to CommonJS behaves differently from ESM that runs natively in Node.js Our test runner, Jest, didn't properly support running ESM
  • 68. @NicoloRibaudo The __esModule convention 68 // src/main.js import circ from "./math.js"; circ(2); // 12.56 // src/math.js export const PI = 3.14; export default function (r) { return 2 * PI * r; } // dist/main.js var _math = require("./math.js"); _math.default(2); // 12.56 // dist/math.js const PI = 3.14; exports.PI = PI; function _default(r) { return 2 * PI * r; } exports.default = _default;
  • 69. @NicoloRibaudo The __esModule convention 69 // src/main.js import circ from "../libs/math.cjs"; circ(2); // 12.56 // src/math.js export const PI = 3.14; export default function (r) { return 2 * PI * r; } // dist/main.js var _math = require("../libs/math.cjs"); _math(2); // 12.56 // dist/math.js const PI = 3.14; exports.PI = PI; function _default(r) { return 2 * PI * r; } exports.default = _default; // libs/math.cjs module.exports = function (r) { return 2 * PI * r; }; No more .default!
  • 70. @NicoloRibaudo The __esModule convention 70 // src/main.js import circ from "../libs/math.cjs"; circ(2); // 12.56 // src/math.js export const PI = 3.14; export default function (r) { return 2 * PI * r; } // dist/main.js var _math = { default: require("../libs/math.cjs"), }; _math.default(2); // 12.56 // dist/math.js const PI = 3.14; exports.PI = PI; function _default(r) { return 2 * PI * r; } exports.default = _default; // libs/math.cjs module.exports = function (r) { return 2 * PI * r; };
  • 71. @NicoloRibaudo The __esModule convention 71 // src/main.js import circ from "./math.js"; circ(2); // 12.56 // dist/main.js var _math = _interopRequireDefault( require("./math.js") ); _math.default(2); // 12.56 function _interopRequireDefault(obj) { return obj is ESM compiled to CommonJS ? obj : { default: obj }; }
  • 72. @NicoloRibaudo The __esModule convention 72 // src/math.js export const PI = 3.14; export default function (r) { return 2 * PI * r; } // dist/math.js exports.__esModule = true; const PI = 3.14; exports.PI = PI; function _default(r) { return 2 * PI * r; } exports.default = _default; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } "This was ESM, compiled to CJS"
  • 74. @NicoloRibaudo The __esModule convention 74 ● Babel ● Traceur ● tsc (TypeScript) ● SWC ● … ● Webpack ● Rollup (@rollup/plugin-commonjs ) ● Parcel ● Vite ● ESBuild ● … It's supported by every JavaScript transpiler and bundler:
  • 75. @NicoloRibaudo The __esModule convention 75 It's supported by every JavaScript transpiler and bundler. It's not supported by Node.js: // main.js import circ from "./math.cjs"; console.log(circ); // math.cjs exports.__esModule = true; const PI = 3.14; exports.PI = PI; function _default(r) { return 2 * PI * r; } exports.default = _default; Always logs { __esModule: true, PI: 3.14, default: [function] }
  • 76. @NicoloRibaudo Converting some files to ESM 76 // math.js export default function (r) { return 2 * PI * r; } // test.js import circ from "./math.js"; assert(circ(2) === 12.56); // math.js exports.__esModule = true; exports.default = function (r) { return 2 * PI * r; }; // test.js var _math = _interopRequireDefault( require("./math.js") ); assert(_math.default(2) === 12.56); ✅ ✅
  • 77. @NicoloRibaudo // test.js import circ from "./math.js"; assert(circ(2) === 12.56); Converting some files to ESM 77 // math.js exports.__esModule = true; exports.default = function (r) { return 2 * PI * r; }; // test.js var _math = _interopRequireDefault( require("./math.js") ); assert(_math.default(2) === 12.56); ❌ ✅
  • 78. @NicoloRibaudo // test.js import circ from "./math.js"; assert(circ.default(2) === 12.56); Converting some files to ESM 78 // math.js exports.__esModule = true; exports.default = function (r) { return 2 * PI * r; }; // test.js var _math = _interopRequireDefault( require("./math.js") ); assert(_math.default.default(2) === 12.56); ✅ ❌
  • 79. @NicoloRibaudo // test.js import circ from "./math.js"; assert(circ.default(2) === 12.56); Converting some files to ESM 79 // math.js exports.__esModule = true; exports.default = function (r) { return 2 * PI * r; }; // test.js var _math = { default: require("./math.js") }; assert(_math.default.default(2) === 12.56); ✅ ✅
  • 80. @NicoloRibaudo importInterop: "node" "This import should be compiled to match Node.js' behavior, without checking the __esModule flag." 80
  • 81. @NicoloRibaudo importInterop: "node" "This import should be compiled to match Node.js' behavior, without checking the __esModule flag." 81 // test/index.js // Standard __esModule interop import helper from "./helper.js"; // importInterop: "node" import dep from "dep"; ["@babel/transform-modules-commonjs", { importInterop(source) { if (source.startsWith(".")) { return "babel"; } return "node"; } }] babel-core |- test | |- index.js | - helper.js - node_modules - dep - index.js
  • 83. @NicoloRibaudo Running Babel's tests as native ESM Problem #1 Problem #2 83 ESM compiled to CommonJS behaves differently from ESM that runs natively in Node.js Our test runner, Jest, didn't properly support running ESM Solved ✓
  • 85. @NicoloRibaudo Jest support for native ESM Jest runs every test in a virtualized context, using Node.js' vm module, to: ● isolate every test file, so that failing or misbehaving tests don't affect other tests ● abstract and control the linking process between modules, to intercept all requires/imports and: ○ allow mocking dependencies ○ transpile modules on-the-fly 85
  • 86. @NicoloRibaudo Jest support for native ESM Node.js support for ESM in virtual vm contexts is still… rough. 86
  • 87. @NicoloRibaudo jest-light-runner Jest supports implementing custom test runners, to define how to execute a test. 87 https://github.com/nicolo-ribaudo/jest-light-runner
  • 89. @NicoloRibaudo Running Babel's tests as native ESM Problem #1 Problem #2 89 ESM compiled to CommonJS behaves differently from ESM that runs natively in Node.js Our test runner, Jest, didn't properly support running ESM Solved ✓ Solved ✓
  • 91. @NicoloRibaudo Dual packages 91 Node.js supports packages with multiple implementations, that are conditionally required/imported. // package.json { "name": "your-package", "exports": { "import": "./esm-dist/index.mjs", "require": "./cjs-dist/index.js" } }
  • 92. @NicoloRibaudo Dual packages Converting to a "dual package" while preserving compatibility with all the Node.js versions and various tools is incredibly complex. 92
  • 94. @NicoloRibaudo Dual packages Very high risk of breaking changes: the complete migration is deferred to the next major release (Babel 8) 94
  • 95. @NicoloRibaudo Dual packages development ● During development, wether it's ESM or ESM-compiled-to-CJS should just be a compilation detail ● Our codebase should always be valid in both modes make use-cjs make use-esm ● We test both ESM and ESM-compiled-to-CJS on CI ● We are always ready to publish an ESM release, by simply flipping a flag 95
  • 96. @NicoloRibaudo Maximizing backwards compatibility "ESM cannot be synchronously imported from CommonJS in Node.js" ~ me, many slides ago 96 const babel = require("@babel/core"); const fs = require("fs/promises"); babel.transformAsync(inputCode) .then(({ code }) => fs.writeFile("./src/output.js", code) ); const { types: t } = require("@babel/core"); module.exports = function myPlugin() { return { visitor: { NumericLiteral(path) { path.replaceWith( t.stringLiteral("foo"), ); } } }; }; How do we preserve compatibility with existing CommonJS Babel usages?
  • 97. @NicoloRibaudo Babel consumers 1. require() Babel 2. Call one of the Babel API entry points, such as transformSync, transformAsync, parseAsync, etc. 97 Babel plugins 1. Babel is loaded by someone else 2. Babel loads the plugin 3. The plugin require()s Babel and uses its utilities const { types: t, template, } = require("@babel/core"); Maximizing backwards compatibility
  • 98. @NicoloRibaudo Instead of duplicating the implementation in CommonJS and ESM files, CommonJS can act as a "proxy" over the ESM implementation. There must still be an asynchronous step somewhere, but for libraries that already offered an async API this should be good enough. 98 CommonJS proxies
  • 99. @NicoloRibaudo Babel consumers 1. require() Babel 2. Call one of the Babel API entry points, such as transformSync, transformAsync, parseAsync, etc. 99 CommonJS proxies // @babel/core/index.mjs export async function transformAsync() { /* ... */ } export function transformSync() { /* ... */ } // @babel/core/index.cjs let babel; exports.transformAsync = async function () { babel ??= await import("@babel/core"); return babel.transformAsync(); }; exports.transformSync = function () { if (!babel) throw new Error("Not loaded yet"); return babel.transformSync(); };
  • 100. @NicoloRibaudo Babel plugins 1. Babel is loaded by someone else 2. Babel loads the plugin 3. The plugin require()s Babel and uses its utilities const { types: t, template, } = require("@babel/core"); 100 CommonJS proxies // @babel/core/index.mjs import { createRequire } from "module"; const require = createRequire(import.meta.url); const cjsProxy = require("./index.cjs"); import * as thisFile from "./index.mjs"; cjsProxy.__initialize(thisFile); // @babel/core/index.cjs let babel; exports.transformAsync = function () { /*..*/ }; exports.__initialize = function (b) { babel = b; exports.types = b.types; exports.template = b.template; /* ... */ };
  • 103. @NicoloRibaudo 103 @nicolo-ribaudo @liuxingbaoyu @JLHwung Babel's development is entirely funded by donations. If you rely on Babel at work, talk to your company to get them to sponsor the project! One more thing! https://opencollective.com/babel Need help talking to your company? team@babeljs.io