Поддержка 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