CommonJS vs ES Modules — what are the differences and how do you use each?
3 minintermediatenodejscommonjsesmmodulesimportrequire
Quick Answer
CommonJS (`require`/`module.exports`) is Node's original, synchronous module system that loads modules at runtime. ES Modules (`import`/`export`) are the standardized system: statically analyzable, asynchronous, with live bindings and top-level await. You opt into ESM with `"type": "module"` or an `.mjs` extension.
Detailed Answer
Answer:
CommonJS (CJS) — Node's original module system:
// math.js
function add(a, b) { return a + b; }
module.exports = { add };
// app.js
const { add } = require('./math');
- Synchronous:
requireloads and executes the module immediately. - Dynamic: you can
requireconditionally, inside functions, with computed paths. - Exports are a copy of the reference at export time.
ES Modules (ESM) — the JavaScript standard:
// math.mjs
export function add(a, b) { return a + b; }
// app.mjs
import { add } from './math.mjs';
- Static: imports/exports are resolved before execution, enabling tree-shaking and better tooling.
- Asynchronous loading; supports top-level
await. - Exports are live bindings — importers see updated values if the exporting module changes them.
- Import paths generally need the file extension.
How Node picks the system:
.cjs→ always CommonJS;.mjs→ always ESM..js→ depends on the nearestpackage.json:"type": "module"means ESM,"type": "commonjs"(or absent) means CJS.
Interop:
- ESM can
importCommonJS modules (themodule.exportsbecomes the default export). - CommonJS cannot
requirean ESM module synchronously; use dynamicawait import(). __dirname/__filenamedon't exist in ESM — useimport.meta.url.
Interview tip: The core distinction is static + async (ESM) vs dynamic + synchronous (CJS). Static structure is what enables tree-shaking.