By Rahul — Google Frontend Engineer
Why Do We Have Multiple Module Systems?
JavaScript was born without a module system. For years, we used global variables and script tags. Then different communities created their own solutions. Understanding the history helps you understand why your bundler does what it does.
CommonJS (CJS)
Created for Node.js. Synchronous loading — works fine on a server where files are on the local disk.
Key Characteristics
- Synchronous:
require()blocks until the module is loaded - Dynamic: You can require inside if statements
- Copies values: When you import a primitive, you get a copy, not a live binding
- Runs on evaluation: The module code runs when first required, then result is cached
AMD (Asynchronous Module Definition)
Created for browsers where synchronous loading blocks rendering. RequireJS was the popular implementation.
AMD is mostly dead now. You will only see it in legacy projects. If you encounter it, your job is to migrate to ES modules.
ES Modules (ESM)
The official standard. Supported natively in all modern browsers and Node.js (since v12 with .mjs or "type": "module").
Key Differences from CommonJS
- Static structure: Imports must be at the top level. This enables tree-shaking
- Live bindings: If the exporting module changes a value, the importing module sees the change
- Asynchronous: Browser can fetch modules in parallel
- Strict mode by default
Live Bindings vs Value Copies
Real Production Issues
- Dual package hazard: A library ships both CJS and ESM. If your app loads both versions, singletons break. I saw this with a state management library where the CJS and ESM versions had separate state stores
- Default export confusion: CJS
module.exports = fnbecomes{ default: fn }when imported as ESM. This causesfn is not a functionerrors - .mjs vs .js: Node treats .js as CJS unless package.json has
"type": "module". Getting this wrong breaks everything silently
Best Practices
- Use ES modules for all new code
- If you publish a library, ship both CJS and ESM using the
exportsfield in package.json - Use dynamic
import()for code splitting, notrequire() - Set
"type": "module"in new Node.js projects
Summary
CommonJS was for Node.js, AMD was for browsers, ES Modules is the standard that replaced both. Use ESM. Understand CJS because you will encounter it in legacy code and Node.js libraries.