ESM проти CommonJS
1. Коротко: головна ідея
| Система | Основна концепція |
|---|---|
| CommonJS (CJS) | Модулі завантажуються під час виконання (runtime), синхронно, порядково. |
| ES Modules (ESM) | Модулі аналізуються на етапі компіляції (parse-time), завантажуються асинхронно, із суворими залежностями та «живими» імпортами. |
2. Філософія систем
CommonJS
Створювався для Node.js (2009 рік), щоб виконувати JS на сервері.
- Усе синхронно (адже файли - на локальному диску);
- Кожен файл - ізольований модуль;
- Код виконується згори вниз під час першого
require(); - Імпорти - це копія значень, а не «живі посилання».
Приклад:
// 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, тобто «живі» посилання, а не копії.
Приклад:
// 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()→ читається файл → виконується код. -
Тому шлях може бути змінною:
javascriptconst lib = require(condition ? './a' : './b');
Працює, тому що це звичайна функція.
ES Modules:
-
Імпорти повинні бути у верхній частині файлу і не можуть бути динамічними:
javascriptimport x from './a'; // коректно if (cond) import x from './b'; // помилка -
Node.js і браузер знають заздалегідь, які залежності потрібні,
і можуть завантажувати їх паралельно до виконання коду.
5. Різниця в структурі модуля
Node.js «обгортає» CommonJS-модуль у внутрішню функцію:
(function (exports, require, module, __filename, __dirname) {
// твій код
});Тому є:
requiremodule.exports__dirname__filename
У ESM цього немає:
- замість
__dirname→import.meta.url requireнедоступний
6. Поведінка під час кешування
У CommonJS модуль виконується один раз і кешується:
const a = require('./a');
const b = require('./a');
console.log(a === b); // trueУ ESM теж є кеш, але імпорти залишаються живими (якщо змінні експортовані як let/const):
import { counter } from './a.js';Якщо модуль оновлює значення, усі імпорти бачать оновлення.
7. Tree-shaking та оптимізація
Tree-shaking - це видалення невикористаного коду (у Webpack, Rollup, Vite тощо).
- CJS: неможливо, тому що імпорти динамічні;
- ESM: можливо, тому що залежності статичні.
Приклад:
import { used } from './utils.js'; // бандлер знає, що потрібен лише used8. Сумісність
| Сценарій | CommonJS | ES Modules |
|---|---|---|
| Використовувати старі пакети | Так | Іноді потрібен createRequire() |
| SSR / сервер | Відмінно | Відмінно (Node 13+) |
| Браузери | Ні | Так |
| Tree-shaking | Ні | Так |
| Top-level await | Ні | Так |
9. «Живі» проти «мертвих» імпортів (приклад)
CommonJS:
// 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:
// 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 - статична, асинхронна, стандартизована система, де залежності відомі заздалегідь, а імпорт створює живі посилання.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.