Раздача общих пакетов без pre-bundling
### Модель работы зависимостей в Vite и роль pre-bundling
Vite в режиме разработки опирается на нативные ES-модули в браузере и не пересобирает весь граф зависимостей через бандлер на старте проекта. Вместо этого применяется отдельный этап подготовки зависимостей — pre-bundling, который выполняется через esbuild и называется оптимизацией зависимостей (`optimizeDeps`).
Pre-bundling нужен в первую очередь для двух задач: ускорение холодного старта и приведение зависимостей к формату, который корректно исполняется в браузере через ESM. Однако не все пакеты требуют этой стадии. Часть библиотек уже поставляется в виде ESM-модулей с корректным `exports`, и их можно отдавать напрямую без промежуточной упаковки.
### Когда Vite не выполняет pre-bundling
Vite старается не трогать зависимости, которые уже совместимы с ESM и не требуют трансформации. В таких случаях модули:
* импортируются напрямую браузером через native ESM
* не попадают в кеш предварительной сборки
* не обрабатываются esbuild на этапе dev server optimization
Решение принимается на основании анализа `package.json` зависимостей, структуры файлов и наличия явных признаков необходимости трансформации (например, CommonJS-формат или глубокие импортируемые пути).
Особую роль играет поле `exports` в `package.json`. Если пакет строго описывает экспортируемые точки входа и не использует legacy-структуру, Vite с большей вероятностью пропустит pre-bundling.
### Механизм оптимизации зависимостей
При запуске dev-сервера Vite анализирует импортируемые модули и формирует список зависимостей для предварительной сборки. Эти зависимости:
* преобразуются в ESM-совместимый формат
* объединяются в оптимизированные бандлы
* кешируются в `node_modules/.vite`
Цель — уменьшить количество HTTP-запросов и ускорить резолвинг модулей в браузере.
Однако этот механизм не обязателен для всех пакетов. Его можно частично или полностью отключать.
### Раздача пакетов без pre-bundling
Сценарий, при котором общие пакеты раздаются без предварительной сборки, возникает в нескольких случаях:
#### 1. ESM-native зависимости
Если пакет изначально написан в ESM и не содержит CommonJS-веток, Vite может отдавать его напрямую:
* без объединения в chunk
* без преобразования через esbuild
* с использованием стандартного module resolution браузера
Такие пакеты становятся частью нативного графа импортов.
#### 2. Явное исключение из optimizeDeps
Поведение можно контролировать через конфигурацию:
```js
export default {
optimizeDeps: {
exclude: ['some-esm-package']
}
}
```
Исключённые зависимости не попадают в pre-bundling и загружаются напрямую. Это часто используется, когда пакет:
* уже оптимизирован
* ломается при преобразовании esbuild
* содержит сложные side effects при бандлинге
#### 3. Монорепозитории и локальные пакеты
В монорепозиториях (pnpm, yarn workspaces) локальные пакеты часто рассматриваются как исходный код, а не как внешние зависимости. В этом случае Vite может:
* обрабатывать их через HMR как часть приложения
* не включать в dependency pre-bundling
* разрешать импорты напрямую через symlink
Ключевым фактором становится корректная настройка `resolve.preserveSymlinks` и структура `exports` внутри пакета.
### Поведение Vite при отсутствии pre-bundling
Когда пакет отдаётся без предварительной сборки, изменяется модель загрузки:
* браузер самостоятельно резолвит модули
* каждый импорт соответствует отдельному HTTP-запросу
* отсутствует агрегация модулей в оптимизированные чанки
Это повышает прозрачность графа зависимостей, но может увеличивать количество запросов при сложных деревьях импортов.
### Проблемы совместимости CommonJS
Наиболее частая причина forced pre-bundling — наличие CommonJS-кода. Такие модули требуют трансформации:
* `require` заменяется на ESM-импорты
* динамические `module.exports` переписываются
* добавляется обёртка для корректного interop
Если CommonJS пакет исключить из pre-bundling, возможны ошибки:
* `require is not defined`
* некорректный default export
* потеря tree-shaking
Поэтому Vite принудительно оптимизирует такие зависимости.
### Дублирование пакетов и изоляция модулей
При работе без pre-bundling возрастает риск появления нескольких экземпляров одной библиотеки. Это особенно критично для:
* React
* Vue
* Zustand / Redux-подобных сторах
Причина — разные пути импорта в монорепозитории или неоднозначный резолвинг symlink-зависимостей.
Для контроля используется:
* `resolve.dedupe`
* единая версия зависимостей в workspace
* явная фиксация peerDependencies
### SSR и раздача зависимостей
В режиме SSR поведение отличается: зависимости часто исключаются из бандла серверной сборки:
```js
export default {
ssr: {
external: ['some-package']
}
}
```
В этом случае пакет не проходит через Vite pipeline и подгружается напрямую Node.js, минуя оптимизацию.
### CDN и внешняя раздача модулей
Отдельный сценарий — полное исключение pre-bundling в пользу CDN. Тогда зависимости могут подключаться как:
* import maps
* ESM CDN (например, esm.sh, skypack-подобные решения)
* внешние URL вместо node_modules
В таком режиме Vite выступает только как orchestrator приложения, а не как сборщик зависимостей.
Особенность такого подхода — полная отказоустойчивость от локального кеша, но зависимость от сети и версии CDN.
### Типичные архитектурные паттерны
Раздача общих пакетов без pre-bundling обычно встречается в следующих структурах:
* библиотеки с чистым ESM и строгим `exports`
* микрофронтенды с разделяемыми зависимостями
* monorepo, где пакеты являются частью одного графа сборки
* системы с CDN-first загрузкой модулей
В таких системах важна согласованность версий и единая стратегия резолвинга, иначе поведение runtime становится неоднозначным.
### Контроль границ оптимизации
Ключевая идея заключается в том, что pre-bundling в Vite — не обязательный слой, а оптимизационный. Управление границами происходит через:
* `optimizeDeps.include`
* `optimizeDeps.exclude`
* `ssr.external`
* `resolve.alias`
* структуру `exports` в пакетах
Корректная настройка позволяет разделить зависимости на три категории:
* полностью предсобранные
* нативно ESM-раздаваемые
* внешние (runtime/CDN/Node)
Так формируется гибкая модель загрузки, где Vite не навязывает единый способ упаковки, а адаптируется к типу зависимости и контексту исполнения.