How can CommonJS code load an ES module?
Use dynamic import(), which works in CommonJS and returns a promise. Plain require of an ES module was not allowed historically; newer Node versions added limited support, so dynamic import is the safe choice.
Explain it simply
Think of an old railway line trying to connect to a new metro line. They run on different tracks, so you need a transfer point. In Node, that transfer point is import() written as a function call. It is allowed inside CommonJS files, and it returns a promise that resolves to the module's exports.
Static import ... from lines are not allowed in a CommonJS file, and ESM-only packages (some popular ones have gone ESM-only) will fail with ERR_REQUIRE_ESM if you try require. Recent Node releases have added the ability to require an ES module in some cases, particularly when it has no top-level await, but the behaviour depends on your Node version, so check before relying on it.
Example
// legacy.cjs
async function main() {
const { default: chalk } = await import("chalk");
console.log(chalk.green("Server started"));
}
main();The default export arrives as the default property of the module namespace object. Named exports arrive as properties with their own names.
The opposite direction is easy: an ES module can import a CommonJS file, and module.exports becomes the default import.
What to say in the interview
"CommonJS can use dynamic import(), which returns a promise. ESM can import CJS directly. Because import() is async, I either wrap it in an async function or migrate the project to ESM."
Common mistakes
- Forgetting
.defaultwhen reading a default export fromimport(). - Using the result before the promise has resolved.