Day 6 · Concept 16
ES modules vs CommonJS
ES modules (import / export) are the standard. CommonJS (require / module.exports) is legacy Node. Both still in widespread use; modern code uses ESM.
1 · ES modules — the modern way
// math.js
export function add(a, b) { return a + b; }
export const PI = 3.14159;
// Default export — one per file
export default function multiply(a, b) { return a * b; }// main.js
import multiply, { add, PI } from "./math.js";
console.log(add(2, 3));
console.log(PI);
console.log(multiply(4, 5));
// Rename
import { add as plus } from "./math.js";
// Import everything as a namespace
import * as math from "./math.js";
console.log(math.add(1, 2));2 · CommonJS — older Node
// math.cjs (or default in older Node)
function add(a, b) { return a + b; }
module.exports = { add };
// Or single export
module.exports = function multiply(a, b) { return a * b; };const { add } = require("./math.cjs");
const multiply = require("./multiply.cjs");
console.log(add(2, 3));
console.log(multiply(4, 5));3 · ESM vs CJS — the practical differences
| ESM | CommonJS |
|---|---|
| Static — imports resolved at parse time | Dynamic — require runs anywhere |
| Async by design | Synchronous |
Top-level await works | Doesn't work |
| Tree-shakable — bundlers can drop unused exports | Hard to tree-shake |
Requires .mjs or "type": "module" in package.json | Default in older Node |
__dirname / __filename not built-in | Built-in |
4 · package.json — the switch
{
"name": "myapp",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"exports": {
".": "./index.js",
"./utils": "./src/utils.js"
}
}"type": "module" makes .js files ESM by default. Without it, they're CJS.
"exports" defines the public entry points consumers can import.
5 · Dynamic import — anywhere
async function maybeLoad() {
if (process.env.DEBUG) {
const { logger } = await import("./debug-logger.js");
logger.start();
}
}Works in both ESM and CJS. Returns a promise that resolves to the module. Useful for code-splitting and conditional loads.
6 · Common mistakes
- Mixing ESM and CJS without thought. Importing CJS from ESM works; the reverse is awkward.
- Forgetting the
.jsextension in ESM imports.import "./util"fails;import "./util.js"works. - Top-level await in CJS. Doesn't work. Wrap in an IIFE.
7 · When it clicks
- You start new projects as ESM by default.
- You translate
requiretoimportautomatically. - You use
"exports"in libraries to control the public surface.
Found this useful?