Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «ESM проти CommonJS». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Концептуально CommonJS - імперативна система, де модулі завантажуються під час виконання, динамічно й синхронно; ESM - декларативна, аналізує залежності ще на етапі парсингу, завантажує асинхронно й дає живі посилання замість копій значень. **Ключове:** саме статичність імпортів ESM (відомі заздалегідь, лише на верхньому рівні файлу) дозволяє tree-shaking - CommonJS цього принципово не може, бо `require()` - звичайна функція, яку можна викликати умовно.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 1. Коротко: головна ідея | Система | Основна концепція | |---|---| | **CommonJS (CJS)** | Модулі завантажуються **під час виконання** (runtime), синхронно, порядково. | | **ES Modules (ESM)** | Модулі аналізуються **на етапі компіляції** (parse-time), завантажуються **асинхронно**, із суворими залежностями й "живими" імпортами. | ## 2. Філософія систем ### CommonJS Створювався **для Node.js** (2009 рік), щоб виконувати JS на сервері. - Усе синхронно (адже файли - на локальному диску); - Кожен файл - ізольований модуль; - Код виконується згори вниз при першому `require()`; - Імпорти - це **копія значень**, не "живі посилання". Приклад: ```javascript // math.js module.exports = { x: 1 }; // app.js const math = require('./math'); console.log(math.x); // 1 math.x = 2; // зміна лише локальної копії ``` ### ES Modules Створювалися **для мови JavaScript** (ES6, 2015) - щоб один стандарт працював і в браузерах, і в Node.js. - Завантаження **асинхронне**, декларативне; - Імпорти **статичні** - відомі до виконання коду; - Можна оптимізувати, робити *tree-shaking*; - Імпорти - це **live bindings**, тобто "живі" посилання, а не копії. Приклад: ```javascript // counter.js export let count = 0; export function inc() { count++; } // app.js import { count, inc } from './counter.js'; inc(); console.log(count); // 1 (значення оновилося) ``` ## 3. Концептуальні відмінності | Концепція | CommonJS | ES Modules | |---|---|---| | Час завантаження | Під час виконання (runtime) | До виконання (parse-time) | | Імпорти | Динамічні (`require()`) | Статичні (`import`) | | Тип зв'язку | Значення копіюються | Живі посилання (live bindings) | | Порядок виконання | Згори вниз | Визначається залежностями | | Асинхронність | Синхронно | Асинхронно | | Оптимізація | Неможлива (runtime) | Можливий tree-shaking | | Контекст | `this` = `module.exports` | `this` = `undefined` | | Кешування | Після першого виклику | Є, але з "живими" значеннями | | Екосистема | Node.js, старі пакети | Сучасний JS, браузери, Node 13+ | | Архітектура | Імперативна | Декларативна | | Сумісність | Тільки сервер | Універсальна (сервер + клієнт) | ## 4. Різниця в момент завантаження й компіляції ### CommonJS: - Виконується рядок `require()` → читається файл → виконується код. - Тому шлях може бути змінною: ```javascript const lib = require(condition ? './a' : './b'); ``` працює, тому що це звичайна функція. ### ES Modules: - Імпорти мають бути **у верхній частині файлу** і **не можуть бути динамічними**: ```javascript import x from './a'; // ок if (cond) import x from './b'; // помилка ``` - Node.js і браузер знають наперед, які залежності потрібні, і можуть завантажувати їх **паралельно** до виконання коду. ## 5. Різниця у структурі модуля Node.js "обгортає" CommonJS-модуль у внутрішню функцію: ```javascript (function (exports, require, module, __filename, __dirname) { // твій код }); ``` Тому є: - `require` - `module.exports` - `__dirname` - `__filename` У ESM цього немає: - замість `__dirname` → `import.meta.url` - `require` недоступний ## 6. Поведінка при кешуванні У CommonJS модуль виконується один раз і **кешується**: ```javascript const a = require('./a'); const b = require('./a'); console.log(a === b); // true ``` У ESM - теж кеш, але імпорти лишаються **живими** (якщо змінні експортовані як let/const): ```javascript import { counter } from './a.js'; ``` Якщо модуль оновлює значення - усі імпорти бачать оновлення. ## 7. Tree-shaking та оптимізація Tree-shaking - видалення невикористовуваного коду (у Webpack, Rollup, Vite тощо) - **CJS:** неможливо, бо імпорти динамічні; - **ESM:** можливо, бо залежності статичні. Приклад: ```javascript import { used } from './utils.js'; // збирач знає, що потрібен лише used ``` ## 8. Сумісність | Сценарій | CommonJS | ES Modules | |---|---|---| | Використовувати старі пакети | Так | Іноді потрібен `createRequire()` | | SSR / сервер | Відмінно | Відмінно (Node 13+) | | Браузери | Немає | Так | | Tree-shaking | Немає | Так | | Top-level await | Немає | Так | ## 9. "Живі" vs "мертві" імпорти (приклад) ### 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 (копія) ``` ### 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 (жива посилання) ``` ## 10. Концептуально - ключові відмінності | Параметр | CommonJS | ESM | |---|---|---| | Основа | Node.js (сервер) | Стандарт ECMAScript | | Модель виконання | Виконує одразу (runtime) | Спочатку аналізує залежності | | Імпорти | Можна будь-де | Тільки на верхньому рівні | | Доступ до середовища | `require`, `module`, `exports`, `__dirname` | `import.meta.url`, `import/export` | | Сумісність | Багато старих бібліотек | Сучасні збирачі й браузери | | Продуктивність | Швидше при локальних require | Ефективніше при великих проєктах | ## Одним реченням > **CommonJS** - динамічна, синхронна, специфічна для Node.js модульна система, де імпорти виконуються під час запуску. > **ES Modules** - статична, асинхронна, стандартизована система, де залежності відомі заздалегідь, а імпорт створює живі посилання.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.