Механизм разрешения модулей в Webpack определяет, где искать
импортируемые файлы и пакеты. По умолчанию Webpack использует
стандартный каталог node_modules, а также относительные
пути относительно текущего файла.
Параметр resolve.modules позволяет определить
дополнительные директории, в которых Webpack будет искать модули при
импорте без относительного пути.
Базовый пример:
const path = require('path');
module.exports = {
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'src')
]
}
};
После такой настройки становится возможным импортировать файлы из
src без длинных относительных путей:
import Button from 'components/Button';
import api from 'services/api';
Вместо:
import Button from '../. ./components/Button';
import api from '../. ./services/api';
Когда Webpack встречает импорт:
import Header from 'components/Header';
он начинает последовательный поиск:
resolve.modulesnode_modulesresolve.extensionsЕсли указано:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
Webpack попытается найти:
src/components/Header
затем:
node_modules/components/Header
Порядок элементов внутри modules критически важен.
Пример:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
Webpack сначала ищет модуль в src, а уже потом в
node_modules.
Если поменять порядок:
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, 'src')
]
}
приоритет получат npm-пакеты.
Наиболее распространённая практика — использование абсолютных путей
через path.resolve.
Правильный вариант:
const path = require('path');
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
Нежелательный вариант:
resolve: {
modules: ['./src']
}
Относительные пути могут вести себя непредсказуемо в зависимости от рабочей директории процесса.
Без resolve.modules глубокая вложенность приводит к
неудобным путям:
import Modal from '../. ./. ./. ./components/ui/Modal';
После настройки:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
импорт становится значительно чище:
import Modal from 'components/ui/Modal';
Пример структуры:
project/
├── src/
│ ├── components/
│ ├── pages/
│ ├── hooks/
│ ├── services/
│ └── utils/
├── node_modules/
├── webpack.config.js
└── package.json
Конфигурация:
const path = require('path');
module.exports = {
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
};
Теперь возможны импорты:
import HomePage from 'pages/HomePage';
import useAuth from 'hooks/useAuth';
import formatDate from 'utils/formatDate';
Webpack поддерживает любое количество директорий:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'shared'),
path.resolve(__dirname, 'core'),
'node_modules'
]
}
Это полезно в крупных монорепозиториях и многоуровневых архитектурах.
Структура:
monorepo/
├── packages/
│ ├── ui/
│ ├── core/
│ └── utils/
├── apps/
│ └── frontend/
│ └── webpack.config.js
Конфигурация:
const path = require('path');
module.exports = {
resolve: {
modules: [
path.resolve(__dirname, '../. ./packages'),
'node_modules'
]
}
};
Импорт:
import Button from 'ui/Button';
import logger from 'core/logger';
modules от
aliasresolve.modules задаёт каталоги поиска.
resolve.alias создаёт псевдонимы.
modulesresolve: {
modules: [
path.resolve(__dirname, 'src')
]
}
Импорт:
import Button from 'components/Button';
aliasresolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components')
}
}
Импорт:
import Button from '@components/Button';
modulesmodules подходит:
aliasalias предпочтительнее:
Опасная конфигурация:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
Если внутри src появится папка:
src/react/
импорт:
import React from 'react';
может начать ссылаться не на npm-пакет, а на локальную директорию.
Наиболее безопасный подход — использование namespace-директорий:
src/app/
src/shared/
src/core/
Импорты:
import Button from 'app/components/Button';
Либо использование alias:
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
Если используется TypeScript, настройки Webpack необходимо
синхронизировать с tsconfig.json.
Webpack:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
TypeScript:
{
"compilerOptions": {
"baseUrl": "src"
}
}
Иначе TypeScript не сможет корректно разрешать импорты.
При использовании Babel часто подключают:
babel-plugin-module-resolver
Пример:
plugins: [
['module-resolver', {
root: ['./src']
}]
]
Без синхронизации конфигураций возможны ситуации, когда:
Некоторые IDE не учитывают resolve.modules
автоматически.
Для корректной работы автодополнения требуется дополнительная настройка:
baseUrljsconfig.jsonДля корректного анализа импортов:
npm install eslint-import-resolver-webpack --save-dev
Конфигурация:
module.exports = {
settings: {
'import/resolver': {
webpack: {
config: 'webpack.config.js'
}
}
}
};
Jest не использует Webpack-конфигурацию автоматически.
Требуется указать:
module.exports = {
moduleDirectories: [
'node_modules',
'src'
]
};
Каждая дополнительная директория увеличивает количество проверок файловой системы.
Плохой пример:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'shared'),
path.resolve(__dirname, 'core'),
path.resolve(__dirname, 'legacy'),
path.resolve(__dirname, 'old'),
path.resolve(__dirname, 'experimental'),
'node_modules'
]
}
Чем больше директорий, тем медленнее разрешение модулей.
Особенно заметно:
resolve.extensionsWebpack комбинирует modules и
extensions.
Пример:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
],
extensions: ['.js', '.jsx', '.ts', '.tsx']
}
Импорт:
import App from 'pages/App';
Webpack проверит:
src/pages/App.js
src/pages/App.jsx
src/pages/App.ts
src/pages/App.tsx
Настройка действует и для import():
const module = await import('services/api');
Webpack будет использовать те же правила поиска.
Webpack использует библиотеку:
enhanced-resolve
Именно она реализует:
modules;mainFields;exports;imports.При импорте:
import Layout from 'layouts/Main';
Webpack пытается найти:
layouts/Main.js
layouts/Main.jsx
layouts/Main.ts
layouts/Main/index.js
Поведение зависит от:
resolve.mainFiles
По умолчанию:
['index']
const path = require('path');
module.exports = {
resolve: {
modules: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'shared'),
'node_modules'
],
extensions: [
'.js',
'.jsx',
'.ts',
'.tsx'
],
alias: {
'@': path.resolve(__dirname, 'src')
}
}
};
Достаточно:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
Лучше использовать:
aliasЖелательно:
node_modulesОшибка:
resolve: {
modules: [
path.resolve(__dirname, 'src')
]
}
В этом случае Webpack может перестать находить npm-пакеты.
Правильно:
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules'
]
}
Плохой вариант:
modules: ['./src']
Правильный вариант:
modules: [path.resolve(__dirname, 'src')]
Опасная структура:
src/lodash/
src/react/
src/vue/
Такие названия способны перекрыть реальные библиотеки.
Если Webpack настроен, а TypeScript или Jest — нет, появляются ошибки:
Module not found
Cannot resolve module
Unable to locate module
Причина обычно связана с тем, что каждая система использует собственный механизм поиска файлов.