mainFields и mainFiles: точки входа пакетов

При подключении пакета через import или require Webpack должен определить, какой файл считать точкой входа модуля. Для этого используется механизм разрешения модулей (module resolution), который анализирует содержимое каталога пакета, включая файл package.json.

Два ключевых параметра системы разрешения:

  • resolve.mainFields
  • resolve.mainFiles

Они определяют:

  • какие поля package.json имеют приоритет;
  • какие файлы считаются входными по умолчанию внутри директорий.

Эти настройки особенно важны при:

  • сборке браузерных приложений;
  • поддержке ESM и CommonJS;
  • работе с универсальными пакетами;
  • оптимизации bundle;
  • подключении библиотек с несколькими версиями сборки.

resolve.mainFields

Общий принцип работы

mainFields задаёт список полей из package.json, которые Webpack должен проверять при поиске точки входа пакета.

Пример:

module.exports = {
    resolve: {
        mainFields: ['browser', 'module', 'main']
    }
};

Webpack будет искать поля в указанном порядке.

Если пакет содержит:

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

то будет выбран:

./dist/browser.js

поскольку поле browser находится первым.


Стандартный порядок полей

Для target: "web"

По умолчанию:

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

Приоритет:

  1. browser
  2. module
  3. main

Для target: "node"

По умолчанию:

mainFields: ['module', 'main']

Поле browser не используется, поскольку Node.js не нуждается в браузерных заменах.


Поле main

Назначение

main — классическая точка входа CommonJS-пакета.

Пример:

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

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

const lib = require('library');

Webpack загрузит:

library/index.js

или файл, указанный в main.


Особенности main

Чаще всего содержит CommonJS

module.exports = function() {};

Может быть неоптимальным для tree shaking

CommonJS хуже анализируется Webpack.

Из-за этого:

  • увеличивается размер bundle;
  • сложнее удалить неиспользуемый код;
  • ухудшается оптимизация.

Поле module

Назначение

Поле module обычно содержит ES Module-сборку.

Пример:

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

Преимущества ESM

Поддержка tree shaking

Webpack способен удалять неиспользуемые импорты:

import { smallUtil } from 'utils';

Если библиотека экспортирует десятки функций, но используется одна — остальные могут быть исключены из bundle.


Более эффективная оптимизация

ESM имеет статическую структуру импортов:

import x from './x.js';

Webpack может заранее анализировать зависимости.


Типичная структура современного пакета

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

Как Webpack выбирает module

Если:

mainFields: ['module', 'main']

то Webpack отдаст приоритет ESM-сборке.


Поле browser

Назначение

Позволяет предоставить браузерную версию пакета.

Пример:

{
    "browser": "./dist/browser.js",
    "main": "./dist/node.js"
}

Основная задача

Замена Node.js-зависимостей браузерными реализациями.

Например:

const fs = require('fs');

в браузере работать не может.

Библиотека может предоставить отдельную сборку:

{
    "browser": {
        "./server.js": "./browser.js"
    }
}

Подмена модулей через browser

Поле может быть объектом:

{
    "browser": {
        "path": "path-browserify",
        "fs": false
    }
}

Что происходит

Замена модуля

path → path-browserify

Исключение модуля

fs → false

Webpack подставит пустой модуль.


Настройка mainFields

Базовый пример

module.exports = {
    resolve: {
        mainFields: ['module', 'main']
    }
};

Приоритет браузерной версии

module.exports = {
    resolve: {
        mainFields: ['browser', 'module', 'main']
    }
};

Игнорирование module

Иногда ESM-сборка библиотеки работает некорректно.

Можно использовать:

module.exports = {
    resolve: {
        mainFields: ['main']
    }
};

Практический пример выбора точки входа

Структура пакета

my-lib/
├─ dist/
│  ├─ browser.js
│  ├─ esm.js
│  └─ cjs.js
└─ package.json

package.json

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

Конфигурация

resolve: {
    mainFields: ['module', 'main']
}

Результат

Webpack подключит:

./dist/esm.js

Если изменить порядок

resolve: {
    mainFields: ['main', 'module']
}

Результат

Webpack выберет:

./dist/cjs.js

Влияние на размер bundle

Выбор поля напрямую влияет на итоговую сборку.

ESM-сборка

Обычно:

  • меньше размер;
  • лучше tree shaking;
  • современный синтаксис;
  • более эффективная оптимизация.

CommonJS-сборка

Часто:

  • содержит весь пакет целиком;
  • хуже оптимизируется;
  • добавляет лишний код.

Проблемы при неправильной настройке

Случайное подключение Node.js-версии

Если убрать browser:

mainFields: ['module', 'main']

браузерный проект может получить Node.js-код.

Следствия:

  • ошибки fs;
  • ошибки path;
  • отсутствие polyfill;
  • падение сборки.

Некорректная ESM-сборка

Некоторые библиотеки публикуют нерабочий module.

Тогда появляются:

  • ошибки импорта;
  • несовместимость Babel;
  • проблемы с TypeScript;
  • runtime-ошибки.

Использование с монорепозиториями

В monorepo часто присутствуют разные типы пакетов:

packages/
├─ ui/
├─ server/
└─ shared/

Для браузерной части:

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

Для серверной:

mainFields: ['module', 'main']

resolve.mainFiles

Назначение

mainFiles определяет имена файлов, которые Webpack ищет внутри директории при импорте папки.


Пример

Импорт:

import app from './app';

Если app — директория:

app/

Webpack проверяет файлы из mainFiles.


Значение по умолчанию

mainFiles: ['index']

Webpack ищет:

app/index.js
app/index.json
app/index.wasm

с учётом resolve.extensions.


Пример настройки

module.exports = {
    resolve: {
        mainFiles: ['index', 'main']
    }
};

Теперь Webpack ищет:

app/index.js
app/main.js

Алгоритм поиска

Структура

src/
└─ utils/
   └─ main.js

Импорт

import utils from './utils';

Конфигурация

resolve: {
    mainFiles: ['main']
}

Результат

Webpack загрузит:

utils/main.js

Использование нескольких значений

resolve: {
    mainFiles: ['index', 'app', 'main']
}

Проверка будет происходить последовательно.


Влияние extensions

mainFiles работает совместно с extensions.

Пример

resolve: {
    extensions: ['.js', '.ts'],
    mainFiles: ['index']
}

Webpack ищет:

index.js
index.ts

Комбинация mainFields и mainFiles

Сценарий разрешения пакета

Webpack:

  1. Находит пакет.
  2. Читает package.json.
  3. Проверяет mainFields.
  4. Если поля отсутствуют — использует mainFiles.

Пример

Структура

node_modules/lib/
├─ index.js
└─ package.json

package.json

{}

Конфигурация

resolve: {
    mainFiles: ['index']
}

Результат

Webpack подключит:

index.js

Если отсутствует mainFiles

При импорте директории:

import x from './dir';

Webpack не сможет определить входной файл.

Появится ошибка:

Module not found

Использование в legacy-проектах

Старые проекты часто используют:

main.js
app.js
default.js

Настройка:

resolve: {
    mainFiles: ['main', 'index']
}

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


Использование в больших архитектурах

Модульные директории

components/
└─ button/
   ├─ button.js
   ├─ styles.css
   └─ index.js

Импорт

import Button from './components/button';

Что делает Webpack

Ищет:

button/index.js

Влияние на DX

Правильная настройка:

  • сокращает длину импортов;
  • улучшает читаемость;
  • упрощает рефакторинг;
  • стандартизирует структуру проекта.

Отличие mainFields от alias

mainFields

Выбирает:

какой файл пакета подключить

alias

Подменяет:

сам путь импорта

Отличие mainFiles от extensions

mainFiles

Определяет:

имя файла внутри директории

extensions

Определяет:

какие расширения проверять

Полный пример конфигурации

module.exports = {
    resolve: {
        extensions: ['.js', '.ts'],
        mainFields: ['browser', 'module', 'main'],
        mainFiles: ['index', 'main']
    }
};

Что происходит при импорте

Импорт

import lib from 'my-lib';

Webpack

  1. Ищет пакет my-lib.

  2. Читает package.json.

  3. Проверяет:

    • browser
    • module
    • main
  4. Если ничего не найдено:

    • ищет index.js
    • ищет main.js

Особенности Webpack 5

Webpack 5 активнее использует:

  • ESM;
  • tree shaking;
  • conditional exports;
  • package exports.

Из-за этого роль mainFields стала ещё важнее.


Связь с exports

Современные пакеты всё чаще используют:

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

Приоритет exports

Если присутствует exports, Webpack сначала анализирует его.

Только затем используются:

  • mainFields;
  • main;
  • mainFiles.

Совместимость со старыми пакетами

Многие legacy-библиотеки:

  • не имеют module;
  • не используют exports;
  • полагаются исключительно на main.

Поэтому:

mainFields: ['main']

до сих пор остаётся актуальной конфигурацией для старых проектов.


Типичные ошибки

Неправильный порядок полей

mainFields: ['main', 'module']

приводит к отключению преимуществ ESM.


Отсутствие browser

В браузерной сборке может подключиться Node.js-код.


Слишком много mainFiles

mainFiles: ['index', 'main', 'app', 'default']

замедляет поиск модулей.


Рекомендации для современных проектов

Браузерные приложения

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

Node.js-проекты

mainFields: ['module', 'main']

Максимальная совместимость

mainFields: ['main']

Типовая настройка директорий

mainFiles: ['index']

Для нестандартной архитектуры

mainFiles: ['main', 'index']