Поле exports в package.json определяет
публичный API пакета и управляет тем, какие модули могут быть
импортированы извне. В контексте сборщика Esbuild это становится
ключевым механизмом контроля разрешения модулей, особенно в современных
библиотеках, ориентированных на ESM и гибридные окружения.
Основная идея exports заключается в том, чтобы заменить
неявный доступ к внутренней структуре пакета на строго описанный
интерфейс. Вместо прямых импортов вида
my-lib/dist/internal/file.js используется декларативное
сопоставление экспортируемых точек входа.
exportsПоле exports может задавать единственную точку входа или
набор субпутей:
{
"name": "my-lib",
"version": "1.0.0",
"exports": "./dist/index.js"
}
В этом случае пакет разрешается только через основной импорт:
import lib from "my-lib";
Попытка обратиться к внутренним файлам:
import x from "my-lib/dist/internal.js";
становится невозможной, если сборщик и рантайм соблюдают правила
exports.
Более гибкий вариант — объектная форма:
{
"exports": {
".": "./dist/index.js",
"./feature": "./dist/feature.js"
}
}
Здесь:
"." — основной модуль пакета"./feature" — публичный субпутьИспользование:
import lib from "my-lib";
import feature from "my-lib/feature";
Esbuild при такой конфигурации строго следует указанной карте и не позволяет обходить её через файловые пути.
exports поддерживает условия, позволяющие адаптировать
модуль под разные окружения:
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
}
}
}
Условия:
import — используется для ESMrequire — для CommonJSdefault — fallbackEsbuild учитывает эти условия при сборке в зависимости от параметров
format и platform.
exportsEsbuild реализует собственный механизм резолва модулей, совместимый с
Node.js, но оптимизированный для скорости. При обработке
exports учитываются:
exports в package.jsonmain (как fallback)module (в некоторых сценариях)platform: node | browser | neutralesm или cjs)Пример конфигурации:
import esbuild from "esbuild";
esbuild.build({
entryPoints: ["src/index.js"],
bundle: true,
platform: "node",
format: "esm",
outdir: "dist"
});
В этом режиме Esbuild выбирает экспорт, соответствующий
import-ветке условий.
Субпуть-экспорты позволяют явно ограничить доступ к внутренним частям пакета:
{
"exports": {
".": "./dist/index.js",
"./utils": "./dist/utils/index.js",
"./hooks/*": "./dist/hooks/*.js"
}
}
Поддержка шаблонов (*) позволяет массово маппировать
структуры каталогов.
Использование:
import { helper } from "my-lib/utils";
import hook from "my-lib/hooks/use-hook";
Esbuild разрешает такие пути на этапе сборки, заменяя их на конкретные файлы.
До появления exports было распространено использование
глубоких импортов:
import x from "my-lib/dist/internal/x.js";
При включённом exports такие обращения блокируются.
Esbuild ведёт себя аналогично Node.js: если путь не описан в
exports, он считается недоступным.
Это особенно важно для библиотек, которые хотят гарантировать стабильность API и не допускать случайных зависимостей от внутренних файлов.
Esbuild учитывает различия модульных систем при выборе экспорта:
format: esm приоритет у importformat: cjs приоритет у requireПример:
{
"exports": {
".": {
"import": "./esm/index.js",
"require": "./cjs/index.js"
}
}
}
Такой подход позволяет одной библиотеке поддерживать оба мира без дублирования логики импорта на стороне пользователя.
browser и других fallback-механизмовХотя exports имеет приоритет, Esbuild может учитывать
дополнительные поля:
browser — замена модулей для браузерной сборкиmain — устаревшая точка входаmodule — ESM-вариант (в legacy-пакетах)Однако при наличии exports большинство
fallback-механизмов игнорируется, поскольку exports
считается источником истины.
Если импорт не соответствует ни одному ключу exports,
Esbuild завершает резолв с ошибкой:
Could not resolve "my-lib/unknown" (exports field does not define this subpath)
Это поведение совпадает с Node.js и позволяет выявлять ошибки на этапе сборки, а не в рантайме.
Esbuild поддерживает передачу пользовательских условий через
conditions:
esbuild.build({
entryPoints: ["src/index.js"],
bundle: true,
conditions: ["custom", "browser"]
});
И в package.json:
{
"exports": {
".": {
"browser": "./dist/browser.js",
"custom": "./dist/custom.js",
"default": "./dist/index.js"
}
}
}
Условия проверяются в порядке приоритета, что позволяет строить сложные схемы дистрибуции.
В монорепозиториях exports часто используется для
строгого разделения пакетов. Esbuild корректно разрешает такие структуры
при условии правильного указания путей:
packages/
core/
package.json
src/
ui/
package.json
src/
Каждый пакет описывает собственный exports, предотвращая
перекрёстные внутренние импорты.
"." в exports, что блокирует
основной импорт пакетаmain, module и
exports при миграции старых пакетов* без соответствующей структуры
файлов после сборкиПри bundle: true Esbuild стремится полностью резолвить
зависимости на этапе сборки. Это означает, что exports
применяется не только к внешним импортам, но и к внутреннему графу
зависимостей.
В результате:
exports в современной экосистеме сборкиИспользование exports становится стандартом для
библиотек, ориентированных на:
Esbuild, благодаря быстрому резолвингу и строгому соблюдению схемы
exports, делает поведение сборки более детерминированным и
близким к реальному Node.js окружению, минимизируя расхождения между
разработкой и продакшеном.