intermediate
ESM vs CommonJS
Сравнивайте static ESM bindings с CommonJS require, package type, interop edges, loading timing и migration paths.
CommonJS (`require`/`module.exports`) грузится синхронно с динамическим разрешением; ESM (`import`/`export`) статически анализируется, асинхронен на верхнем уровне и стандарт для нового кода Node.
| Аспект | CommonJS | ESM | |--------|----------|-----| | Загрузка | Синхронный `require` | Асинхронный граф модулей | | Экспорт | Мутабельный `module.exports` | Live bindings | | Top-level await | Нет | Да | | `__dirname` | Встроен | Через `import.meta.url` |
`"type": "module"` в `package.json` делает `.js` ESM; иначе `.js` — CommonJS, `.mjs` — ESM. Interop: `import cjs from 'pkg'` часто оборачивает default; `createRequire` — CJS из ESM.
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const legacy = require('./legacy.cjs');
На интервью: live bindings; боль dual packages; миграция (`type`, `.cjs`, `.mjs`, tooling).
Типовые ошибки: `require` в ESM без createRequire; ожидание hoisting как у bundler; различия циклических зависимостей.
Компромисс — между простотой, производительностью, безопасностью и эксплуатацией: назовите, что оптимизировали и какую цену приняли.
Чеклист:
- Явно задайте `type` в пакете.
- Знайте мосты interop.
- Согласуйте test runner и module в tsconfig.
- Не публикуйте CJS+ESM без exports map.