← Назад к вопросам
Объясните разницу между CommonJS и ES modules в Node.js.
1.3 Junior🔥 191 комментариев
#Node.js и JavaScript#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
CommonJS vs ES Modules в Node.js
Node.js поддерживает две модульные системы: CommonJS (CJS) - исторический стандарт Node.js, и ES Modules (ESM) - стандарт ECMAScript, пришедший из браузеров. Понимание различий между ними критично для работы с современными Node.js проектами.
CommonJS (CJS)
Исходная модульная система Node.js, существует с первых версий:
// Экспорт
module.exports = { hello: "world" };
module.exports.greet = function(name) { return `Hello, ${name}`; };
exports.add = (a, b) => a + b;
// Импорт
const fs = require("fs");
const { readFile } = require("fs");
const myModule = require("./myModule");
Характеристики CJS:
- Синхронная загрузка:
require()блокирует выполнение до загрузки модуля - Динамические импорты:
require()можно вызывать внутри условий, циклов, функций - Кэширование: модуль загружается один раз, результат кэшируется в
require.cache - Расширение:
.js,.cjs,.json,.node - this: указывает на
module.exports
// Динамический require - работает
if (process.env.NODE_ENV === "production") {
const logger = require("./prodLogger");
} else {
const logger = require("./devLogger");
}
ES Modules (ESM)
Стандарт ECMAScript, поддерживается в Node.js с v12 (стабильно с v16):
// Экспорт
export const hello = "world";
export function greet(name: string) { return `Hello, ${name}`; }
export default class UserService { }
// Импорт
import fs from "fs";
import { readFile } from "fs/promises";
import UserService from "./UserService.js"; // расширение обязательно!
import * as utils from "./utils.js";
Характеристики ESM:
- Асинхронная загрузка: модули загружаются асинхронно
- Статические импорты:
importтолько на верхнем уровне файла - Tree-shaking: бандлеры могут удалить неиспользуемый код благодаря статическому анализу
- Strict mode: ESM всегда работает в strict mode
- this: равен
undefinedна верхнем уровне
// Динамический import
const module = await import("./dynamicModule.js");
// Top-level await - работает в ESM
const config = await loadConfig();
Ключевые различия
| Аспект | CommonJS | ES Modules |
|---|---|---|
| Синтаксис | require() / module.exports | import / export |
| Загрузка | Синхронная | Асинхронная |
| Парсинг | Runtime | Compile-time |
| Tree-shaking | Нет | Да |
| Top-level await | Нет | Да |
__dirname | Есть | Нет (нужен workaround) |
| Расширение | .js, .cjs | .mjs или "type": "module" |
| Циклические зависимости | Частичный объект | Live bindings |
Как включить ESM в Node.js
Способ 1: package.json
{
"type": "module"
}
Все .js файлы трактуются как ESM. Для CJS используйте расширение .cjs.
Способ 2: расширение .mjs
utils.mjs -> ESM
utils.js -> CJS (по умолчанию)
utils.cjs -> CJS (явно)
Workaround для __dirname в ESM
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
const esmModule = require("./modern.mjs"); // ОШИБКА!
// CJS может использовать динамический import для ESM
const esmModule = await import("./modern.mjs"); // работает
Dual Package (CJS + ESM)
Библиотеки, поддерживающие оба формата, используют conditional exports:
{
"name": "my-library",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.cjs"
}
}
}
Рекомендации
- Новые проекты: используйте ESM с
"type": "module"в package.json - TypeScript: компилируйте в ESM с
"module": "NodeNext"в tsconfig - Библиотеки: поддерживайте оба формата через conditional exports
- Не забывайте расширения: в ESM
import "./utils"не работает, нужноimport "./utils.js"