Modules: CommonJS vs ES Modules
Modules: CommonJS vs ES Modules
Module formats, static analysis, tree-shaking, and live bindings.
The Courier Messenger vs The City Architect Blueprint
CommonJS was designed for server-side Node.js in 2009. ECMAScript Modules (ESM) was introduced in ES2015 as the official standard for both browsers and modern servers.
Static Analysis vs Dynamic Loading
ESM `import` statements must appear at the top level and are resolved at compile time. CJS `require()` is a regular runtime function that can be placed inside `if` statements or loops.
Tree Shaking Optimization
Because ESM imports and exports are static, bundlers (Vite, Rollup, Webpack) can safely eliminate unused code from client bundles, dramatically reducing bundle size.
Live Bindings vs Value Copies
ESM exports are live references. If an exporting module updates a variable internally, importers see the updated value. CJS exports a copied snapshot.
Top-Level Await
ESM modules support `await` at the top level without needing to wrap in an async IIFE function.
Dynamic import()
ESM provides `import("./module.js")` returning a Promise, enabling on-demand lazy code splitting.
Comparing CJS and ESM syntax and understanding live bindings:
// --- CommonJS (CJS) Syntax ---
// math.js
// const add = (a, b) => a + b;
// module.exports = { add };
// app.js
// const { add } = require("./math");
// --- ES Modules (ESM) Syntax ---
// counter.mjs
export let count = 1;
export function increment() {
count++;
}
// app.mjs
import { count, increment } from "./counter.mjs";
console.log("Initial count:", count); // 1
increment();
// In ESM, 'count' is a live binding; it automatically updates!
console.log("Updated count:", count); // 2
Initial count: 1
Updated count: 2
Importing ESM into CJS Synchronously
The Risk: `const mod = require("esm-package")` throws `ERR_REQUIRE_ESM` in modern Node.js because ESM cannot be loaded synchronously by CJS.
The Fix: Convert your project to ESM (`"type": "module"` in `package.json`), or use dynamic `await import("esm-package")` inside CJS.
- ✓ ESM (`import/export`): Static, asynchronous, live bindings, supports tree-shaking.
- ✓ CJS (`require/module.exports`): Dynamic, synchronous, exports value copies.
- ✓ ESM allows Top-Level Await natively.
- ✓ Use dynamic `import()` for route-based lazy loading in frontends.
- ✓ Modern Node.js enables ESM via `"type": "module"` in `package.json`.