Монорепозиторий строится вокруг идеи единого хранилища для нескольких приложений и пакетов, где общие зависимости и код переиспользуются без дублирования. В экосистеме JavaScript это особенно актуально для проектов с несколькими фронтенд-приложениями, библиотеками компонентов и общими утилитами.
Vite в такой архитектуре выполняет роль инструмента разработки и сборки, который должен корректно работать как с локальными пакетами, так и с внешними зависимостями, не теряя скорости dev-сервера и не ломая резолв модулей.
Ключевая сложность монорепозитория — корректное связывание workspace-пакетов, поддержка алиасов и предотвращение дублирования зависимостей, которые могут приводить к конфликтам React/Vue или другим runtime-ошибкам.
Типичная структура с Vite выглядит следующим образом:
repo/
apps/
admin/
web/
packages/
ui/
utils/
config/
package.json
pnpm-workspace.yaml
apps — конечные приложения, которые собираются и
деплоятсяpackages — переиспользуемые модулиconfig — общие конфигурации (eslint, tsconfig, vite
config)Такое разделение позволяет изолировать бизнес-логику приложений и переиспользовать инфраструктурный код.
Монорепозиторий с Vite требует workspace-поддержки. На практике используются три варианта:
Наиболее предсказуемая и строгая модель зависимостей:
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
Особенность: отсутствует классический hoisting, зависимости строго изолированы.
Подходит для проектов, где важен привычный npm-опыт:
{
"workspaces": [
"apps/*",
"packages/*"
]
}
Минимальный набор возможностей, но достаточный для простых монореп.
Каждое приложение в apps/* содержит собственный
Vite-проект:
apps/web/package.json
apps/admin/package.json
Пример зависимости:
{
"name": "web",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"vue": "^3.0.0"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
Важно: Vite устанавливается в каждом приложении, а не на уровне всего монорепозитория, чтобы избежать конфликтов версий плагинов.
Внутренние пакеты подключаются через workspace-ссылки:
{
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/utils": "workspace:*"
}
}
Vite по умолчанию не всегда корректно резолвит такие зависимости, поэтому требуется настройка.
В каждом приложении Vite необходимо явно указать алиасы:
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@repo/ui': path.resolve(__dirname, '../. ./packages/ui/src'),
'@repo/utils': path.resolve(__dirname, '../. ./packages/utils/src')
}
}
})
Это позволяет:
Ключевой файл — общий tsconfig.base.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@repo/ui": ["packages/ui/src"],
"@repo/utils": ["packages/utils/src"]
}
}
}
Каждое приложение наследует конфигурацию:
{
"extends": "../. ./tsconfig.base.json",
"compilerOptions": {
"outDir": "dist"
}
}
Важно синхронизировать paths в TypeScript и
alias в Vite, иначе возможны расхождения между dev и
build.
Vite использует ESM-ориентированный резолвер, поэтому в монорепозитории возникают типичные проблемы:
Если react установлен в каждом пакете отдельно, возможны
ошибки хуков.
Решение:
peerDependencies в библиотеках{
"peerDependencies": {
"react": "^18.0.0"
}
}
Vite пред-бандлит зависимости через esbuild:
export default defineConfig({
optimizeDeps: {
include: ['react', 'react-dom']
}
})
В монорепозитории часто требуется явно указывать зависимости, иначе dev-сервер будет медленным или нестабильным.
export default defineConfig({
optimizeDeps: {
exclude: ['@repo/ui', '@repo/utils']
}
})
Пакеты в packages/* часто собираются отдельно через Vite
library mode:
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
formats: ['es']
},
rollupOptions: {
external: ['react', 'vue']
}
}
})
Важно:
external предотвращает включение зависимостей в
бандлes предпочтителен для монорепыВ монорепозитории часто используются UI-библиотеки, которые импортируют CSS:
import '@repo/ui/styles.css'
Подходит для уменьшения глобальных конфликтов.
build: {
cssCodeSplit: true
}
Для ускорения сборок используется task runner:
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false
}
}
}
Vite в этом случае отвечает за сборку конкретных пакетов, а orchestrator управляет зависимостями.
При запуске нескольких приложений одновременно возникают вопросы:
server: {
port: 3000
}
Каждое приложение должно иметь уникальный порт.
Чтобы изменения в packages/ui обновляли приложения:
alias на src)В больших монорепозиториях часто выносят базовый конфиг:
packages/config/vite.base.ts
import { defineConfig } from 'vite'
export const baseViteConfig = defineConfig({
resolve: {
dedupe: ['react', 'vue']
}
})
И используют в приложениях:
import { baseViteConfig } from '@repo/config/vite.base'
export default defineConfig({
...baseViteConfig
})
Причина: пакет подключён как готовый build, а не исходники.
Решение: alias на src.
Причина: разные копии react в node_modules.
Решение:
Причина: слишком много зависимостей в optimizeDeps.
Решение:
Причина: разные alias/path конфигурации.
Решение: единый tsconfig.base.json.
При росте монорепозитория Vite остаётся эффективным при соблюдении нескольких принципов:
Монорепозиторий с Vite становится предсказуемым только при строгом контроле зависимостей и единых правилах импорта между пакетами.