Модули: ESM и CommonJS

↑ 1.4 JavaScript: основы · ← Предыдущая

Желательно~3 мин чтенияУровень: junior

Зачем это на собесе

Различия форматов модулей и «живых» привязок; важно для сборки и Node.js.

Объяснение

ESM (ES Modules)CommonJS
Синтаксисimport/exportrequire/module.exports
Загрузкастатический анализ, асинхроннаядинамическая, синхронная
Привязки«живые» (live bindings)копия значения на момент require
Tree shakingвозможензатруднён
Строгий режимвсегданет
Top-level awaitданет
this на верхнем уровнеundefinedmodule.exports
Браузернативно (type="module")нет (нужна сборка)
// math.js
export const PI = 3.14;
export function sum(a, b) { return a + b; }
export default class Calc {}
 
// app.js
import Calc, { PI, sum as add } from "./math.js";
import * as math from "./math.js";
const { heavy } = await import("./heavy.js");     // динамический импорт → код-сплиттинг
export { add };            // реэкспорт: export { x } from "./x.js";
// CommonJS
const fs = require("node:fs");
module.exports = { read };

Особенности ESM: импорты «поднимаются», выполняются один раз (кэш модулей), циклические зависимости разрешаются через живые привязки; пути с расширением (./a.js) в браузере и Node.

Сборщики (Vite, webpack, Rollup, esbuild) объединяют модули, выполняют tree shaking, code splitting. Пакеты: поля type, exports, main, module в package.json.

Нюансы и подводные камни

  • Смешение ESM и CJS: require ESM-модуля недоступно, нужен import(); __dirname отсутствует в ESM (import.meta.url).
  • Побочные эффекты при импорте (код на верхнем уровне) выполняются один раз.
  • Tree shaking требует чистых модулей и sideEffects: false в package.json.
  • Именованные и default-экспорты: default затрудняет переименование и автодополнение.
  • Циклические зависимости приводят к undefined или TDZ.

Практика

  1. Разделите скрипт на модули и подключите как type="module".
  2. Выделите тяжёлую часть в динамический import() и посмотрите чанк в Network.
  3. Найдите циклическую зависимость через madge.

Вопросы с ответами

Связанные темы