Алгоритм разрешения модулей — один из центральных механизмов Webpack.
Именно он определяет, как сборщик находит файлы при использовании
import, require, динамических импортов и
внутренних зависимостей. От правильного понимания этого процесса зависит
структура проекта, скорость сборки, работа алиасов, корректность
подключения пакетов и поведение TypeScript, Babel и других
инструментов.
Webpack использует собственную систему поиска модулей, основанную на Node.js, но значительно расширяет её возможностями конфигурации.
При встрече конструкции:
import Button from './components/Button';
Webpack выполняет последовательность действий:
index.js.package.json.resolve.modules.Результатом становится точное физическое расположение модуля в файловой системе.
Webpack разделяет пути на несколько категорий.
Начинаются с:
./
../
Примеры:
import Header from './Header';
import utils from '../utils';
Такие пути рассчитываются относительно текущего файла.
Если файл расположен здесь:
src/pages/Home.js
то:
import Button from './Button';
означает:
src/pages/Button.js
А:
import api from '../api';
преобразуется в:
src/api.js
Могут работать через:
resolve.modules
или:
resolve.alias
Пример:
import Button from 'components/Button';
Webpack может искать модуль внутри:
src/components/Button.js
если настроен:
resolve: {
modules: ['src', 'node_modules']
}
Если путь не начинается с:
./
../
/
Webpack предполагает, что импортируется пакет.
Пример:
import React from 'react';
Тогда запускается поиск внутри node_modules.
Webpack начинает поиск относительно файла-инициатора.
Структура:
src/
├── app.js
├── utils/
│ └── math.js
Код:
import math from './utils/math';
Webpack:
src../utils/math.Получается:
src/utils/math
Если расширение не указано:
import Button from './Button';
Webpack перебирает расширения из:
resolve.extensions
Пример:
resolve: {
extensions: ['.js', '.jsx', '.ts']
}
Проверка будет происходить в порядке:
Button.js
Button.jsx
Button.ts
Первый найденный файл считается результатом.
Если импорт указывает на директорию:
import Layout from './layout';
Webpack проверяет:
layout/index.js
layout/index.jsx
layout/index.ts
Это поведение аналогично Node.js.
При импорте:
import lodash from 'lodash';
Webpack начинает искать папку:
node_modules/lodash
Поиск происходит не только в текущей директории.
Структура:
project/
├── src/
│ └── pages/
│ └── Home.js
├── node_modules/
Файл:
src/pages/Home.js
Импорт:
import _ from 'lodash';
Webpack проверяет:
src/pages/node_modules/lodash
src/node_modules/lodash
project/node_modules/lodash
Алгоритм поднимается вверх до корня файловой системы.
Это полностью повторяет поведение Node.js.
Такой подход позволяет:
node_modules;После нахождения директории пакета Webpack анализирует
package.json.
Пример:
node_modules/react/package.json
{
"main": "index.js"
}
Webpack загрузит:
react/index.js
Современные библиотеки часто содержат:
{
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js"
}
Webpack предпочитает:
module
потому что ES Modules лучше подходят для tree shaking.
Некоторые пакеты публикуют браузерные версии:
{
"browser": "dist/browser.js"
}
Webpack может выбрать именно этот файл.
Порядок проверки задаётся через:
resolve: {
mainFields: ['browser', 'module', 'main']
}
Webpack будет искать файл именно в этой последовательности.
По умолчанию:
resolve: {
modules: ['node_modules']
}
Можно добавить собственные директории:
resolve: {
modules: ['src', 'node_modules']
}
Теперь:
import Button from 'components/Button';
будет искать:
src/components/Button.js
Вместо:
import Button from '../. ./. ./components/Button';
можно писать:
import Button from 'components/Button';
Абсолютные импорты делают архитектуру проекта понятнее.
Без настройки:
../. ./. ./. ./
цепочки становятся трудно поддерживаемыми.
Webpack позволяет задавать короткие имена для путей.
Пример:
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
Теперь:
import Button from '@/components/Button';
означает:
src/components/Button
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
'@pages': path.resolve(__dirname, 'src/pages'),
'@utils': path.resolve(__dirname, 'src/utils')
}
}
resolve: {
alias: {
config$: path.resolve(__dirname, 'src/config/prod.js')
}
}
Символ $ означает точное совпадение.
Webpack может подменять модули.
Пример:
resolve: {
alias: {
lodash: 'lodash-es'
}
}
Теперь любой импорт:
import _ from 'lodash';
будет использовать:
lodash-es
Конфигурация:
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx']
}
Позволяет писать:
import App from './App';
вместо:
import App from './App.jsx';
Webpack проверяет расширения последовательно.
Если существуют:
App.js
App.ts
и указано:
extensions: ['.ts', '.js']
будет выбран:
App.ts
Слишком большое количество расширений замедляет сборку.
Плохо:
extensions: [
'.js',
'.jsx',
'.ts',
'.tsx',
'.json',
'.mjs',
'.cjs',
'.vue',
'.coffee'
]
Каждый импорт требует множества проверок файловой системы.
По умолчанию:
resolve: {
mainFiles: ['index']
}
Поэтому:
import layout from './layout';
ищет:
layout/index.js
resolve: {
mainFiles: ['main', 'index']
}
Теперь Webpack сначала проверит:
layout/main.js
Позволяет задавать корневые директории для абсолютных путей.
Пример:
resolve: {
roots: [
path.resolve(__dirname, 'src')
]
}
Импорт:
import '/components/Button';
будет преобразован в:
src/components/Button
Webpack разворачивает symlink:
resolve: {
symlinks: true
}
Это важно для:
resolve: {
symlinks: false
}
Иногда улучшает производительность.
Разрешение модулей — дорогая операция.
Webpack активно кэширует результаты поиска:
resolve: {
unsafeCache: true
}
Кэшируются:
В Webpack 5 появилась поддержка строгих ESM-импортов.
Некоторые ESM-модули требуют полного пути:
import utils from './utils.js';
Без расширения импорт может не работать.
resolve: {
fullySpecified: false
}
Позволяет использовать старый стиль:
import utils from './utils';
Webpack учитывает условия экспорта пакетов.
Пример:
{
"exports": {
".": {
"import": "./esm.js",
"require": "./cjs.js"
}
}
}
Webpack анализирует:
resolve: {
conditionNames: ['import', 'require']
}
и подбирает нужный файл.
Современные пакеты используют:
{
"exports": {
".": "./index.js",
"./utils": "./utils.js"
}
}
Если файл не указан в exports, импорт невозможен:
import x from 'library/internal/file';
Webpack выдаст ошибку.
Несмотря на похожий алгоритм, есть отличия.
Node ориентирован на выполнение кода.
Webpack ориентирован на сборку.
Он дополнительно поддерживает:
Webpack позволяет внедряться в процесс поиска модулей.
Пример:
resolve: {
plugins: []
}
Через плагины можно:
Конфигурация:
resolve: {
extensions: ['.js', '.jsx'],
modules: ['src', 'node_modules'],
alias: {
'@': path.resolve(__dirname, 'src')
}
}
Импорт:
import Button from '@/components/Button';
Webpack:
@.src.src/components/Button
Button.js
Button.jsx
Button/index.js
Button/index.jsx
Самая распространённая ошибка:
Module not found: Error: Can't resolve ...
import Button from './components/Button';
при отсутствии файла.
Linux чувствителен к регистру:
Button.js
и:
button.js
— разные файлы.
Если расширение не входит в resolve.extensions.
Неверная настройка:
alias: {
'@': './src'
}
вместо:
path.resolve(...)
npm install lodash
не был выполнен.
В больших проектах резолвинг может занимать значительное время.
Каждый alias увеличивает объём проверок.
Каждый импорт приводит к серии обращений к файловой системе.
Сложная структура зависимостей увеличивает число операций поиска.
Symlink увеличивают стоимость поиска файлов.
extensions: ['.js']
лучше, чем:
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json']
если TypeScript не используется.
Только для ключевых директорий.
cache: {
type: 'filesystem'
}
значительно ускоряет повторные сборки.
В monorepo алгоритм становится сложнее.
Структура:
packages/
├── app
├── ui
├── core
Webpack может:
node_modules;PnP полностью убирает node_modules.
Webpack начинает использовать специальный резолвер.
Вместо поиска по файловой системе:
node_modules/react
используется карта зависимостей.
Это ускоряет разрешение модулей и уменьшает размер проекта.
Внутри Webpack используется библиотека:
enhanced-resolve
Она отвечает за:
При импорте:
import x from '@/utils/math';
Webpack:
Итогом становится точный путь к модулю, который затем передаётся в систему загрузчиков и компиляции.