Современная экосистема JavaScript перешла от простого указания
main в package.json к сложной системе
управления точками входа через поле exports. Это изменение
напрямую повлияло на то, как формируются бандлы в Rollup, как
проектируется структура пакетов и как выстраивается стратегия вывода
(output strategy) при сборке библиотек.
exports и ограничения доступа к модулямПоле exports определяет публичный API пакета и управляет
тем, какие модули доступны при импорте извне.
{
"name": "my-lib",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
Ключевой момент заключается в том, что все пути, не указанные в
exports, становятся недоступными для внешнего импорта:
import something from "my-lib/internal/helper.js"; // может быть запрещено
Такой подход вводит строгую инкапсуляцию, превращая пакет в контролируемую систему публичных контрактов.
exports позволяет описывать не только корневой модуль,
но и субпути:
{
"exports": {
".": "./dist/index.js",
"./utils": "./dist/utils.js",
"./feature": {
"import": "./dist/feature/index.js",
"require": "./dist/feature/index.cjs"
}
}
}
Это формирует явную карту публичной поверхности пакета. В контексте Rollup это напрямую влияет на архитектуру сборки: каждый экспорт становится потенциальной точкой входа или отдельным чанком.
exports поддерживает условия (conditional exports),
которые определяют, какой файл будет использоваться в зависимости от
окружения:
import — ESMrequire — CommonJSnode — Node.js runtimebrowser — браузерная средаdefault — fallback{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"default": "./dist/index.mjs"
}
}
}
Это создаёт необходимость синхронизации стратегии вывода Rollup с целевыми форматами.
Rollup не просто собирает модули в файл, он формирует конкретные
артефакты в зависимости от output.format:
esm — ES Modulescjs — CommonJSiife — самовызывающаяся функцияumd — универсальный форматsystem — SystemJSexport default {
input: "src/index.js",
output: [
{ file: "dist/index.mjs", format: "esm" },
{ file: "dist/index.cjs", format: "cjs" }
]
};
Стратегия вывода в этом случае должна быть согласована с
exports, иначе пакет может стать неконсистентным: Node
будет ожидать один файл, а фактически использоваться будет другой.
exports и стратегии dual-packageНаиболее распространённый паттерн — dual package (ESM + CJS). В этом
случае exports становится центральной точкой
синхронизации:
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
Rollup обязан генерировать два согласованных артефакта:
importrequireКритически важно, чтобы:
exports на external-логику RollupRollup использует external для исключения зависимостей
из бандла. Однако exports влияет на то, как эти зависимости
интерпретируются:
exports, глубокие импорты
становятся запрещённымиexport default {
external: ["lodash"]
};
При использовании exports у lodash
(гипотетически) доступ к внутренним путям был бы ограничен, и Rollup
должен учитывать только публичный API.
@rollup/plugin-node-resolveRollup сам по себе не реализует полный алгоритм Node resolution. Эту
задачу выполняет @rollup/plugin-node-resolve.
Современные версии плагина поддерживают exports-map:
import или require
условиеЭто критично для предотвращения расхождений между dev-сборкой и runtime Node.js.
exports картеСтратегия вывода в Rollup должна зеркально соответствовать структуре
exports. Несоответствие приводит к нескольким типичным
проблемам:
Broken entry points
./feature, но Rollup не генерирует
соответствующий файлMismatch форматов
exports.import указывает на ESM, но файл собран как
CJSTree-shaking деградация
При наличии множества subpath exports возникает архитектурный вопрос: генерировать ли один бандл или несколько независимых артефактов.
Два основных подхода в Rollup:
input: "src/index.js"
Плюсы:
Минусы:
exportsinput: {
index: "src/index.js",
utils: "src/utils.js",
feature: "src/feature/index.js"
}
Плюсы:
exportsМинусы:
Опция preserveModules позволяет сохранять структуру
исходных модулей:
output: {
dir: "dist",
format: "esm",
preserveModules: true
}
Это приближает структуру output к exports-карте,
особенно при использовании subpath exports. Каждый файл может
соответствовать отдельному публичному маршруту.
Поле sideEffects в package.json
взаимодействует с exports косвенно, но критически важно для
Rollup:
{
"sideEffects": false
}
При корректной настройке Rollup может безопасно удалять
неиспользуемые части кода. Однако при неправильной синхронизации с
exports возможны ситуации, когда:
exports в стратегии сборкиexports фактически становится декларацией контракта
пакета, а Rollup — механизмом его реализации.
Стратегия вывода должна учитывать:
dist/ карте
exportsexports, типов и декларацийПри TypeScript-сборке появляется дополнительный слой:
{
"types": "./dist/index.d.ts"
}
или через exports:
{
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
}
}
Rollup-стратегия должна обеспечивать, чтобы .d.ts файлы
зеркально соответствовали структуре JS-экспорта.
Связь между exports и Rollup-выводом формируется на трёх
уровнях:
Любое расхождение между этими уровнями приводит к неконсистентности пакета, особенно в условиях dual-package и subpath exports, где структура экспорта становится не просто конфигурацией, а основой архитектуры распространения библиотеки.