Поле main в package.json традиционно
определяет точку входа пакета для CommonJS-среды. Исторически оно
появилось задолго до ES-модулей и служило универсальным способом указать
файл, который должен быть загружен при require('package') в
Node.js или аналогичных системах.
{
"name": "example-lib",
"version": "1.0.0",
"main": "dist/index.cjs.js"
}
При таком описании Node.js, а также инструменты, ориентированные на CommonJS, будут использовать указанный файл как основной экспорт пакета. Для библиотек, ориентированных исключительно на серверную среду, этого поля часто достаточно.
Однако в современных JavaScript-проектах этого недостаточно, поскольку экосистема разделилась на несколько типов модулей: CommonJS, ES Modules и браузерные сборки.
Поле module используется для указания точки входа в
формате ES Modules (ESM). Оно было введено как негласный стандарт для
сборщиков (включая Rollup, Webpack, Vite), чтобы отделить ESM-версию
пакета от CommonJS-версии.
{
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js"
}
Смысл разделения заключается в том, что ES-модули обладают преимуществами статического анализа: tree-shaking, более эффективное связывание зависимостей, предсказуемая структура импорта.
Rollup в первую очередь ориентируется именно на
module-поле при сборке зависимостей. Если пакет содержит
оба поля, module обычно используется предпочтительно,
поскольку позволяет Rollup выполнять более агрессивное удаление
неиспользуемого кода.
Ключевое отличие:
main — CommonJS-форматmodule — ES Module-форматПример использования в сборке:
import something from 'example-lib';
Если сборщик поддерживает ESM, он возьмёт версию из
module, а не из main.
browserПоле browser предназначено для указания альтернативной
сборки пакета, оптимизированной под браузерную среду. Оно решает две
задачи:
{
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"browser": "dist/index.browser.js"
}
В расширенной форме browser может быть объектом,
позволяющим заменять отдельные модули:
{
"browser": {
"fs": false,
"path": "./shims/path-browser.js",
"./internal/fs-utils.js": "./shims/fs-utils-browser.js"
}
}
Значение false означает полное исключение модуля из
бандла. Это критически важно для библиотек, которые изначально
разрабатывались под Node.js, но должны корректно работать в
браузере.
Rollup учитывает поле browser при сборке, если
активирован соответствующий резолвер (например, через
@rollup/plugin-node-resolve). В этом случае происходит
замена зависимостей на браузерные аналоги до этапа бандлинга.
Разные инструменты используют разные правила приоритета, но типичная логика выглядит следующим образом:
mainindex.js и т.д.)modulebrowser (если target = browser)mainЭта схема объясняет, почему один и тот же пакет может вести себя по-разному в разных окружениях.
Rollup не использует package.json напрямую без плагинов.
Основную роль играет @rollup/plugin-node-resolve, который
интерпретирует поля следующим образом:
Если сборка для браузера:
browsermodulemainЕсли сборка для Node.js:
browsermodule или mainТакже важно, что Rollup работает со статическим графом зависимостей, поэтому выбор конкретного entry-файла происходит один раз на этапе анализа модулей.
Наиболее значительное влияние оказывает именно связка
module + sideEffects.
Если пакет экспортирует ES Modules через module, Rollup
способен:
Пример структуры пакета:
{
"name": "example-lib",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"browser": "dist/index.browser.js",
"sideEffects": false
}
Поле sideEffects: false усиливает эффект tree-shaking,
позволяя Rollup безопасно удалять неиспользуемые импорты без анализа
побочных эффектов файлов.
main и
moduleЧасто встречается ситуация, когда main и
module указывают на один и тот же файл:
{
"main": "dist/index.js",
"module": "dist/index.js"
}
Это фактически отключает преимущества ES Modules, поскольку Rollup не получает ESM-версию пакета.
browser при наличии Node.js-зависимостейЕсли библиотека использует Node.js API, но не предоставляет
browser-замены, браузерная сборка может завершиться
ошибкой:
fspathprocess без полифилловНекоторые пакеты экспортируют ESM-версию в main, а
CommonJS — в module, что нарушает ожидания сборщиков:
{
"main": "dist/index.esm.js",
"module": "dist/index.cjs.js"
}
Такое распределение приводит к деградации tree-shaking и увеличению размера бандла.
В современных библиотеках чаще всего применяется тройственная схема:
main — CommonJS (для обратной совместимости)module — ESM (для сборщиков)browser — ESM или UMD, адаптированный под браузерПример:
{
"name": "example-lib",
"version": "2.0.0",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"browser": "dist/browser/index.js",
"sideEffects": false
}
Такой подход обеспечивает совместимость с:
Хотя package.json играет важную роль, финальное
поведение определяется конфигурацией Rollup:
export default {
input: 'src/index.js',
output: [
{
file: 'dist/cjs/index.js',
format: 'cjs'
},
{
file: 'dist/esm/index.js',
format: 'esm'
},
{
file: 'dist/browser/index.js',
format: 'esm'
}
]
};
В этом случае package.json становится контрактом между
библиотекой и внешними сборщиками, но не управляет самой генерацией
файлов.
main обеспечивает базовую совместимость с Node.jsmodule оптимизирует работу сборщиков через ESMbrowser адаптирует пакет под клиентскую средуЭти поля формируют фундаментальную модель того, как один и тот же пакет может иметь разные представления в зависимости от среды исполнения и инструментов сборки.