esbuild — высокопроизводительный бандлер, ориентированный на скорость
сборки за счёт реализации на Go и агрессивной оптимизации графа модулей.
В экосистеме Node.js и фронтенд-инструментов он часто используется как
замена более медленным JavaScript-бандлерам в задачах разработки и
продакшена.
Символические ссылки (symlinks) представляют собой специальные файлы,
которые указывают на другой путь в файловой системе. В контексте
JavaScript-проектов они часто встречаются в следующих сценариях:
- монорепозитории (pnpm, Yarn workspaces, Lerna)
- локальная разработка пакетов через
npm link
- разделение общих модулей между несколькими проектами
- сложные схемы деплоя, где зависимости физически находятся вне
node_modules
Поведение инструментов сборки при встрече символических ссылок
критично влияет на:
- разрешение импортов (
import)
- идентификацию модулей как «одинаковых» или «разных»
- кэширование зависимостей
- корректность tree-shaking и дедупликации
Важнейшая проблема: один и тот же модуль может быть доступен по
разным путям, если проходит через symlink-цепочку. Это ломает
предположения бандлера о уникальности модулей.
Модель резолвинга модулей в
esbuild
esbuild использует собственный высокоскоростной механизм разрешения
модулей, который стремится повторять семантику Node.js, но с упрощениями
ради производительности.
При стандартной настройке esbuild:
- символические ссылки разыменовываются
(realpath)
- итоговый путь нормализуется до физического расположения файла
- модуль считается уникальным по реальному пути, а не по пути
импорта
Это поведение важно для:
- корректного объединения зависимостей
- предотвращения дублирования пакетов
- стабильного tree-shaking
Однако оно может приводить к расхождениям с Node.js в специфических
случаях.
Проблема двойных
экземпляров модулей
Типичная проблема при активных symlinks:
- пакет A зависит от
react
- пакет B (в монорепозитории) также зависит от
react
- оба пакета подключены через symlink в один проект
Без корректной настройки может возникнуть ситуация:
- два разных пути к одному и тому же пакету
- два экземпляра React в рантайме
- нарушение правил хуков React
- некорректное состояние контекста
esbuild в режиме стандартного резолвинга пытается объединить такие
зависимости, используя физический путь, что снижает риск дублирования,
но может расходиться с поведением Node.js при запуске.
Опция preserveSymlinks
Параметр preserveSymlinks изменяет стратегию разрешения
модулей и определяет, как esbuild обрабатывает символические ссылки при
сборке.
Основное поведение
При включении:
- символические ссылки не разворачиваются в реальный
путь
- модуль считается находящимся по пути symlink
- сохраняется оригинальная структура импортов
При выключении (по умолчанию):
- выполняется
realpath-разрешение
- symlink заменяется на физический путь
- возможна дедупликация зависимостей
Семантика
preserveSymlinks в контексте Node.js
Параметр исторически связан с поведением Node.js
(--preserve-symlinks):
- влияет на кеш модулей
- изменяет идентичность
require()
- сохраняет различие между ссылкой и реальным файлом
esbuild реализует аналогичную модель на этапе сборки, а не
выполнения, что делает последствия более «статическими».
Влияние на разрешение
импортов
При preserveSymlinks: true:
- импорт через symlink сохраняет свой путь
- два одинаковых пакета из разных мест могут считаться разными
модулями
- возрастает вероятность дублирования зависимостей
При preserveSymlinks: false:
- все пути схлопываются в физические
- одинаковые зависимости чаще объединяются
- уменьшается размер бандла
Пример монорепозитория
Структура:
repo/
packages/
ui/
index.js
app/
index.js
app использует ui через workspace
symlink.
Без preserveSymlinks
ui резолвится в физический путь
packages/ui
- esbuild объединяет зависимости
- общий граф модулей становится единым
С preserveSymlinks
ui остаётся виртуально связанным через symlink
- путь интерпретируется как отдельная сущность
- возможны различия в идентификации модулей внутри графа
Влияние на
кеширование и производительность
esbuild активно использует кеширование результатов резолвинга.
Без preserveSymlinks
- выше вероятность попадания в кеш
- меньше уникальных ключей модулей
- быстрее повторные сборки
С preserveSymlinks
- увеличивается количество уникальных путей
- кеш фрагментируется
- возможен рост времени сборки в больших монорепозиториях
Совместимость с
инструментами экосистемы
pnpm
pnpm активно использует symlink-структуры:
- node_modules строится через виртуальные ссылки
- зависимости строго изолированы
При работе с esbuild:
preserveSymlinks: false чаще даёт более «схлопнутый»
граф
true может быть ближе к реальной runtime-структуре
Node
Yarn workspaces
- похожая модель symlinks
- чаще ожидается единый граф зависимостей
- preserveSymlinks обычно не требуется
npm link
При локальной разработке пакетов:
preserveSymlinks: true может быть полезен для симуляции
поведения linked-пакета
false приводит к «слиянию» с реальной копией
пакета
Влияние на ESM и CommonJS
ESM
- идентичность модуля зависит от URL/пути
- symlink влияет на уникальность импорта
- возможны разные экземпляры одного модуля
CommonJS
- зависит от
require-кеша
- Node может различать или объединять модули в зависимости от режима
запуска
- esbuild лишь формирует результат, но не управляет runtime-кешем
Типичные
ошибки при использовании preserveSymlinks
Дублирование состояния
Особенно критично для:
- React
- Vue (реактивность)
- state management библиотеки
Причина:
- один пакет загружен дважды из-за разных путей
Несовпадение окружений
Сборка и runtime ведут себя по-разному:
- esbuild с preserveSymlinks
- Node без preserveSymlinks
Это приводит к:
- различию в поведении импортов
- трудноуловимым багам
Ломание tree-shaking
При различной идентичности модулей:
- оптимизатор не может объединить экспорт
- увеличивается размер бандла
Рекомендованные сценарии
применения
preserveSymlinks: false
- стандартные SPA-проекты
- приложения без сложных symlink-структур
- максимальная оптимизация бандла
- монорепозитории с единым hoisting-резолвингом
preserveSymlinks: true
- разработка через npm link
- плагины и расширяемые архитектуры
- сценарии, где важно сохранить «виртуальные границы» пакетов
- диагностика проблем с резолвингом модулей
Внутренняя логика резолвинга
В упрощённом виде поведение esbuild можно описать так:
Эта разница определяет фундаментальную модель идентичности
модулей.
Взаимодействие с
другими опциями esbuild
bundle
При включённом bundling:
- влияние symlinks усиливается
- граф модулей строится глобально
splitting
При code splitting:
- разные чанки могут содержать дубли при preserveSymlinks
- увеличивается риск повторных зависимостей
При Node-ориентированной сборке:
- preserveSymlinks становится ближе к runtime-поведению
- но всё ещё не полностью идентично Node.js loader’у
Итоговая модель поведения
Поведение символических ссылок в esbuild определяется балансом
между:
- физической структурой файловой системы
- виртуальной структурой монорепозитория
- необходимостью дедупликации модулей
- соответствием Node.js runtime
Опция preserveSymlinks переключает интерпретацию между
двумя моделями:
- физическая модель (realpath) — оптимизация и
дедупликация
- виртуальная модель (symlink-aware) — сохранение
структуры проектов