CommonJS vs ES Modules в Node
Node.js поддерживает две системы модулей: CommonJS (CJS) — историческая система Node.js с require/module.exports, и ES Modules (ESM) — стандарт JavaScript с import/export, поддерживаемый с Node.js 12+.
Зачем нужно
Выбор между CJS и ESM влияет на совместимость пакетов, синтаксис импортов, возможность использования top-level await и инструментов (Jest, TypeScript, Vite). Понимание различий позволяет избежать ошибок при смешении модулей и правильно настроить проект.
Где используется
- CJS — legacy Node.js проекты, большинство npm-пакетов до 2022
- ESM — современные проекты, TypeScript (
moduleResolution: node16), пакеты только с ESM (chalk v5+, got v12+) - Dual CJS+ESM — npm-библиотеки, которые поддерживают оба формата
Основной контент
CommonJS (CJS)
// Экспорт
module.exports = { add, multiply };
// или
exports.greet = (name) => `Hello, ${name}`;
// Импорт
const { add } = require('./math');
const fs = require('fs');
const path = require('path');
// Динамический импорт
const plugin = require(`./plugins/${name}`);
// CJS — синхронный, модуль кешируется после первого require
ES Modules (ESM)
// package.json
{ "type": "module" }
// или расширение .mjs
// Экспорт
export function add(a, b) { return a + b; }
export const PI = 3.14;
export default class Calculator {}
// Импорт — обязательно указывать расширение .js
import { add } from './math.js';
import Calculator from './calculator.js';
import * as utils from './utils.js';
// Динамический импорт (асинхронный)
const { default: plugin } = await import(`./plugins/${name}.js`);
// Top-level await (только в ESM)
const data = await fetch('https://api.example.com/data').then(r => r.json());
Ключевые отличия
| Характеристика | CommonJS | ES Modules |
|---|---|---|
| Синтаксис | require/exports |
import/export |
| Загрузка | Синхронная | Асинхронная |
| Top-level await | Нет | Да |
__dirname |
Есть | Нет (см. ниже) |
this в модуле |
module.exports |
undefined |
| Расширение файла | .js, .cjs |
.mjs или .js при "type":"module" |
| Динамический импорт | require |
import (промис) |
__dirname и __filename в ESM
// В ESM эти переменные недоступны, используем import.meta
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Импорт CJS из ESM и наоборот
// ESM может импортировать CJS
import cjsModule from './legacy.cjs'; // работает
// CJS НЕ МОЖЕТ синхронно require ESM
// Только через динамический import
async function loadESM() {
const { add } = await import('./math.mjs');
return add(1, 2);
}
package.json конфигурация
{
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
Частые ошибки
require is not defined— файл определён как ESM ("type":"module"), но используется CJS синтаксисCannot use import statement— файл.jsв CJS-проекте содержитimport; переименовать в.mjsили добавить"type":"module"- Пропустить
.jsв ESM-импортах —import { x } from './math'→ ошибка; нужно'./math.js' - Top-level await в CJS — не поддерживается, обернуть в
async function main() {}
Связанные темы
Ресурсы
🎓 Источник: Модули, слои, структура проекта, песочницы
- 📅 2018-10-02 · YouTube · [Marp](../../Documents/TimurShemsedinov/2018-10-02 — Модули, слои, структура проекта, песочницы в JavaScript и Node.js (O7A9chb573E).md)
- Тезисы:
requireисполняется СИНХРОННО — модуль кешируется вrequire.cacheпосле первого вызоваrequireвозвращает один и тот же объект → паттерн синглтон- Можно сбросить кеш:
delete require.cache[require.resolve('./mod')]— для hot reload - ESM с
.mjsнесовместим сrequire— нужен динамическийimport package.jsonуказывает точку входа в полеmain(CJS) илиmodule/exports(ESM)- Сборка модулей через
Object.assign(exports, require('./a'), require('./b'))— антипаттерн, перекрытие ключей
🎓 Источник: Node.js модули ECMA, Common.js, Module API
- 📅 2021-09-14 · YouTube · [Marp](../../Documents/TimurShemsedinov/2021-09-14 — 💻 Node.js модули ECMA, Common.js, Module API (CJr2vS3hjMU).md)
- Тезисы: обзор обеих систем модулей, package.json "type", conditional exports
🎓 Источник: Node.js modules and packages in ECMA and CommonJS
- 📅 2021-09-15 · YouTube
- Тезисы: dual packages, "exports" field, разрешение путей в ESM
🎓 Источник: Как избавиться от кучи require/import
- 📅 2023-06-21 · YouTube
- Тезисы:
- Загрузка папки целиком через рекурсивный обход + объект-namespace
- В Metarhia подход: одна
applicationзагружает все слои lib/api/domain как namespaces - Не нужно дублировать imports в каждом файле — вся библиотека доступна как
app.lib.X - Подход не для всех — теряет статический анализ, требует продуманной архитектуры