Node.js, part 22: require(esm) — the dual-package problem finally closes
Part 22from the Node.js series · 24 parts in all
Part 5 left an asymmetry standing: a module could import CommonJS, but
CommonJS could not require an ES module. Every library author paid for it,
usually by shipping two builds, importing two packages, or refusing to migrate. Node 22 made
require(esm) work. Part 22 is what that fixes and the one case that still
fails.
Requiring a module, at last
// lib.mjs (an ES module, maybe in a dependency you do not control)
export function parse(text) { /* ... */ }
export const VERSION = '2.1.0';
// consumer.cjs
const { parse, VERSION } = require('./lib.mjs'); // Node 22+, previously ERR_REQUIRE_ESM
It works without a flag in Node 22.12 and later, and it is the feature that lets a large CommonJS codebase adopt far more of the ecosystem without a top-to-bottom migration.
The one restriction: top-level await
Requiring a module whose initialisation is asynchronous cannot work synchronously — there is
no value to return yet. So a module with a top-level await (part 12) still refuses
to be required, and the error is explicit about why. The fix is
await import('./lib.mjs'), which is asynchronous by construction and therefore
always legal:
// Only option when the module awaits during initialisation.
const { parse } = await import('./lib.mjs');
Why this mattered so much more than it looks
- One build instead of two. Shipping
dist/index.cjsanddist/index.mjsmeans one process can load both copies, and theninstanceofchecks across the boundary fail and singleton state silently doubles. - No more
createRequiregymnastics in a CommonJS file that needed one modern dependency. - Migration stops being all-or-nothing. A codebase can move one folder at a time, which is the only way large migrations actually finish.
The residual caveat is the load graph, not the API: if the same package is pulled in as both
CJS and ESM you can still get two instances, so a stateful library is better published as one
format with a default export that both worlds can consume. Next: everything else
in Node 22.