Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Що таке поле exports у package.json і як воно керує точками входу пакета?

Поле exports у package.json визначає, які файли пакета можна імпортувати і який файл віддати залежно від умов (ES-модулі чи CommonJS, браузер чи Node.js, типи TypeScript).

Старий підхід - поле main (одна точка входу) плюс можливість імпортувати будь-який внутрішній файл: import x from 'lib/dist/internal/helpers.js'. Користувачі пакета покладалися на внутрішню структуру, і будь-яке перейменування файлу ламало їхній код.

З exports:

{
  "name": "@acme/ui",
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    },
    "./button": {
      "types": "./dist/button.d.ts",
      "import": "./dist/button.js"
    },
    "./styles.css": "./dist/styles.css",
    "./package.json": "./package.json"
  }
}
  • import '@acme/ui' → dist/index.js, а require('@acme/ui') → dist/index.cjs;
  • import '@acme/ui/button' - дозволена «підточка»;
  • import '@acme/ui/dist/internal.js' → помилка ERR_PACKAGE_PATH_NOT_EXPORTED. Внутрішні файли інкапсульовані.

Умови перевіряються в порядку, в якому записані в об'єкті, - перша відповідна перемагає:

  • types - для TypeScript, завжди першою;
  • import / require - залежно від способу імпорту;
  • browser, node, deno, worker - середовище (збирачі передають browser);
  • development / production - режим (підтримують збирачі);
  • default - запасний варіант, завжди останнім.

Шаблони: "./icons/*": "./dist/icons/*.js" - для пакетів з сотнями файлів.

Поле imports - дзеркальне: внутрішні псевдоніми для коду самого пакета, що починаються з #:

"imports": { "#utils/*": "./src/utils/*.js" }
import { slugify } from '#utils/strings';

Працює без налаштувань збирача - на відміну від псевдонімів @/ у Vite чи TypeScript.

Підводні камені:

  • додавання exports до існуючого пакета - ламаюча зміна: усі глибокі імпорти, які раніше працювали, перестануть. Тому це роблять у мажорній версії;
  • подвійний пакет (dual package hazard): якщо застосунок завантажить і ESM-, і CJS-версію пакета, у пам'яті буде два екземпляри - два різні класи, два синглтони, instanceof між ними не працює;
  • TypeScript читає exports лише з moduleResolution: "node16"/"nodenext"/"bundler". Зі старим node він бачить лише types/main, і помилки типів з'являються лише у споживачів пакета.

Перевірка перед публікацією: інструмент @arethetypeswrong/cli і publint знаходять невідповідності між exports, файлами й типами.

Докладніше в документації: Node.js: точки входу пакетів

Схожі питання