Поддержка pnpm workspaces

## Особенности работы Vite в pnpm workspaces ### Архитектура pnpm workspaces и влияние на Vite Модель pnpm workspace основана на строгих символических ссылках и изолированной структуре зависимостей. В отличие от npm и Yarn classic, pnpm формирует содержимое `node_modules` через контент-адресуемое хранилище и создаёт жёстко контролируемые symlink-связи между пакетами. В контексте монорепозитория это означает: * каждый пакет получает собственное логическое окружение зависимостей * общие зависимости физически не дублируются * внутренние пакеты workspace подключаются через symlink * структура `node_modules` становится вложенной и предсказуемой Vite изначально ориентирован на ESM-окружение и работу через нативные модули браузера и Node.js, поэтому поведение symlink-структуры pnpm напрямую влияет на: * резолв модулей * prebundle оптимизацию * HMR в dev-сервере * корректность алиасов * обработку peerDependencies --- ### Резолвинг модулей в workspace-среде В pnpm workspace каждый пакет может ссылаться на другие пакеты через протокол: ```json { "dependencies": { "@app/shared": "workspace:*" } } ``` Это создаёт symlink внутри `node_modules`, который указывает на локальный пакет. Проблема возникает на уровне резолва: * Node.js видит symlink как отдельный путь * Vite может воспринимать пакет как внешний модуль * HMR может дублировать инстансы зависимостей Для корректной работы важно учитывать: * одинаковые версии зависимостей во всех workspace-пакетах * единый hoisting policy pnpm * согласованность peerDependencies --- ### Особенности dev-сервера Vite в монорепозитории Dev-сервер Vite использует нативный ESM резолв и оптимизацию зависимостей через esbuild prebundle. В pnpm workspace это приводит к следующим эффектам: #### 1. Symlink-разыменование Vite по умолчанию старается резолвить зависимости через реальный путь: * symlink → realpath * пакет становится частью физического дерева проекта * увеличивается риск дублирования модулей Настройки, влияющие на поведение: ```js resolve: { preserveSymlinks: false } ``` При включении `preserveSymlinks: true`: * модули сохраняют оригинальные пути * упрощается работа monorepo * уменьшается риск двойных React/Vue инстансов --- #### 2. Корневой контекст проекта В pnpm workspace корень репозитория часто отличается от корня Vite-приложения. Структура: ``` repo/ packages/ app/ ui/ shared/ ``` Если Vite запускается из `packages/app`, он должен: * видеть workspace-пакеты * корректно резолвить ссылки вверх по дереву * учитывать общий `node_modules` Проблема решается через: * запуск dev-сервера из пакета * либо настройку root --- ### Оптимизация зависимостей (optimizeDeps) Vite активно предсобирает зависимости через esbuild. В pnpm workspace это особенно критично из-за большого количества локальных пакетов. Типичные проблемы: * зависимость доступна через symlink, но не попадает в prebundle * дублирование ESM/CJS версий * медленный cold start dev-сервера Решения: #### Явное включение workspace-пакетов ```js optimizeDeps: { include: [ '@app/shared', '@app/ui' ] } ``` #### Исключение проблемных зависимостей ```js optimizeDeps: { exclude: ['some-linked-package'] } ``` --- ### Дедупликация зависимостей pnpm строго изолирует версии, но в monorepo это может привести к нескольким экземплярам одной библиотеки. Критичные случаи: * React + React DOM * Vue runtime * Zustand / Redux stores * singleton API clients Решение через Vite: ```js resolve: { dedupe: ['react', 'react-dom'] } ``` Это заставляет Vite использовать единый инстанс модуля по всему графу зависимостей. --- ### Работа с alias в workspace В pnpm workspace часто требуется централизованный доступ к пакетам. Пример: ```js resolve: { alias: { '@shared': '/packages/shared/src' } } ``` Однако более устойчивый подход — использование workspace-ссылок: * `workspace:*` * прямой импорт пакетов * избегание абсолютных путей --- ### TypeScript и синхронизация путей В монорепозиториях pnpm почти всегда используется единый `tsconfig.base.json`. Проблемы: * Vite резолвит пути иначе, чем TypeScript * `paths` не совпадают с реальной структурой symlink * IDE и runtime расходятся Типичная конфигурация: ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@shared/*": ["packages/shared/src/*"] } } } ``` Важно, чтобы Vite и TypeScript использовали одинаковую карту алиасов: ```js resolve: { alias: { '@shared': '/packages/shared/src' } } ``` --- ### Dev HMR в workspace Hot Module Replacement в pnpm workspace чувствителен к: * дублированию зависимостей * неправильным symlink-границам * различию путей модулей Типовые проблемы: #### 1. Два инстанса состояния Причина: * пакет UI и приложение используют разные копии store-библиотеки #### 2. Потеря HMR Причина: * файл находится вне `server.fs.allow` Решение: ```js server: { fs: { allow: ['..'] } } ``` --- ### Build в монорепозитории Build процесс Vite в pnpm workspace должен учитывать: * отдельные сборки каждого пакета * общие зависимости * отсутствие пересборки всего дерева Подходы: #### 1. Независимые сборки Каждый пакет имеет свой `vite.config.js`. #### 2. Общий shared build config ```js import { defineConfig } from 'vite' export default defineConfig({ build: { sourcemap: true, rollupOptions: { external: ['react', 'react-dom'] } } }) ``` --- ### Проблемы с peerDependencies pnpm строго соблюдает peerDependencies, что может привести к ошибкам: * отсутствующая зависимость * несовместимая версия React/Vue * конфликт версий в workspace Vite при этом не всегда явно сообщает о причине, так как ошибка может возникать на уровне esbuild или Rollup. Типичная диагностика: * проверка `pnpm list` * анализ дублирующихся пакетов * проверка hoisting --- ### Hoisting стратегия pnpm и влияние на Vite pnpm поддерживает разные стратегии hoist: * public hoist * shared dependencies * isolated node_modules Влияние на Vite: * при слабом hoisting увеличивается глубина резолва * оптимизация deps становится медленнее * возрастает риск конфликтов ESM/CJS Рекомендуемые настройки: ``` shamefully-hoist=false ``` или точечный hoist для проблемных пакетов. --- ### Частые ошибки в pnpm + Vite #### Дублирование React Симптом: * hooks error * invalid hook call Причина: * две копии React в workspace #### Нераспознанные workspace пакеты Симптом: * Module not found Причина: * пакет не попал в optimizeDeps #### Медленный dev server Причина: * слишком много linked пакетов без исключений --- ### Стратегия устойчивой архитектуры Монорепозиторий с pnpm и Vite требует стабильной структуры: * единый root конфиг Vite при необходимости * синхронизация TypeScript paths * контроль dedupe зависимостей * явное управление optimizeDeps * минимизация cross-package side effects * строгая версияная согласованность Стабильность достигается не настройкой одного параметра, а балансом между: * резолвингом Node.js * графом модулей Vite * физической структурой pnpm symlinks