При подключении пакета через import или
require Webpack должен определить, какой файл считать
точкой входа модуля. Для этого используется механизм разрешения модулей
(module resolution), который анализирует содержимое
каталога пакета, включая файл package.json.
Два ключевых параметра системы разрешения:
resolve.mainFieldsresolve.mainFilesОни определяют:
package.json имеют приоритет;Эти настройки особенно важны при:
resolve.mainFieldsmainFields задаёт список полей из
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']
Приоритет:
target: "node"По умолчанию:
mainFields: ['module', 'main']
Поле browser не используется, поскольку Node.js не
нуждается в браузерных заменах.
mainmain — классическая точка входа CommonJS-пакета.
Пример:
{
"main": "./index.js"
}
Используется:
const lib = require('library');
Webpack загрузит:
library/index.js
или файл, указанный в main.
mainmodule.exports = function() {};
CommonJS хуже анализируется Webpack.
Из-за этого:
moduleПоле module обычно содержит ES Module-сборку.
Пример:
{
"module": "./esm/index.js"
}
Webpack способен удалять неиспользуемые импорты:
import { smallUtil } from 'utils';
Если библиотека экспортирует десятки функций, но используется одна — остальные могут быть исключены из bundle.
ESM имеет статическую структуру импортов:
import x from './x.js';
Webpack может заранее анализировать зависимости.
{
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js"
}
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 подставит пустой модуль.
mainFieldsmodule.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
{
"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
Выбор поля напрямую влияет на итоговую сборку.
Обычно:
Часто:
Если убрать browser:
mainFields: ['module', 'main']
браузерный проект может получить Node.js-код.
Следствия:
fs;path;Некоторые библиотеки публикуют нерабочий module.
Тогда появляются:
В monorepo часто присутствуют разные типы пакетов:
packages/
├─ ui/
├─ server/
└─ shared/
Для браузерной части:
mainFields: ['browser', 'module', 'main']
Для серверной:
mainFields: ['module', 'main']
resolve.mainFilesmainFiles определяет имена файлов, которые 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']
}
Проверка будет происходить последовательно.
extensionsmainFiles работает совместно с
extensions.
resolve: {
extensions: ['.js', '.ts'],
mainFiles: ['index']
}
Webpack ищет:
index.js
index.ts
mainFields и mainFilesWebpack:
package.json.mainFields.mainFiles.node_modules/lib/
├─ index.js
└─ package.json
{}
resolve: {
mainFiles: ['index']
}
Webpack подключит:
index.js
mainFilesПри импорте директории:
import x from './dir';
Webpack не сможет определить входной файл.
Появится ошибка:
Module not found
Старые проекты часто используют:
main.js
app.js
default.js
Настройка:
resolve: {
mainFiles: ['main', 'index']
}
позволяет адаптировать Webpack без изменения структуры проекта.
components/
└─ button/
├─ button.js
├─ styles.css
└─ index.js
import Button from './components/button';
Ищет:
button/index.js
Правильная настройка:
mainFields от
aliasmainFieldsВыбирает:
какой файл пакета подключить
aliasПодменяет:
сам путь импорта
mainFiles
от extensionsmainFilesОпределяет:
имя файла внутри директории
extensionsОпределяет:
какие расширения проверять
module.exports = {
resolve: {
extensions: ['.js', '.ts'],
mainFields: ['browser', 'module', 'main'],
mainFiles: ['index', 'main']
}
};
import lib from 'my-lib';
Ищет пакет my-lib.
Читает package.json.
Проверяет:
Если ничего не найдено:
index.jsmain.jsWebpack 5 активнее использует:
Из-за этого роль 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-код.
mainFilesmainFiles: ['index', 'main', 'app', 'default']
замедляет поиск модулей.
mainFields: ['browser', 'module', 'main']
mainFields: ['module', 'main']
mainFields: ['main']
mainFiles: ['index']
mainFiles: ['main', 'index']