Modules
CommonJS (Node legacy) 🟡, ES Modules (ESM) 🟢, Dynamic import, ESM vs CommonJS 🎯
CommonJS (Node legacy) 🟡#
// export
const add = (a, b) => a + b;
module.exports = { add };
// import
const { add } = require("./math");
Synchronous, dynamic, runtime resolution — the historic Node system.
ES Modules (ESM) 🟢#
// named exports
export const PI = 3.14159;
export function area(r) { return PI * r * r; }
// default export
export default class Circle {}
// re-export
export { thing } from "./other.js";
// imports
import Circle, { PI, area } from "./circle.js";
import * as circle from "./circle.js";
import "./side-effect.js"; // run for effects only
- Static (imports are hoisted, resolved before execution) → enables tree shaking (bundlers drop unused exports).
- Always strict mode; top-level
thisisundefined. - Enable in Node via
"type":"module"in package.json or.mjs; in browsers via<script type="module">.
Dynamic import#
const { heavy } = await import("./heavy.js"); // load on demand → code splitting
button.addEventListener("click", async () => {
const mod = await import("./chart.js"); // lazy-load a feature
mod.render();
});
Returns a promise; enables lazy loading, conditional loading, and route-based code splitting.
ESM vs CommonJS 🎯#
| CommonJS | ESM | |
|---|---|---|
| Syntax | require/module.exports |
import/export |
| Loading | synchronous, runtime | static, hoisted |
| Tree-shakeable | no | yes |
Top-level await |
no | yes |
| Live bindings | copies values | live references |
Interview Q&A#
Q1. ESM vs CommonJS — three differences.
ESM uses import/export with static, hoisted resolution; CommonJS uses require/module.exports resolved synchronously at runtime. ESM is tree-shakeable and supports top-level await, which CommonJS is not/does not. ESM imports are live read-only bindings to the exporter's values, while CommonJS require returns a copy of the exports at import time.
Q2. What is tree shaking and why does ESM enable it?
Tree shaking is a bundler eliminating unused exports from the final bundle. ESM's imports/exports are static and analyzable at build time (no conditional require), so a bundler can prove which exports are never used and safely drop them.
Q3. When would you use dynamic import()?
When you want to load a module on demand rather than upfront — lazy-loading a heavy feature on user interaction, conditional loading, or route-based code splitting. It returns a promise, so you await it, and it works in both ESM and CommonJS contexts.