← Назад к вопросам

Объясните разницу между 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();

Ключевые различия

АспектCommonJSES Modules
Синтаксисrequire() / module.exportsimport / export
ЗагрузкаСинхроннаяАсинхронная
ПарсингRuntimeCompile-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"
Объясните разницу между CommonJS и ES modules в Node.js. | PrepBro