JavaScript Module Systems: CJS vs ESM vs UMD
Learn the interview-ready mental model, practical trade-offs, and production patterns for this web fundamentals topic.
Topic content
JavaScript has evolved through multiple module systems. CommonJS (CJS) uses synchronous require() and is runtime-based. ES Modules (ESM) use static import/export syntax, enabling tree-shaking and better optimization. UMD is a legacy wrapper that works across environments. Understanding static vs runtime resolution is the key differentiator for performance and tooling.
CJS containers are opened and inspected only when the ship arrives at runtime (dynamic). ESM containers are scanned and optimized at the port (build time) with clear labels, allowing removal of unused cargo (tree-shaking). UMD is an old multi-format shipping crate that works everywhere but is bulky.
1CommonJS (CJS)
Legacy Node.js module system. Uses synchronous require(). Modules are loaded and executed at runtime. Dynamic imports are possible but prevents many build-time optimizations.
const add = (a, b) => a + b;
module.exports = { add };2ES Modules (ESM)
Modern standard. Uses static import/export. Enables tree-shaking, scope hoisting, and top-level await. Native support in modern browsers and Node (with type: module).
export const add = (a, b) => a + b;
export default { add };3UMD (Universal Module Definition)
Legacy pattern designed to work in browser, Node, and AMD environments. Usually generated by bundlers for library distribution. Larger output size and rarely used in modern apps.
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.myLib = factory();
}
}(typeof self !== 'undefined' ? self : this, function() {
return { /* lib */ };
}));4Bundler Role & Interop
Bundlers (Webpack, Vite, Rollup) resolve the module graph at build time. ESM enables powerful optimizations. Interop between CJS and ESM can be tricky — default exports from CJS become .default in ESM.
- ✓ESM is the modern standard — use it by default
- ✓CJS is runtime-based and blocks tree-shaking
- ✓Static analysis in ESM enables powerful bundler optimizations
- ✓CJS ↔ ESM interop requires care (especially default exports)
- ✓UMD is mostly legacy for CDN library distribution