В файловых системах Unix-подобных ОС и Windows существуют символьные ссылки — symbolic links или symlinks. Симлинк представляет собой специальный объект файловой системы, который указывает на другой файл или каталог.
Webpack при разрешении модулей умеет учитывать наличие симлинков и по умолчанию пытается определить реальный физический путь файла, на который указывает ссылка.
За это отвечает параметр:
resolve: {
symlinks: true
}
По умолчанию значение равно true.
Предположим, существует структура:
project/
├── node_modules/
│ └── shared-lib -> ../. ./shared-lib
├── src/
│ └── index.js
Каталог shared-lib подключён в node_modules
не как обычная директория, а как символическая ссылка.
Такое часто происходит:
npm linksymlinks: trueЕсли параметр включён:
resolve: {
symlinks: true
}
Webpack:
Например:
node_modules/shared-lib
может быть преобразован в:
/home/user/shared-lib
Когда импортируется модуль:
import lib from 'shared-lib';
Webpack выполняет:
fs.realpathЭто влияет на:
include/excludeИсходная структура:
project/
├── node_modules/
│ └── ui-kit -> ../. ./packages/ui-kit
Импорт:
import Button from 'ui-kit/Button';
При symlinks: true Webpack может считать модуль
расположенным здесь:
../. ./packages/ui-kit/Button.js
а не внутри:
node_modules/ui-kit
symlinks: falseЕсли параметр отключён:
resolve: {
symlinks: false
}
Webpack перестаёт вычислять реальный путь.
Симлинк рассматривается как обычная директория.
То есть путь остаётся:
node_modules/shared-lib
даже если фактически пакет расположен в другом месте.
| Поведение | true |
false |
|---|---|---|
| Определение real path | Да | Нет |
Использование fs.realpath |
Да | Нет |
| Физический путь пакета | Используется | Игнорируется |
| Производительность | Ниже | Выше |
| Совместимость с monorepo | Иногда проблемная | Лучше |
Работа include/exclude |
Может ломаться | Более предсказуема |
include и
excludeОчень важная особенность связана с Babel Loader.
Конфигурация:
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
loader: 'babel-loader'
}
Если пакет подключён через symlink и symlinks: true, то
физический путь может оказаться вне src.
Например:
/home/user/packages/shared
Тогда правило include перестанет работать.
Структура:
root/
├── packages/
│ ├── app/
│ └── ui/
ui подключается в app через
workspace-ссылку.
Webpack при symlinks: true может видеть:
/packages/ui
вместо:
/packages/app/node_modules/ui
В результате:
symlinks: false часто используют в monorepoПри отключении симлинков Webpack начинает считать workspace-пакеты
обычными зависимостями из node_modules.
Это позволяет:
module.exports = {
resolve: {
symlinks: false
}
};
Очень распространённая практика для:
npm linkКоманда:
npm link
создаёт симлинк внутри node_modules.
Например:
node_modules/my-lib -> /Users/dev/my-lib
Webpack при symlinks: true начнёт использовать:
/Users/dev/my-lib
а не путь внутри node_modules.
Одна из самых известных проблем связана с React.
Структура:
app/
node_modules/react
и:
my-lib/
node_modules/react
Если библиотека подключена через symlink, Webpack может увидеть две разные копии React.
Это приводит к ошибкам:
Invalid hook call
или:
Hooks can only be called inside...
Webpack определяет модуль по абсолютному пути.
Если пути разные:
/project/node_modules/react
и:
/Users/dev/my-lib/node_modules/react
Webpack считает их разными модулями.
symlinks: false
помогаетПри отключении realpath:
resolve: {
symlinks: false
}
оба пути могут интерпретироваться как:
node_modules/react
Это уменьшает вероятность дублирования зависимостей.
Hot Module Replacement сильно зависит от идентичности модулей.
При нестабильных путях через symlink могут возникать:
Отключение symlink-resolution нередко делает HMR стабильнее.
Разрешение симлинков требует:
fs.realpathНа больших monorepo это может становиться заметной нагрузкой.
symlinks: true
полезенВключённый режим полезен, когда необходимо:
symlinks: false предпочтителенНаиболее распространённые случаи:
resolve: {
symlinks: false
}
Webpack кеширует результаты module resolution.
При использовании real path:
/home/user/lib
и symlink path:
/project/node_modules/lib
кеш может содержать разные записи для одного модуля.
Это увеличивает:
watchWatch mode отслеживает изменения файлов.
При симлинках возникают сложности:
Иногда Webpack начинает:
pnpm активно использует symlink-структуры.
Физически зависимости могут находиться:
.pnpm/
а в node_modules создаются ссылки.
Webpack без правильной настройки иногда:
Поэтому symlinks: false особенно распространён при
pnpm.
Важно понимать различие.
resolve.aliasПодмена путей внутри resolver:
alias: {
'@': path.resolve(__dirname, 'src')
}
resolve.symlinksУправление обработкой символических ссылок файловой системы.
Это совершенно разные механизмы.
Node.js тоже умеет разрешать symlink.
Webpack частично повторяет логику Node resolver, но имеет собственную систему кеширования и dependency graph.
Параметр resolve.symlinks влияет именно на внутренний
resolver Webpack.
Часто помогает вывод:
console.log(__filename);
console.log(process.cwd());
а также анализ:
npm ls
или:
pnpm why react
В Node.js:
const fs = require('fs');
console.log(
fs.realpathSync('./node_modules/shared-lib')
);
Можно увидеть физический путь, который Webpack будет использовать при
symlinks: true.
const path = require('path');
module.exports = {
resolve: {
symlinks: false
},
module: {
rules: [
{
test: /\.js$/,
include: [
path.resolve(__dirname, 'src')
],
loader: 'babel-loader'
}
]
}
};
После переключения режима могут измениться:
Иногда требуется полная очистка:
rm -rf node_modules/.cache
Во многих больших проектах:
symlinks: false используется по умолчаниюОсобенно это характерно для: