Back to Tsx

CJS/ESM interop

notes/node/cjs-esm-interop.md

4.23.63.3 KB
Original Source

CJS/ESM interop

How Node bridges CommonJS and ES modules.

CJS preparse and synthetic namespaces

When ESM imports CJS, Node synthesizes an ESM namespace from static CJS source analysis before evaluating the CJS module.

The lexer implementation changed without changing the supported grammar:

ESM importing CJS

Node behaviorPRVerified releases
The ESM load hook can return source for format === 'commonjs'.nodejs/node#50825v20.11.0, v21.3.0
CJS source returned by a load hook can be preparsed into a namespace.nodejs/node#50825, #54769[20.11.0, 21.0.0) and >=21.3.0

The load hook receives import-attributes context and reads CJS source at the boundary (v20.11.0 load.js#L113-L145). The translator then preparses CJS before namespace creation and evaluates via CJSModule._load (v20.11.0 translators.js#L190-L203).

CJS requiring ESM

Node behaviorPRVerified releases or window
CJS require() can load eligible ESM instead of throwing ERR_REQUIRE_ESM.nodejs/node#55085v20.19.0, v22.12.0, v23.0.0
Normal require(esm) stops printing the experimental warning.nodejs/node#56194v20.19.0, v22.13.0, v23.5.0
Extensionless .mjs lookup was broken.nodejs/node#55085, #55590[20.19.0, 20.19.5), [22.12.0, 22.14.0)
Synthetic CJS namespaces expose 'module.exports'.nodejs/node#53848v23.0.0