Chapter 18 โ Modules: ESM vs CommonJS
๐ Definition
A module is a file with its own scope that explicitly exports values and imports others. JavaScript has two module systems:
- CommonJS (CJS) โ
require()/module.exports. Node's original system. - ES Modules (ESM) โ
import/export. The language standard, used by browsers, bundlers and modern Node.
๐ Key Differences
| CommonJS | ES Modules | |
|---|---|---|
| Syntax | const x = require("x"), module.exports = โฆ |
import x from "x", export default โฆ |
| Loading | Synchronous, at runtime | Parsed statically, loaded asynchronously |
| When resolved | When the require line runs |
Before any code runs (static graph) |
| Bindings | A copy of module.exports at require time |
Live bindings (read-only view of the exporter's variable) |
| Conditional import | if (x) require("a") works |
Use dynamic await import("a") |
Top-level await |
โ | โ |
this at top level |
module.exports |
undefined |
__dirname, __filename |
โ | โ โ use import.meta.dirname / import.meta.url |
| Tree shaking | Hard | โ (static structure) |
| File extension in Node | .js (default) or .cjs |
.mjs, or .js with "type": "module" |
๐ป Code Example โ Same Module, Both Ways
// math.cjs โ CommonJS
function add(a, b) { return a + b; }
module.exports = { add };
// app.cjs
const { add } = require("./math.cjs");// math.mjs โ ESM
export function add(a, b) { return a + b; }
export default function multiply(a, b) { return a * b; }
// app.mjs
import multiply, { add } from "./math.mjs"; // extension is required in Node ESM
import * as math from "./math.mjs";๐ป Code Example โ Live Bindings vs Copies
// counter.mjs
export let count = 0;
export function inc() { count++; }
// main.mjs
import { count, inc } from "./counter.mjs";
inc();
console.log(count); // 1 โ live binding// counter.cjs
let count = 0;
module.exports = { count, inc: () => count++ };
// main.cjs
const { count, inc } = require("./counter.cjs");
inc();
console.log(count); // 0 โ a copy of the value at require time๐ป Code Example โ Dynamic Import and Code Splitting
// Load a heavy library only when needed (works in CJS and ESM)
button.addEventListener("click", async () => {
const { default: Chart } = await import("chart.js/auto");
new Chart(ctx, config);
});// React: each lazy() creates a separate bundle chunk
const AdminPage = React.lazy(() => import("./pages/AdminPage"));๐ป Code Example โ ESM Equivalents of __dirname in Node
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Node 20.11+: import.meta.dirname and import.meta.filename๐ป Code Example โ Interop
// ESM importing a CJS package: default import gets module.exports
import express from "express";
// CJS requiring an ESM package: Node 22+ supports require(esm) for sync ESM graphs;
// older versions need dynamic import:
const { nanoid } = await import("nanoid");๐ Circular Dependencies
Both systems allow cycles, but behave differently:
- CJS returns a partially filled
module.exportsโ you getundefinedfor things not yet exported. - ESM hoists bindings; accessing a
let/constexport before it is initialised throws aReferenceError(TDZ), while function declarations work.
Fix cycles by extracting the shared code into a third module.
๐ Module Caching
Both systems evaluate a module once and cache it. Every later import returns the same instance โ which is why a module-level new PrismaClient() or mongoose.connect() acts as a singleton.
๐ Real-World Use
- New Node projects:
"type": "module"inpackage.jsonand ESM everywhere. - Libraries often ship both (
"exports": { "import": "./index.mjs", "require": "./index.cjs" }). - Bundlers (Vite, webpack) rely on ESM's static structure for tree shaking.
๐ฏ Likely Interview Questions
- Difference between CommonJS and ES Modules?
- What are live bindings?
- Why does tree shaking work better with ESM?
- How do you use
__dirnamein an ES module? - What is dynamic
import()and where do you use it? - What happens with circular dependencies in CJS vs ESM?
- Why does requiring the same module twice not run it twice?
โ Proxy, Reflect & Symbols | Index | Next: Functional Patterns & Immutability โ