Раздача общих пакетов без 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 не навязывает единый способ упаковки, а адаптируется к типу зависимости и контексту исполнения.