JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Различия между CommonJS и ESM, совместимость

Различия между CommonJS и ESM, совместимость

Модуль 4: Node.js, Next.js и Angular
1 уровень , 8 лекция
Открыта

1. Основные отличия: CommonJS vs ESM

Node.js долгое время поддерживал только CommonJS. Позже, когда браузерный мир массово перешёл на ESM, Node.js решил: «А почему бы и нам не поддерживать оба стандарта?» И вот теперь у нас есть два параллельных способа описывать модули. Прогресс — штука сложная!

  • CommonJS — это система модулей, с которой Node.js стартовал в 2009 году. Она была придумана специально для серверного JavaScript, когда о фронтенде ещё никто и не мечтал.
  • ESM (ECMAScript Modules) — это стандартная система модулей, появившаяся в самом языке JavaScript (начиная с ES6, 2015 год). Она изначально создавалась для браузеров, чтобы можно было писать модульный код прямо на клиенте.

Давайте сравним эти две системы по ключевым параметрам.

Характеристика CommonJS ESM (ECMAScript Modules)
Синтаксис импорта
const x = require('x')
import x from 'x'
Синтаксис экспорта
module.exports = ...

exports.foo = ...
export default ...

export const foo = ...
Расширение файлов
.js
(по умолчанию)
.mjs
или
.js
с type:module
Загрузка модулей Синхронная Асинхронная
Контекст Модуль — это функция Модуль — это файл
this в модуле
this === module.exports
this === undefined
Доступ к require Доступен всегда Обычно недоступен
Возможность top-level await Нет Да (Node 14.8+ и ESM)
Динамический импорт Можно:
require(expr)
Можно:
import(expr)

Синтаксис: require vs import

CommonJS:

// Импорт
const fs = require('fs');

// Экспорт
module.exports = function sayHello() { ... };
// или
exports.sayBye = function() { ... };

ESM:

// Импорт
import fs from 'fs';

// Экспорт
export default function sayHello() { ... }
export function sayBye() { ... }

Расширения файлов и package.json

CommonJS: Всё просто — любой .js файл по умолчанию считается CommonJS-модулем.

ESM:

  • Можно использовать расширение .mjs (например, index.mjs).
  • Или в package.json прописать "type": "module", тогда все .js файлы будут считаться ESM-модулями.
  • Если "type": "commonjs" или поле отсутствует — .js по умолчанию CommonJS.

Пример package.json для ESM:

{
  "type": "module"
}

Как Node.js понимает, какой модуль использовать?

  • Если файл заканчивается на .mjs — это ESM.
  • Если файл заканчивается на .cjs — это CommonJS (даже если type: module).
  • Если файл заканчивается на .js:
    • Если "type": "module" — это ESM.
    • Если "type": "commonjs" или поле отсутствует — это CommonJS.

Загрузка модулей: синхронно или асинхронно?

CommonJS: Загрузка модулей происходит синхронно — код модулей выполняется сразу при require.

ESM: Импорт асинхронный — модули могут загружаться параллельно, а сам import работает как промис (особенно при динамическом импорте).

Экспорт по умолчанию и именованный экспорт

CommonJS: Можно экспортировать что угодно (функцию, объект, класс, число).

ESM: Можно делать export default (экспорт по умолчанию) и именованные экспорты (export const, export function).

2. Взаимная совместимость: можно ли смешивать CommonJS и ESM?

Это самый частый вопрос у всех, кто начинает работать с современным Node.js. Давайте разберёмся!

Импорт CommonJS из ESM

В целом, ESM может импортировать CommonJS-модули через обычный import, но есть нюансы:

// math.cjs (CommonJS)
module.exports = {
  sum(a, b) { return a + b; }
};

// main.mjs (ESM)
import math from './math.cjs';
console.log(math.sum(2, 3)); // 5
  • В этом случае весь экспортированный объект попадёт в default (то есть import math from ...).
  • Если в CJS используется exports.foo = ..., то в ESM это будет math.foo.

Импорт ESM из CommonJS

Тут всё сложнее. В CommonJS нельзя использовать import (без дополнительных ухищрений). Но можно сделать динамический импорт через промис:

// myModule.mjs (ESM)
export function greet() {
  console.log('Hello from ESM!');
}

// index.cjs (CommonJS)
(async () => {
  const esmModule = await import('./myModule.mjs');
  esmModule.greet();
})();
  • Динамический импорт возвращает промис, поэтому нужен async/await или .then().
  • Нельзя использовать статический import в CommonJS.

require внутри ESM

В ESM-модулях нельзя использовать require напрямую — это вызовет ошибку. Для импорта CJS-модуля используйте обычный import.

import внутри CommonJS

В CommonJS нельзя использовать статический import — только динамический через import().

3. Особенности совместимости: подводные камни

Экспорт по умолчанию

  • В CommonJS можно сделать module.exports = function() {} или module.exports = { ... }.
  • В ESM — export default ....

Импортируя CommonJS-модуль в ESM, весь экспорт попадёт в default:

// math.cjs
module.exports = function(a, b) { return a + b; };

// main.mjs
import sum from './math.cjs';
console.log(sum(2, 2)); // 4

Импортируя ESM-модуль в CommonJS, результат будет объект с полями:

// math.mjs
export function sum(a, b) { return a + b; }

// main.cjs
(async () => {
  const math = await import('./math.mjs');
  console.log(math.sum(2, 2)); // 4
})();

this и глобальные переменные

  • В CommonJS this внутри модуля ссылается на module.exports.
  • В ESM — this равен undefined.

Top-level await

  • В CommonJS нельзя использовать await на верхнем уровне.
  • В ESM (Node 14.8+) можно писать await прямо в файле.

Пример:

// esm.mjs
const data = await fetch('https://api.example.com/data');

4. Когда использовать CommonJS, а когда ESM?

CommonJS:

  • Если вы пишете код только для Node.js и не планируете использовать его в браузере.
  • Если используете старые версии Node.js (<12).
  • Если работаете с большим количеством сторонних библиотек, которые написаны на CommonJS.

ESM:

  • Если хотите писать современный код, совместимый с браузерами и Node.js.
  • Если используете современные фичи (top-level await, tree-shaking).
  • Если ваш проект новый, и вам не нужно поддерживать старые версии Node.js.

Совет: В новых проектах лучше сразу использовать ESM с "type": "module" в package.json, чтобы не мучиться с переходом позже.

Совместимость сторонних библиотек

Большинство старых npm-пакетов — CommonJS. Их можно импортировать в ESM, но иногда экспорты будут только в поле default.

Некоторые новые библиотеки публикуют две версии: CommonJS и ESM (например, через поле "exports" в package.json).

Если вы пишете библиотеку, старайтесь поддерживать оба формата, чтобы не оттолкнуть часть пользователей.

5. Примеры совместимости на практике

Пример 1: Импорт CommonJS-модуля в ESM

math.cjs

module.exports = {
  sum(a, b) { return a + b; }
};

main.mjs

import math from './math.cjs';
console.log(math.sum(1, 2)); // 3

Пример 2: Импорт ESM-модуля в CommonJS

math.mjs

export function sum(a, b) { return a + b; }

main.cjs

(async () => {
  const math = await import('./math.mjs');
  console.log(math.sum(1, 2)); // 3
})();

Пример 3: Смешанный проект

Если у вас проект, где часть файлов — CommonJS, а часть — ESM, то:

  • Используйте расширения .cjs для CommonJS и .mjs для ESM.
  • Или настройте "type": "module" и используйте только ESM.

6. Типичные ошибки

Ошибка №1: Использование require в ESM
Если вы попробуете написать в ESM-модуле const fs = require('fs'), получите ошибку: ReferenceError: require is not defined in ES module scope. Используйте import fs from 'fs'.

Ошибка №2: Использование import в CommonJS
Попытка использовать import ... from ... в .js файле без "type": "module" вызовет ошибку: SyntaxError: Cannot use import statement outside a module.

Ошибка №3: Путаница с расширениями
Node.js не понимает, какой модуль загружать, если вы используете не те расширения (.js, .cjs, .mjs) или не настроили type в package.json.

Ошибка №4: Экспорт по умолчанию
Импортируя CommonJS-модуль в ESM, не забывайте, что весь экспорт попадёт в default. То есть, если в CJS module.exports = fn, то в ESM: import fn from './mod.cjs', а не import { fn } from ....

Ошибка №5: Смешивание синтаксиса
Иногда встречается код, где в одном файле используются и require, и import. Так делать нельзя — выберите один стиль для каждого файла.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 1 уровень, 8 лекция
Недоступна
Расширения файлов и настройка type:module
Расширения файлов и настройка type:module
1
Задача
Модуль 4: Node.js, Next.js и Angular, 1 уровень, 8 лекция
Недоступна
Взаимный импорт CommonJS и ESM
Взаимный импорт CommonJS и ESM
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ