Explorer
JavaScript

Modules: CommonJS vs ES Modules

JavaScript Theory & Concepts

Modules: CommonJS vs ES Modules

Module formats, static analysis, tree-shaking, and live bindings.

📖 The Story & Real-World Analogy

The Courier Messenger vs The City Architect Blueprint

"• **CommonJS (CJS: `require` / `module.exports`)**: Like a motorcycle courier in city traffic. When line 10 runs, your program stops and waits for the courier to deliver a physical cardboard package. Inside is a snapshot copy of whatever was packed at that moment. • **ES Modules (ESM: `import` / `export`)**: Like an architect's master blueprint drawn before construction starts. Before running a single line of code, the system examines all imports statically. It prunes unused rooms (Tree Shaking) and connects every wire with a live electrical link (Live Bindings) rather than a dead snapshot!"

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.

⚙️ How It Works Under The Hood (Step-by-Step)
1

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.

2

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.

3

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.

4

Top-Level Await

ESM modules support `await` at the top level without needing to wrap in an async IIFE function.

5

Dynamic import()

ESM provides `import("./module.js")` returning a Promise, enabling on-demand lazy code splitting.

💻 Interactive Code Walkthrough

Comparing CJS and ESM syntax and understanding live bindings:

JAVASCRIPT
// --- 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
Console Output:
CODE
Initial count: 1
Updated count: 2
⚠️ Common Pitfalls & Interview Traps
Trap
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.

⚡ 30-Second Quick Revision Cheat Sheet (TL;DR)
  • ✓ 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`.

Finished this lesson?

Mark this chapter complete to update your learning streak and unlock the next lesson.