ES Modules vs CommonJS: import и require в JavaScript
Основы3 минРедакция Innovialabs
Коротко
CommonJS (require) — старая синхронная модульная система Node.js, ES Modules (import) — официальный стандарт языка; Node.js выбирает систему по расширению файла и полю type в package.json.
Коротко о разнице
В JavaScript есть две модульные системы:
- CommonJS (CJS) —
require()иmodule.exports. Появилась в Node.js задолго до стандарта и до сих пор встречается в старых пакетах и конфигах. - ES Modules (ESM) —
importиexport. Официальный стандарт языка, работает и в браузерах, и в Node.js.
Для нового кода выбирайте ESM. CommonJS нужно понимать, чтобы читать существующий код и разбираться с ошибками совместимости.
// CommonJS
const fs = require('fs');
function sum(a, b) { return a + b; }
module.exports = { sum };
// ES Modules
import fs from 'node:fs';
export function sum(a, b) { return a + b; }
Чем они отличаются по сути
| CommonJS | ES Modules | |
|---|---|---|
| Синтаксис | require, module.exports | import, export |
| Загрузка | синхронная, во время выполнения | статический анализ до выполнения |
| Условный импорт | require где угодно | import() — динамический, возвращает Promise |
| Top-level await | нет | есть |
__dirname, __filename | есть | нет, используется import.meta |
| Строгий режим | по желанию | всегда включён |
| Tree shaking | плохо работает | работает |
Главное практическое следствие: ESM-импорты статичны. Сборщик заранее видит, что и откуда импортируется, и может выбросить неиспользуемый код. С require это сделать сложнее, потому что путь может вычисляться во время работы.
Как Node.js решает, какая система у файла
Node.js смотрит на расширение и package.json:
.mjs— всегда ESM..cjs— всегда CommonJS..js— зависит от поляtypeв ближайшем package.json:"module"означает ESM,"commonjs"или отсутствие поля — CommonJS.
Чтобы перевести проект на ESM:
{
"type": "module"
}
После этого учтите:
- В относительных импортах нужно указывать расширение:
import { sum } from './math.js'. __dirnameбольше нет. Используйтеimport.meta.urlвместе сfileURLToPathилиimport.meta.dirnameв свежих версиях Node.js.- Конфиги инструментов, которые ещё ожидают CommonJS, переименуйте в
.cjs.
Откуда берутся типичные ошибки
«Cannot use import statement outside a module» — файл с import выполняется как CommonJS. Причины:
- в package.json нет
"type": "module", а файл имеет расширение.js; - в браузере скрипт подключён без
type="module"; - тестовый раннер или старый инструмент не настроен на ESM.
Решение: добавить "type": "module", переименовать файл в .mjs или подключить <script type="module">.
«require is not defined in ES module scope» — обратная ситуация: в ESM-файле вызывают require. Замените на import или используйте createRequire из модуля node:module.
ERR_REQUIRE_ESM — CommonJS-код пытается через require загрузить пакет, который распространяется только как ESM. Новые версии Node.js умеют require() для ES-модулей без top-level await, в старых поможет только динамический import() или переход на ESM.
«Cannot find module './utils'» в ESM — забыли расширение файла в пути.
Правила совместного использования
ESM импортирует CommonJS — работает почти всегда:
import pkg from './legacy.cjs'; // module.exports целиком
import { helper } from './legacy.cjs'; // работает, если Node.js смог определить экспорты статически
Если именованный импорт не находится, импортируйте объект по умолчанию и деструктурируйте его.
CommonJS загружает ESM — сложнее:
// в CommonJS-файле
async function main() {
const { sum } = await import('./math.mjs');
console.log(sum(2, 3));
}
main();
Динамический import() работает в CommonJS всегда, но он асинхронный.
Как выбрать для проекта
- Новое приложение — ESM,
"type": "module". - Next.js, Vite и другие современные фреймворки — пишите
import/export, сборщик разберётся сам. - Старый Node.js-сервис на CommonJS — переводите постепенно: новые файлы как
.mjsили переход целиком, когда есть тесты. - Библиотека для npm — ESM как основной формат; если нужны старые потребители, публикуйте и CommonJS-сборку через поле
exports.