Why modules exist
Modules let files expose some values and hide the rest, replacing fragile global variables with explicit imports and exports. Two systems dominate JavaScript today.
CommonJS
CommonJS, used widely in older Node, loads modules synchronously with require and assigns to a module exports object. Because require runs at call time, it is dynamic, so you can require conditionally inside a function.
ES Modules
ES Modules are the standard, using import and export. They are static, meaning the imports are known before the code runs. This enables tooling to do tree shaking, dropping unused exports from the final bundle.
Live bindings versus values
- An ESM import is a live binding to the exported variable, so it reflects later updates in the source module.
- A CommonJS require copies the value present at require time, so later changes in the source are not seen.
Resolution and interop
ESM loads asynchronously and supports top level await, while CommonJS does not. Mixing them needs care because a default export and module exports do not map cleanly, so bundlers and Node apply interop rules to bridge the gap.
Key idea
CommonJS loads dynamically and copies values; ES Modules are static live bindings that enable tree shaking and top level await.