Vite 与 Monorepo 多应用架构:pnpm workspace、共享包与构建隔离

系统讲解在 Monorepo 架构中使用 Vite 的最佳实践:pnpm workspace 搭建、共享包(UI 库/工具包)开发与发布、依赖预构建与 resolve.dedupe、多应用构建隔离与缓存、以及典型的 Monorepo Vite 项目结构。

引言

当组织内有多个前端应用(管理后台、官网、营销页),且它们共享组件库、工具函数、类型定义时,Monorepo 是公认的工程解。但 Monorepo + Vite 组合也有自己的坑:共享包是「源码」还是「产物」?依赖预构建如何避免重复?多应用并行构建如何隔离与缓存?

本文从 pnpm workspace 搭建讲起,覆盖共享包的两种形态(源码直引 vs 产物构建)、依赖预构建与 resolve.dedupe 去重、多应用构建隔离与 CI 缓存、以及一个完整的 Monorepo 项目结构模板。读懂本文,你将能自信地为一个多应用团队搭建可扩展的 Vite Monorepo。

前置:https://plumephp.com/vite-config-guide/(resolve/build 配置)与 https://plumephp.com/vite-build-optimization/(构建优化基础)。


目录


1. 为什么 Monorepo 适合 Vite 项目

1.1 痛点对比

方案共享代码版本同步开发体验
多仓库(multi-repo)靠 npm 包发布版本漂移改包要发版
单仓多目录硬编码拷贝不可控重复代码
Monorepoworkspace 直连同步锁定源码联动 + HMR

1.2 Vite 与 Monorepo 的契合

  • 原生 ESM + 依赖预构建:对 workspace 内共享包的源码加载友好。
  • 别名与 resolve 灵活:可让不同应用解析到共享包的源码或产物。
  • 构建独立:每个应用有独立 vite.config.ts,互不干扰。

1.3 不适合的场景

  • 团队边界强、技术栈分歧大的组织,Monorepo 会放大冲突。
  • 共享包极度稳定、极少改动时,直接发 npm 包更省事。

2. pnpm workspace 搭建

2.1 初始化

mkdir my-monorepo && cd my-monorepo
pnpm init
# 创建 pnpm-workspace.yaml

2.2 pnpm-workspace.yaml

packages:
  - 'apps/*'          # 应用
  - 'packages/*'      # 共享包

2.3 创建应用与共享包

# 应用
pnpm create vite apps/admin --template react-ts
pnpm create vite apps/web --template react-ts

# 共享包(手动创建 packages/ui)
mkdir -p packages/ui/src
// packages/ui/package.json
{
  "name": "@my/ui",
  "version": "0.0.0",
  "main": "src/index.ts",       // 源码直引
  "types": "src/index.ts",
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}

2.4 在应用里引入共享包

pnpm --filter @my/admin add @my/ui@workspace:*
// apps/admin/src/App.tsx
import { Button } from '@my/ui'

3. 共享包:源码直引 vs 产物构建

3.1 两种形态对比

形态做法优点缺点
源码直引main 指向 src/index.ts开发零构建、HMR 直接生产需 Vite 转译源码
产物构建先 pnpm build 生成 dist生产更稳改包要先构建、HMR 延迟

3.2 源码直引时的配置

// apps/admin/vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  resolve: {
    // 让 Vite 直接处理共享包源码中的 TS/JSX
    alias: {
      '@my/ui': '/packages/ui/src/index.ts',
    },
  },
  build: {
    commonjsOptions: {
      include: [/packages/, /node_modules/],
    },
  },
})

3.3 共享包源码中的依赖

源码直引时,共享包的依赖需在应用侧可解析。pnpm 的 workspace 提升 + peerDependencies 能覆盖大部分场景;必要时用 resolve.dedupe 强制版本统一。


4. 依赖预构建与去重

4.1 预构建去重的意义

Monorepo 中,不同应用可能安装同一依赖的不同版本,导致:

同一库打多份 -> 体积膨胀
React 两份实例 -> hooks 状态断裂

4.2 resolve.dedupe

// 各应用 vite.config.ts
export default defineConfig({
  resolve: {
    dedupe: ['react', 'react-dom'],
  },
})

4.3 optimizeDeps 与共享包

export default defineConfig({
  optimizeDeps: {
    include: ['react', 'react-dom', '@my/ui'],  // 预构建共享包
    exclude: ['@my/ui'],                        // 或排除以走源码
  },
})
配置场景
include明确告知哪些依赖要预构建
exclude让源码直引的共享包跳过预构建
dedupe强制统一依赖版本

5. 多应用构建隔离与缓存

5.1 各自独立构建

// 根 package.json
{
  "scripts": {
    "dev:admin": "pnpm --filter @my/admin dev",
    "dev:web": "pnpm --filter @my/web dev",
    "build:all": "pnpm -r --filter './apps/*' build"
  }
}

5.2 构建产物隔离

apps/admin/dist/   # 管理后台产物
apps/web/dist/     # 官网产物

5.3 缓存策略

# 用 pnpm 的 build 缓存
pnpm --filter @my/ui build
# 或配合 CI 缓存 node_modules 与 vite 缓存
export default defineConfig({
  cacheDir: '.vite-cache',   // 自定义缓存目录(默认 node_modules/.vite)
})

6. 典型 Monorepo 项目结构

6.1 完整目录

my-monorepo/
├── apps/
│   ├── admin/               # 管理后台
│   │   ├── src/
│   │   └── vite.config.ts
│   └── web/                 # 官网
│       ├── src/
│       └── vite.config.ts
├── packages/
│   ├── ui/                  # 共享 UI 组件库
│   │   └── src/
│   ├── utils/               # 共享工具函数
│   └── types/               # 共享类型定义
├── pnpm-workspace.yaml
├── package.json
└── tsconfig.base.json       # 共享 TS 配置

6.2 tsconfig.base.json 共享

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@my/ui": ["packages/ui/src/index.ts"]
    }
  }
}

6.3 共享包的构建配置(产物模式)

// packages/ui/vite.config.ts(库模式)
import { defineConfig } from 'vite'
import { resolve } from 'node:path'

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'MyUI',
      fileName: (format) => `my-ui.${format}.js`,
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
  },
})

7. 共享包的 HMR 联动

7.1 源码直引的天然 HMR

源码直引时,编辑共享包源码会触发引用它的应用热更新——这是 Monorepo 开发体验的核心优势。

# 将共享包 link 到各应用
pnpm --filter @my/admin link @my/ui

# 或直接 workspace:* 依赖,pnpm 自动建立链接

7.3 HMR 边界

编辑共享包 .ts 文件 -> 应用到 Vite transform -> HMR 触发
注意:共享包的 CSS 文件、JSON 同样会联动

8. CI 中的 Monorepo 构建

8.1 并行构建

# .github/workflows/build.yml
name: Build Monorepo
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with: { version: 9 }
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile
      # 共享包先构建
      - run: pnpm --filter @my/ui build
      # 应用并行构建
      - run: pnpm -r --filter './apps/*' build

8.2 依赖关系感知的构建

# pnpm 支持按拓扑顺序构建
pnpm -r build
# 会先构建被依赖的包,再构建依赖方

8.3 缓存优化

  • CI 缓存 node_modules(pnpm 默认)
  • 缓存 Vite 缓存目录 node_modules/.vite
  • 共享包产物缓存(无变化则跳过 rebuild)

9. 总结:Monorepo 的取舍与路径

9.1 一句话框架

workspace 连接(源码直引) -> 依赖去重(dedupe) -> 构建隔离(独立 config) -> CI 拓扑构建

9.2 关键决策点

决策选择理由
包管理器pnpmworkspace 原生支持 + 硬链接省空间
共享包形态开发期源码直引HMR 最佳体验
共享包发布产物构建(lib 模式)生产稳定、peer 清晰
依赖去重resolve.dedupe避免多实例与体积膨胀

9.3 自检清单

检查项是否掌握
能搭建 pnpm workspace☐
能解释源码直引 vs 产物构建☐
能配置 resolve.dedupe 去重☐
能设计 CI 拓扑构建☐
能处理共享包 HMR 联动☐

延伸阅读

  • https://plumephp.com/vite-config-guide/ — resolve/dedupe/optimizeDeps 详解
  • https://plumephp.com/vite-build-optimization/ — 共享包产物与构建优化
  • https://plumephp.com/vite-plugin-development/ — 为共享包写插件的扩展
  • pnpm workspace 文档 — 官方 workspace 参考
  • Vite 库模式文档 — 共享包构建配置

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「vite」更多文章

  1. Vite 静态资源与媒体资产处理:图片、字体、SVG 与 Worker
  2. Vite 环境变量与生产构建最佳实践:import.meta.env、构建模式与产物优化
  3. Vite 浏览器兼容与 Legacy 构建:build.target、Polyfill 与兼容插件