Публикация ESM-пакета для потребителей CommonJS требует настройки экспорта модулей и выбора формата бандла. Основные шаги включают определение точки входа, настройку условий сборки для разных форматов (ESM/CJS) и проверку совместимости зависимостей.

Настройка package.json

В файле package.json необходимо явно указать поле "type": "module". Это сообщает инструментам сборки, что исходный код пакета использует синтаксис ESM (import/export). Без этого флага инструменты могут пытаться обрабатывать код как CommonJS или ES5, игнорируя ваши намерения.

Если ваш пакет должен поддерживать и ESM-потребителей, и CommonJS-потребителей, в поле "exports" нужно прописать условия. Это позволяет сборщику выбирать правильный формат на лету: для импортирующего модуля CommonJS будет сгенерирован код с require(), а для ESM — с import.

Пример структуры поля exports для совместимости:

Использование условных экспортов

Поле "exports" в package.json позволяет задать разные пути к файлам в зависимости от типа импортирующего проекта. Синтаксис условий работает следующим образом: если импортирующий проект имеет "type": "module", используется путь с .mjs или .js (в зависимости от настроек сборщика), а если нет — путь с .cjs.

Для реализации совместимости часто используют расширение .cjs для файлов, предназначенных для CommonJS. Это гарантирует, что даже внутри ESM-пакета файлы, которые должны быть потреблены CommonJS, будут корректно обработаны.

Пример конфигурации exports:

Настройка сборщика (Webpack/Vite/Rollup)

Инструменты сборки должны быть настроены на генерацию двух разных бандлов или одного с правильными условиями. В Webpack это делается через опцию output.format: 'esm' для основного входа и использование плагинов для условной загрузки, либо настройка entry points.

В Vite используется поле "main" и "module" в package.json. Поле "main" указывает на точку входа для CommonJS (обычно index.cjs), а "module" — для ESM (index.js). Сборщик автоматически выбирает нужный файл при импорте.

При использовании Rollup необходимо явно указать формат вывода через опцию output.format: ['cjs', 'esm'] и настроить условия экспорта через объект conditions.

Проверка зависимостей

Все зависимости вашего пакета должны быть совместимы с выбранными форматами. Если вы используете библиотеки, которые сами поддерживают только ESM, ваш пакет не сможет работать в среде CommonJS без дополнительных конвертеров.

Проверяйте версии зависимостей на наличие флагов "type": "module" в их package.json. Если зависимость требует ESM, а ваш потребитель CommonJS, потребуется использовать polyfills или альтернативные версии библиотек.

Тестирование совместимости

Перед публикацией протестируйте импорт вашего пакета в двух разных проектах: одном с "type": "module" и другом без этого флага. Убедитесь, что в обоих случаях код выполняется без ошибок.

Используйте инструменты вроде esm.sh или аналогичные онлайн-песочницы для проверки корректности экспорта модулей перед загрузкой на репозиторий.

Вопросы и ответы

Можно ли использовать только один файл index.js для ESM и CommonJS?

Нет, если потребитель CommonJS не настроен на поддержку ESM. Рекомендуется разделять файлы: index.js для ESM и index.cjs для CommonJS, указывая их в полях "module" и "main" соответственно.

Что делать, если зависимость требует строго ESM?

Используйте альтернативную версию зависимости, которая поддерживает CommonJS, или конвертируйте код через инструменты вроде esm-shim. Публикуйте пакет с явным указанием формата в exports.

По теме

{
  "name": "my-esm-package",
  "type": "module",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist-cjs/index.d.cts",
        "default": "./dist-cjs/index.cjs"
      }
    }
  },
  "main": "./dist-cjs/index.cjs",
  "module": "./dist/index.js"
}