Опция mainFields: порядок полей в package.json

В системах сборки JavaScript разрешение входных точек модулей часто зависит от структуры package.json. Пакет может содержать несколько альтернативных полей, указывающих на разные сборочные артефакты: для браузера, для ESM, для CommonJS или для специфичных окружений. Esbuild предоставляет механизм управления приоритетом этих полей через опцию mainFields, определяющую порядок, в котором анализируются ключи внутри package.json.


Роль mainFields в разрешении модулей

При импорте модуля из npm-пакета сборщик сталкивается с задачей выбора конкретного файла, указанного в метаданных пакета. Один и тот же пакет может содержать несколько входных точек:

{
  "main": "dist/index.cjs",
  "module": "dist/index.mjs",
  "browser": "dist/index.browser.js"
}

Каждое поле предназначено для разных сценариев:

  • main — классический CommonJS-экспорт
  • module — ES Module версия
  • browser — версия, оптимизированная под браузер

Опция mainFields определяет, какое из этих полей будет выбрано первым.


Поведение Esbuild по умолчанию

Esbuild использует разумные дефолты, ориентированные на современную экосистему:

  • для platform: "browser":

    mainFields: ["browser", "module", "main"]
  • для platform: "node":

    mainFields: ["main", "module"]

Этот порядок отражает приоритетность:

  1. наиболее специфичная сборка для среды (browser или main)
  2. ESM-версия (module)
  3. универсальный fallback (main)

Механика разрешения: как Esbuild выбирает поле

При импорте пакета Esbuild выполняет следующие шаги:

  1. Читает package.json целевого пакета
  2. Проверяет наличие полей в порядке, заданном mainFields
  3. Берёт первое существующее поле
  4. Резолвит путь относительно корня пакета
  5. Загружает соответствующий файл как точку входа

Пример:

{
  "browser": "dist/browser.js",
  "module": "dist/module.js",
  "main": "dist/index.js"
}

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

{
  platform: "browser",
  mainFields: ["module", "main"]
}

будет выбран dist/module.js, потому что module стоит выше main.


Значение порядка полей

Порядок в mainFields напрямую влияет на итоговый бандл. Небольшое изменение приоритета может привести к:

  • загрузке другой реализации библиотеки
  • изменению tree-shaking поведения
  • включению или исключению Node-specific API
  • разным побочным эффектам выполнения кода

Особенно критично это для библиотек, которые предоставляют разные сборки под разные среды.


Сравнение полей browser, module, main

browser

Используется для клиентских сборок. Часто содержит:

  • замену Node API на заглушки
  • отключение файловой системы
  • упрощённые зависимости

Пример:

"browser": {
  "fs": false,
  "./server.js": "./browser.js"
}

module

ESM-версия пакета. Используется для:

  • tree-shaking
  • статического анализа импорта
  • оптимизации бандла

main

Универсальный CommonJS entry point. Используется как fallback для сред без ESM-оптимизаций или когда другие поля отсутствуют.


Настройка mainFields в Esbuild

Опция передаётся в конфигурации сборки:

import esbuild from "esbuild";

esbuild.build({
  entryPoints: ["src/index.js"],
  bundle: true,
  platform: "browser",
  mainFields: ["browser", "module", "main"],
  outfile: "dist/bundle.js"
});

Изменение порядка:

mainFields: ["module", "main", "browser"]

может привести к тому, что даже в браузерной сборке будет использована ESM-версия без browser-specific замен.


Влияние platform на mainFields

Параметр platform тесно связан с mainFields.

platform: "browser"

  • приоритет: browser → module → main
  • акцент на клиентскую совместимость

platform: "node"

  • приоритет: main → module
  • акцент на серверную среду

platform: "neutral"

В нейтральном режиме Esbuild не навязывает жёсткой логики, и поведение зависит от явно указанных mainFields.


Сложные случаи с exports

Современные пакеты часто используют поле exports, которое частично перекрывает mainFields.

Пример:

{
  "exports": {
    ".": {
      "browser": "./dist/browser.js",
      "import": "./dist/module.js",
      "require": "./dist/index.cjs"
    }
  }
}

В этом случае:

  • exports имеет приоритет над mainFields
  • mainFields используется только если exports не определяет путь

Таким образом, mainFields становится fallback-механизмом для старых или упрощённых пакетов.


Поведение при отсутствии полей

Если ни одно поле из mainFields не найдено:

  1. Esbuild ищет index.js в корне пакета
  2. затем проверяет package.json-дефолты Node-стиля
  3. при отсутствии — выбрасывает ошибку резолва

Влияние на tree-shaking и оптимизацию

Выбор поля напрямую влияет на возможность удаления неиспользуемого кода:

  • ESM (module) позволяет статически анализировать зависимости
  • CommonJS (main) ограничивает оптимизацию
  • browser-бандлы могут содержать уже предсобранные версии без модульности

Таким образом, неправильный порядок mainFields может привести к:

  • увеличению размера бандла
  • потере tree-shaking
  • включению лишнего кода

Практическая интерпретация порядка

Рассмотрим три варианта конфигурации:

Вариант A

mainFields: ["browser", "module", "main"]

Поведение:

  • максимальная адаптация под фронтенд
  • приоритет клиентских реализаций

Вариант B

mainFields: ["module", "main"]

Поведение:

  • приоритет ESM
  • стабильная оптимизация дерева зависимостей

Вариант C

mainFields: ["main"]

Поведение:

  • игнорирование альтернативных сборок
  • использование только CommonJS entry point

Взаимодействие с alias и resolve

При использовании alias и кастомного resolve порядок mainFields всё ещё применяется после сопоставления алиасов:

  1. сначала применяется alias
  2. затем читается package.json
  3. затем выбирается поле по mainFields
  4. затем резолвится путь

Особенности поведения в монорепозиториях

В монорепозиториях часто встречаются пакеты с разной структурой сборок. В таких условиях:

  • разные пакеты могут иметь разные наборы полей
  • mainFields становится способом унифицировать поведение
  • неправильная настройка приводит к смешению ESM и CJS графов

Типичные ошибки конфигурации

  1. Слишком высокий приоритет main

    • отключение tree-shaking
    • ухудшение производительности
  2. Игнорирование browser

    • попадание Node-зависимостей в фронтенд
  3. Несогласованность между пакетами

    • разные версии библиотек выбирают разные entry points

Поведение при минификации и production-сборке

Хотя mainFields не влияет напрямую на минификацию, он определяет:

  • исходный код, который попадёт в граф зависимостей
  • возможность удаления мёртвого кода
  • структуру итогового bundle

В production-сценариях выбор module чаще всего даёт наиболее эффективный результат.


Связь с экосистемой npm-пакетов

Современные пакеты стремятся поддерживать одновременно:

  • обратную совместимость (main)
  • ESM-стандарт (module)
  • браузерные оптимизации (browser)
  • современный exports

mainFields в Esbuild служит механизмом интерпретации этих слоёв, когда exports отсутствует или не полностью определён.


Итоговая модель принятия решения Esbuild

Логика выбора входного файла можно представить как последовательность приоритетов:

  1. exports (если существует)
  2. mainFields (если exports не определён)
  3. index.js fallback
  4. ошибка резолва

Именно второй уровень определяет поведение mainFields, делая его критическим параметром для контроля сборки зависимостей.