CommonJS vs ES Modules (require vs import)
CommonJS vs ES Modules (require vs import)
Level 10 — Ecosystem & Tooling Node's legacy module system vs the ES standard.
1. Prerequisites
- Modules (import/export) — The ES standard for modular code sharing.
- Node.js — The runtime host environment.
2. Term Category
Ecosystem / Tooling (Universal: Supported in modern Node.js and browser bundlers.): CommonJS vs ES Modules (require vs import) is a fundamental concept in this technology stack. Level 10 — Ecosystem & Tooling
3. Explanation
(1) Design Motivation — "Why did we design this?"
JavaScript originally lacked a built-in module system. When Node.js was created in 2009 to run JS on servers, it designed a custom module solution called CommonJS (CJS).
Years later, in 2015, the TC39 committee standardized a native, built-in module format for browsers called ES Modules (ESM).
Today, Node.js supports both, creating a transition period where developers must understand the technical differences between them:
| Feature | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| Syntax | require() / module.exports | import / export |
| Resolution | Dynamic & Synchronous: Resolved at runtime. You can place require() inside functions or if statements. | Static & Asynchronous: Resolved before execution. Static imports must remain at the file top-level, enabling bundler optimizations like Tree Shaking. |
| Globals | Has access to __dirname and __filename. | Lacks __dirname and __filename. (Uses import.meta.url instead). |
| Default in Node | Default format. | Must be activated via "type": "module" in package.json or .mjs extensions. |
(2) Reality Metaphor
- CommonJS is like ordering food delivery dynamically. You sit in your room and call the driver. You can place the order inside the kitchen, in the backyard, or decide to order only if you feel hungry (
ifblock require). The delivery arrives synchronously while you wait. - ES Modules is like a pre-flight checked baggage system at an airport. Before you are allowed to board the plane (static compilation phase), the airline inspects all bags, checks tickets, and optimizes cargo layout (tree shaking). You cannot add new bags after the plane takes off.
(3) JavaScript Code Examples
Syntactic Comparison
1. CommonJS style (math.cjs)
// Exporting:
const PI = 3.14;
const add = (a, b) => a + b;
module.exports = { PI, add };
// Importing:
const { PI, add } = require("./math.cjs");
console.log(add(2, 2)); // 4
2. ES Modules style (math.mjs)
// Exporting:
export const PI = 3.14;
export const add = (a, b) => a + b;
// Importing:
import { PI, add } from "./math.mjs";
console.log(add(2, 2)); // 4
Calculating __dirname inside ES Modules
ES Modules do not possess the __dirname global variable. To calculate the absolute path of the current directory in ESM, you must process import.meta.url:
import { fileURLToPath } from "url";
import { dirname } from "path";
// import.meta.url yields: file:///home/user/project/app.js
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log("Current Directory:", __dirname);
4. Common Mistakes & Pitfalls
Mistake 1: Using require() inside ES Modules
The mistake: Running require() inside a file configured as an ES module (e.g. when "type": "module" is set).
Why it's wrong: The ESM execution parser does not define or support require, throwing a ReferenceError instantly. To load a CommonJS file or JSON payload inside ESM, use static import or dynamic import().
Incorrect:
// Inside ESM:
const data = require("./data.json"); // ReferenceError: require is not defined
Fix:
// Option A: Use standard import
import data from "./data.json" assert { type: "json" };
// Option B: createRequire wrapper
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const data = require("./data.json"); // Safe!
Mistake 2: Losing Context Binding (this) in Commonjs Vs Esm Callbacks
The mistake: Passing methods from Commonjs Vs Esm instances as standalone callbacks to timers or event listeners without explicitly binding this.
Why it's wrong: Extracting object methods disassociates them from their target parent instance, causing this to resolve to undefined (in strict mode) or window/globalThis at runtime.
Incorrect:
const obj = {
name: "commonjs_vs_esm",
log() { console.log(this.name); }
};
setTimeout(obj.log, 100); // ❌ Output: undefined (loses object context)
Fix:
const obj = {
name: "commonjs_vs_esm",
log() { console.log(this.name); }
};
setTimeout(() => obj.log(), 100); // Correct: Arrow function captures lexical context
Mistake 3: Unhandled Asynchronous Failures in Commonjs Vs Esm Operations
The mistake: Executing asynchronous operations within Commonjs Vs Esm without wrapping await calls in try...catch blocks or chaining .catch().
Why it's wrong: Unhandled promise rejections trigger UnhandledPromiseRejectionWarning in Node.js or unhandled rejection errors in modern browsers, leaving application state in corrupted or uncoordinated states.
Incorrect:
async function processData() {
const res = await fetch("/api/commonjs_vs_esm"); // ❌ Unhandled network failure crashes execution flow
const data = await res.json();
return data;
}
Fix:
async function processData() {
try {
const res = await fetch("/api/commonjs_vs_esm");
if (!res.ok) throw new Error(`HTTP Error: ${res.status}`);
return await res.json();
} catch (err) {
console.error(`Caught error in commonjs_vs_esm: ${err.message}`);
return null;
}
}
5. Practice Exercises
Exercise 1: Dual Package Module Exporter Implementation
Scenario: A modern JavaScript build and tooling architecture implements dual package module exporter to manage application code lifecycle.
Requirements:
- Write processCommonjsVsEsmPrimary(payload).
- Validate input config/options.
- Execute tool/runtime operation.
- Return result object.
Answer
Implementation
function processCommonjsVsEsmPrimary(payload) {
if (!payload || typeof payload !== "object") return null;
return {
status: "SUCCESS",
target: "commonjs_vs_esm",
data: payload
};
}
// Verification tests
const res = processCommonjsVsEsmPrimary({ name: "app" });
console.assert(res.status === "SUCCESS", "Test 1 Failed");
console.assert(res.target === "commonjs_vs_esm", "Test 2 Failed");
Technical Explanation
- Dual Package Module Exporter Fundamentals: Understanding dual package module exporter is essential for modern frontend/backend tooling infrastructure.
- Build & Runtime Boundary: Distinguishes between static compilation time and dynamic runtime execution phases.
- Tooling Integration: Seamlessly integrates with bundlers, transpilers, and package managers.
Exercise 2: Dynamic Async ESM Import Handler Handler
Scenario: An enterprise toolchain handles dynamic async esm import handler using defensive fallback options and specification compliance.
Requirements:
- Write handleCommonjsVsEsmSecondary(target, options).
- Check target validity.
- Apply configuration options.
- Return status boolean.
Answer
Implementation
function handleCommonjsVsEsmSecondary(target, options) {
if (!target || typeof target !== "object") return false;
const opts = options || {};
target.enabled = opts.enabled !== undefined ? opts.enabled : true;
return true;
}
// Verification tests
const mockObj = {};
console.assert(handleCommonjsVsEsmSecondary(mockObj, { enabled: true }) === true, "Test 1 Failed");
console.assert(mockObj.enabled === true, "Test 2 Failed");
Technical Explanation
- Dynamic Async ESM Import Handler Architecture: Applying dynamic async esm import handler provides robust toolchain component abstractions.
- Defensive Option Validation: Guards against missing configuration parameters in build scripts.
- Specification Standard Compliance: Adheres to ECMA and module resolution specifications.
Exercise 3: Static Tree-Shakeable ESM vs CommonJS require Optimization
Scenario: A high-performance build pipeline optimizes static tree-shakeable esm vs commonjs require to accelerate compilation speed and reduce bundle size.
Requirements:
- Write optimizeCommonjsVsEsmTertiary(modules).
- Filter invalid module references.
- Return optimized modules list.
Answer
Implementation
function optimizeCommonjsVsEsmTertiary(modules) {
if (!Array.isArray(modules)) return [];
return modules.filter(m => m !== null && m !== undefined);
}
// Verification tests
const list = ["modA", null, "modB"];
const clean = optimizeCommonjsVsEsmTertiary(list);
console.assert(clean.join(",") === "modA,modB", "Test 1 Failed");
Technical Explanation
- Static Tree-Shakeable ESM vs CommonJS require Best Practices: Optimizing static tree-shakeable esm vs commonjs require reduces bundle memory footprint and speeds up builds.
- Dead Code & Resource Cleanup: Eliminates unused code paths and stale temporary build artifacts.
- Cross-Toolchain Compatibility: Operates reliably across Node, Webpack, Vite, and Rollup build tools.
6. Related Terms
- package.json — The manifest file where
"type": "module"is declared. - Bundler — Tooling that bridges CJS/ESM modules for web browser targets.
- Framework vs Library (React / Vue / Angular) — Related concept: Framework vs Library (React / Vue / Angular).
7. Key Takeaways
- CommonJS is Node's legacy module system (
require/module.exports); ES Modules is the standardized ES module format (import/export). - CommonJS resolves modules dynamically and synchronously at runtime.
- ES Modules resolves modules statically and asynchronously before runtime, enabling tree-shaking optimizations.
- ES Modules lacks native CJS globals like
__dirnameand__filename; calculate them usingimport.meta.url. - To enable ES Modules in Node.js, set
"type": "module"inpackage.jsonor use the.mjsfile extension.