mediumFull Stack EngineerTechnology
How do ES Modules differ from CommonJS in Node.js, and what are the migration challenges?
Posted 18/04/2026
by Mehedy Hasan Ador
Question Details
At a Node.js-heavy company:
"We're migrating our codebase from CommonJS to ES Modules. Some packages only ship CJS, some only ESM. Our Jest tests broke after switching. What are the key differences and how do we handle interoperability?"
Suggested Solution
Key Differences
| Feature | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| Syntax | require() / module.exports | import / export |
| Loading | Synchronous | Asynchronous |
| Tree-shaking | ❌ Not possible | ✅ Static analysis |
| Top-level await | ❌ No | ✅ Yes |
| Execution | Runs at require time | Parsed, then executed |
| Mutability | Exports are copies | Exports are live bindings |
this at top | module.exports | undefined |
Interoperability
// CJS importing ESM (dynamic import only)
const { default: esmModule } = await import("./esm-module.mjs");
// ESM importing CJS (named imports may not work)
import cjsModule from "./cjs-module.cjs"; // ✅ default import works
import { specific } from "./cjs-module.cjs"; // ⚠️ May fail
Migration Challenges
__dirnameand__filenamenot available in ESM:
// ESM replacement
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
- JSON imports require assertion:
import pkg from "./package.json" with { type: "json" };
- Jest ESM support: Use
--experimental-vm-modulesor switch to Vitest - Package.json:
"type": "module"makes all.jsfiles ESM by default
Best Practice for New Projects
// package.json
{ "type": "module" }
Use .mts/.mtsextensions for explicit ESM,.cts` for CJS.