Настройка package.json: main, module, exports

Поле main в package.json определяет точку входа модуля при использовании в среде, поддерживающей CommonJS-резолюцию (Node.js и часть инструментов сборки).

Типичное значение:

{
  "main": "dist/index.js"
}

При импорте пакета без указания конкретного файла резолвер обращается к main и загружает указанный файл:

const lib = require("my-library");

В контексте современных сборщиков, включая Parcel, значение main перестаёт быть единственным источником истины, но продолжает играть роль базовой точки входа для CommonJS-совместимости.

Важные особенности поведения:

  • используется Node.js при require()
  • учитывается при отсутствии более специфичных полей (module, exports)
  • может игнорироваться в ESM-сборках, если задан module или exports
  • остаётся критически важным для обратной совместимости

Типичная структура пакета:

dist/
  index.js
  index.mjs
src/
  index.js
package.json
{
  "main": "dist/index.js"
}

module

Поле module введено для поддержки ES Modules в экосистеме до появления стандартизированного поля exports. Оно указывает на сборку, предназначенную для ESM-резолвинга.

Пример:

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

Разделение позволяет различным инструментам использовать оптимальный формат:

  • main → CommonJS
  • module → ES Modules

В контексте Parcel поле module активно используется для tree-shaking и построения ESM-графа зависимостей. ES Modules позволяют статически анализировать импорт:

import { sum } from "my-library";

Parcel может:

  • удалить неиспользуемый код
  • оптимизировать граф зависимостей
  • объединять модули без лишних обёрток CommonJS

Ключевая особенность:

  • module имеет приоритет над main в ESM-сценариях
  • игнорируется Node.js в чистом CommonJS-режиме
  • используется большинством современных сборщиков

Типичная конфигурация:

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

Различие между CommonJS и ESM в контексте package.json

Резолюция модулей зависит от типа окружения:

CommonJS:

const pkg = require("pkg");

Используется:

  • main
  • fallback на index.js

ES Modules:

import pkg from "pkg";

Используется:

  • module (если поддерживается инструментом)
  • либо exports (при наличии)

Parcel анализирует оба сценария и строит два параллельных графа: CJS и ESM. Это позволяет поддерживать гибридные библиотеки без дублирования логики импорта.


exports

Поле exports является современным стандартом Node.js и определяет явную карту экспортируемых точек входа. Оно заменяет неявное поведение main и module, предоставляя строгую структуру доступа к файлам пакета.

Простейший вариант:

{
  "exports": "./dist/index.js"
}

Более сложная конфигурация:

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

Такой подход позволяет:

  • разделять ESM и CommonJS
  • управлять точками входа
  • скрывать внутреннюю структуру пакета
  • предотвращать глубокие импорты (my-lib/internal/file.js)

Parcel полностью поддерживает exports и использует его как приоритетный источник резолюции.


Приоритет резолюции полей

При сборке и импорте пакета порядок приоритета обычно следующий:

  1. exports
  2. module (в ESM-сценариях)
  3. main
  4. index.js (fallback)

Этот порядок важен для понимания поведения при конфликтующих настройках.

Пример конфликта:

{
  "main": "dist/cjs.js",
  "module": "dist/esm.js",
  "exports": "./dist/modern.js"
}

В этом случае exports полностью перекрывает main и module.


Использование в библиотечной разработке

При создании библиотеки важно учитывать совместимость с разными окружениями.

Рекомендуемая структура:

{
  "name": "example-lib",
  "version": "1.0.0",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "exports": {
    ".": {
      "require": "./dist/cjs/index.js",
      "import": "./dist/esm/index.js"
    }
  }
}

Такая конфигурация обеспечивает:

  • поддержку require
  • поддержку import
  • корректную работу современных сборщиков, включая Parcel
  • защиту внутренних модулей

Влияние на tree-shaking

Tree-shaking зависит от того, какой формат используется:

  • ESM (module, import) → статический анализ
  • CJS (main, require) → ограниченный анализ

Parcel использует module и exports.import как основной источник для оптимизаций. Это позволяет удалять неиспользуемые части кода.

Пример:

// utils.js
export function used() {}
export function unused() {}
import { used } from "lib";

В итоговую сборку попадёт только used.


Переход от main к exports

Современные пакеты постепенно смещаются от main/module к exports, поскольку:

  • exports более строгий
  • поддерживает многоточечные входы
  • контролирует доступ к внутренним файлам
  • унифицирует поведение между Node.js и сборщиками

Тем не менее main остаётся необходимым для:

  • старых инструментов
  • legacy-проектов
  • минимальной конфигурации пакета

Сценарии совместимости

Библиотека для широкого использования:

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

Современная библиотека:

{
  "exports": {
    ".": {
      "import": "./dist/index.js"
    }
  }
}

Смешанный подход:

{
  "main": "dist/cjs.js",
  "module": "dist/esm.js",
  "exports": {
    ".": {
      "require": "./dist/cjs.js",
      "import": "./dist/esm.js"
    }
  }
}

Parcel одинаково корректно обрабатывает все три варианта, выбирая оптимальный путь сборки в зависимости от целевой среды.


Влияние структуры package.json на сборку Parcel

Parcel анализирует package.json на этапе построения графа зависимостей. Поля влияют на:

  • выбор входного файла
  • тип модульной системы
  • оптимизацию кода
  • разделение чанков
  • удаление мёртвого кода

Особенно важно:

  • наличие module ускоряет ESM-анализ
  • наличие exports ограничивает доступ к внутренним файлам
  • отсутствие явных полей приводит к fallback на main

При сборке монорепозиториев это становится критически важным, поскольку каждая зависимость может иметь собственную стратегию экспорта.


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

Часто встречающиеся проблемы:

Несогласованность путей

{
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "exports": "./src/index.js"
}

Проблема: exports указывает на исходники вместо сборки.


Отсутствие ESM-сборки при заявленном module

{
  "module": "dist/index.js"
}

Проблема: файл CommonJS помечен как ESM.


Перекрытие exports

{
  "main": "dist/a.js",
  "exports": "./dist/b.js"
}

main становится недостижимым.


Роль полей при публикации пакета

При публикации в npm структура package.json определяет:

  • какие файлы доступны пользователю
  • как пакет импортируется
  • будет ли работать tree-shaking
  • совместимость с bundler-ами

Parcel при установке пакета анализирует эти поля и строит оптимальный план включения зависимостей в финальный бандл, минимизируя лишний код и дублирование модулей.