- Rewrites CommonJS modules to ESM when
target: 'module'withtransformSyntaxenabled. - Assumes Node 22.21+ runtime with native ESM.
- Skips lowering when
moduleorexportsare shadowed at module scope to avoid mis-compilation. - Throws when encountering
withstatements or unshadowedevalto avoid unsound rewrites. - Deprecated CJS features (
require.extensions,module.parent, legacy folder-as-module resolution) are left as-is.
- Top-level static
require()(andmodule.require()) calls become hoisted ESM imports:const foo = require('./x')→import * as foo from './x'const foo = module.require('./x')→import * as foo from './x'- Multi-declarator:
const a = require('./x'), { foo } = require('./y')→ hoisted namespace imports plus destructuring from those namespaces. require('./x')→import './x'
- Dynamic/templated/non-literal
require()calls (or any require that isn't hoistable) stay as-is and trigger acreateRequireshim:import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);- Nested or block-scoped
require()calls also stay undercreateRequireand execute at runtime.
require.mainmaps toimport.meta.main; comparisons againstmoduleare simplified toimport.meta.main/!import.meta.main.require.resolveis rewritten toimport.meta.resolvevia the member-expression formatter.require.cachebecomes an empty object placeholder;require.extensionsis not rewritten (deprecated).
exports/module.exportsassignments are rewritten to an internal__exportsobject.- Export metadata is collected to emit real ESM exports at the end of the file:
- Default export synthesized when
module.exports = ...is found. - Named exports emitted for
exports.foo = ...,Object.assign(exports, {...}),Object.defineProperty, etc.
- Default export synthesized when
- Shadowed
module/exportsbindings cause the transform to throw to avoid emitting invalid exports. - Exports inside simple control flow are collected and re-exported; hoisting preserves the original runtime writes on
__exports.
__filename/__dirnamebecomeimport.meta.url/import.meta.dirnamein ESM output.import.meta.filenameseeding ensuresimport.metais present even if unused in source.- Live-binding semantics for exports are preserved to the extent they exist in the source; assignments remain on
__exportsand are re-exported by reference. - Conditional or runtime-dependent
require()calls are not hoisted; they continue to execute at runtime undercreateRequire.