Skip to content

ESM vs CommonJS

ES Modules (ESM) is JavaScript’s official module system. CommonJS is the module system used by Node.js before ESM. Understanding the differences is important for working with both environments.

FeatureESMCommonJS
Syntaximport / exportrequire() / module.exports
LoadingStatic (parse time)Dynamic (runtime)
ScopeModule scopeModule scope
Strict modeAlwaysOptional
this at top levelundefinedexports object
Top-level await✅ Yes❌ No
File extensionsRequired (.js, .mjs)Optional
// ESM — export
export const greet = () => 'Hello';
export default class User {}
// CommonJS — export
module.exports.greet = () => 'Hello';
module.exports = class User {};
// ESM — import
import User, { greet } from './user.js';
// CommonJS — import
const User = require('./user');
const { greet } = require('./user');
// ESM — static (analyzed at parse time)
import { something } from './module.js'; // must be at top level
// CommonJS — dynamic (can be conditional)
if (condition) {
const module = require('./module.js'); // ✅ works
}
// ESM — dynamic import for runtime loading
if (condition) {
const module = await import('./module.js'); // ✅
}

CommonJS handles circular dependencies by returning the partially built module.exports. ESM handles them better due to live bindings:

// ESM: imports are live read-only bindings to the exporting module
// Even with circular deps, you always get the latest value
// CommonJS: imports are copies at require time
// Circular deps can get undefined if accessed too early
// ESM — must include extension (in browsers, Node.js)
import { greet } from './utils.js'; // ✅
import { greet } from './utils'; // ❌ in browsers
// CommonJS — extension optional
const { greet } = require('./utils'); // ✅
const { greet } = require('./utils.js'); // ✅
// Package.json allows mixing with "type" field
{
"type": "module", // .js files treated as ESM
"exports": {
"import": "./src/index.js", // ESM consumers
"require": "./dist/index.js" // CommonJS consumers
}
}
AspectESMCommonJS
StandardOfficial JavaScript specNode.js convention
Syntaximport / exportrequire / module.exports
LoadingStatic (faster)Dynamic (flexible)
Top-level await✅❌
Browser support✅ Native❌ (needs bundler)
File extensionRequiredOptional