What do you think of CommonJS vs ESM?
TL;DR
ECMAScript modules (ESM) are the JavaScript standard and the best default for new code intended for browsers, Node.js, or bundlers. Their static import / export structure provides live bindings and enables ahead-of-time dependency analysis; dynamic import() and top-level await cover asynchronous loading needs. CommonJS (CJS) is Node.js's older require() / module.exports system and remains important in existing packages and applications.
Interop is runtime- and version-specific. In current Node.js, ESM can import CommonJS, while require() can load only eligible synchronous ESM graphs without top-level await. Define the package format explicitly with file extensions, package.json type, and exports, then test the packed artifact. AMD is now mainly historical or a legacy-maintenance concern.
Overview
As JavaScript applications grow, organizing code into separate files becomes important. Module systems help manage this complexity by allowing code to be split into reusable pieces (modules) with clear dependencies. Over time, different module systems emerged before an official standard was adopted.
CommonJS
Characteristics
- Designed for synchronous loading of modules
- Primarily used in server-side environments like Node.js
- Uses
module.exportsandrequirefor defining and loading modules
Example
// Defining a module in CommonJSconst dep1 = require('dependency1');const dep2 = require('dependency2');module.exports = {// Module code here};// Loading a module in CommonJSconst mod1 = require('module1');const mod2 = require('module2');// Code that uses mod1 and mod2
ESM (ECMAScript Modules)
Characteristics
- Official JavaScript standard module system
- Supported natively in modern browsers and Node.js
- Designed for both asynchronous and synchronous scenarios
- Uses
exportandimportfor defining and loading modules
Example
import dep1 from 'dependency1'; // if dependency1 had a default exportimport { dep2 } from 'dependency2'; // named import to import something specific from dependency2// Module code using dep1, dep2...export function someFunction() {// ... function logic ...}export const someValue = 'hello';// Or export multiple things at once// export { someFunction, someValue };// Or export a default value// export default class MyClass {// ...// }
Key differences
Loading mechanism
- CommonJS: Synchronous (blocks until loaded).
- ESM: Statically linked; browsers fetch module graphs asynchronously, and modules can use top-level
await.
Environment suitability
- CommonJS: Node.js legacy standard.
- ESM: Official standard (Browser & Node.js).
Syntax
- CommonJS:
require()/module.exports. - ESM:
import/export.
Analysis
- CommonJS: Dynamic (runtime analysis).
- ESM: Static (compile-time analysis, enables tree-shaking).
Use cases
CommonJS
- Older Node.js projects or where sync loading is needed.
- Existing CommonJS packages and applications.
- Files or package scopes explicitly marked as CommonJS. Modern Node.js also performs syntax detection for some ambiguous input, so packages should set
typerather than depend on ambiguity.
ESM
- Modern web development (native browser support).
- New Node.js projects, especially those needing async features or optimizations.
- Code intended for both browser and server.
Practical interoperability notes
- Relative ESM specifiers in browsers and Node.js use URL-style resolution and normally need explicit file extensions.
- Importing a CommonJS module from ESM reliably exposes
module.exportsas the default export; named exports depend on Node.js static analysis and are not always available. - Current Node.js can synchronously
require()an eligible ESM graph only when it has no top-levelawait. Useimport()when the graph may be asynchronous or when supporting older Node.js versions. - Publishing separate ESM and CommonJS builds can create a “dual package” hazard where the same library is instantiated twice. Prefer one format when consumers allow it, or design and test conditional exports carefully.
- ESM syntax enables tree-shaking analysis but does not guarantee unused code will be removed; side effects and bundler configuration still matter.
Further reading
- MDN Web Docs on JavaScript modules
- Node.js: CommonJS modules
- Node.js: ESM
- RequireJS (AMD Loader - Historical)