JavaScript Modules: ESM vs CommonJS Explained

JavaScript has two competing module systems in active use today — CommonJS (Node’s original) and ES Modules (the language’s official standard) — and knowing which is which matters for debugging import errors.

// CommonJS (Node's traditional default, .js with "type": "commonjs")
const fs = require('fs');
module.exports = { readConfig };

function readConfig() { /* ... */ }
// ES Modules (the language standard, .mjs or "type": "module" in package.json)
import fs from 'fs';
export function readConfig() { /* ... */ }
export default readConfig;

Key Practical Differences

  • ESM imports are statically analyzable (enables tree-shaking in bundlers); CommonJS require() is dynamic and can’t be analyzed the same way
  • ESM runs in strict mode by default; CommonJS does not
  • Top-level await works in ESM, not in CommonJS
  • Browsers natively support ESM (<script type="module">); they’ve never supported CommonJS directly

Node.js now fully supports both, but mixing them in the same project (importing a CommonJS package from ESM, or vice versa) still occasionally causes friction — worth checking a package’s actual module type before assuming it “just works” either way.