Suggest an editImprove this articleRefine the answer for “ESM vs CommonJS”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)Conceptually, CommonJS is an imperative system where modules load at runtime, dynamically and synchronously; ESM is declarative, analyzing dependencies at parse time, loading asynchronously, and giving live bindings instead of copied values. **Key point:** ESM's static imports (known ahead of time, only at the top level of a file) are exactly what makes tree-shaking possible - CommonJS fundamentally can't do that, because `require()` is a plain function that can be called conditionally.Shown above the full answer for quick recall.Answer (EN)Image## 1. In short: the core idea | System | Core concept | |---|---| | **CommonJS (CJS)** | Modules load **at runtime**, synchronously, line by line. | | **ES Modules (ESM)** | Modules are analyzed **at parse time**, load **asynchronously**, with strict dependencies and "live" imports. | ## 2. The philosophy behind each ### CommonJS Built **for Node.js** (2009), to run JS on the server. - Everything is synchronous (since files sit on the local disk); - Every file is an isolated module; - Code runs top to bottom on the first `require()`; - Imports are **copies of values**, not "live bindings". Example: ```javascript // math.js module.exports = { x: 1 }; // app.js const math = require('./math'); console.log(math.x); // 1 math.x = 2; // changes only the local copy ``` ### ES Modules Built **for the JavaScript language** (ES6, 2015), so one standard could work in both browsers and Node.js. - Loading is **asynchronous**, declarative; - Imports are **static**, known before the code runs; - They can be optimized, enabling *tree-shaking*; - Imports are **live bindings**, not copies. Example: ```javascript // counter.js export let count = 0; export function inc() { count++; } // app.js import { count, inc } from './counter.js'; inc(); console.log(count); // 1 (the value updated) ``` ## 3. The conceptual differences | Concept | CommonJS | ES Modules | |---|---|---| | When it loads | At runtime | Before execution (parse time) | | Imports | Dynamic (`require()`) | Static (`import`) | | Binding type | Values are copied | Live bindings | | Execution order | Top to bottom | Determined by dependencies | | Asynchrony | Synchronous | Asynchronous | | Optimization | Not possible (it's runtime) | Tree-shaking is possible | | Context | `this` = `module.exports` | `this` = `undefined` | | Caching | After the first call | Yes, but with "live" values | | Ecosystem | Node.js, older packages | Modern JS, browsers, Node 13+ | | Architecture | Imperative | Declarative | | Compatibility | Server only | Universal (server + client) | ## 4. The difference at load and compile time ### CommonJS: - The `require()` line runs → the file is read → the code executes. - So the path can be a variable: ```javascript const lib = require(condition ? './a' : './b'); ``` This works, because it's a plain function call. ### ES Modules: - Imports must sit **at the top of the file** and **cannot be dynamic**: ```javascript import x from './a'; // fine if (cond) import x from './b'; // an error ``` - Node.js and the browser know the dependencies in advance, and can load them **in parallel** before running the code. ## 5. The difference in module structure Node.js "wraps" a CommonJS module in an internal function: ```javascript (function (exports, require, module, __filename, __dirname) { // your code }); ``` So you get: - `require` - `module.exports` - `__dirname` - `__filename` ESM has none of this: - `import.meta.url` instead of `__dirname` - `require` isn't available ## 6. Caching behavior In CommonJS, a module runs once and gets **cached**: ```javascript const a = require('./a'); const b = require('./a'); console.log(a === b); // true ``` In ESM, there's also caching, but imports stay **live** (if the exported variables are let/const): ```javascript import { counter } from './a.js'; ``` If the module updates the value, every import sees the update. ## 7. Tree-shaking and optimization Tree-shaking removes unused code (in Webpack, Rollup, Vite, and so on). - **CJS:** not possible, because imports are dynamic; - **ESM:** possible, because dependencies are static. Example: ```javascript import { used } from './utils.js'; // the bundler knows only "used" is needed ``` ## 8. Compatibility | Scenario | CommonJS | ES Modules | |---|---|---| | Using older packages | Yes | Sometimes needs `createRequire()` | | SSR / server | Great | Great (Node 13+) | | Browsers | No | Yes | | Tree-shaking | No | Yes | | Top-level await | No | Yes | ## 9. "Live" vs "dead" imports (an example) ### CommonJS: ```javascript // counter.js let count = 0; module.exports = { count, inc() { count++; } }; // app.js const c = require('./counter'); c.inc(); console.log(c.count); // 0 (a copy) ``` ### ES Modules: ```javascript // counter.mjs export let count = 0; export function inc() { count++; } // app.mjs import { count, inc } from './counter.mjs'; inc(); console.log(count); // 1 (a live binding) ``` ## 10. Conceptually: the key differences | Parameter | CommonJS | ESM | |---|---|---| | Foundation | Node.js (server) | The ECMAScript standard | | Execution model | Runs immediately (runtime) | Analyzes dependencies first | | Imports | Allowed anywhere | Top level only | | Environment access | `require`, `module`, `exports`, `__dirname` | `import.meta.url`, `import`/`export` | | Compatibility | Many older libraries | Modern bundlers and browsers | | Performance | Faster for local requires | More efficient in large projects | ## In one sentence > **CommonJS** is a dynamic, synchronous, Node.js-specific module system, where imports run at startup. > **ES Modules** is a static, asynchronous, standardized system, where dependencies are known in advance and an import creates a live binding.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.