Monorepo
Monorepo(单仓多包)= 一个 git 仓库管理多个项目/包。与之对应的是 Polyrepo(多仓多包)。
# Monorepo
my-org/
├─ apps/
│ ├─ web/ # 主应用
│ └─ admin/ # 后台
└─ packages/
├─ ui/ # 组件库
├─ utils/ # 工具
└─ sdk/ # SDK
# Polyrepo
my-org/
├─ web/ # 独立仓库
├─ admin/ # 独立仓库
├─ ui/ # 独立仓库
└─ utils/ # 独立仓库
优缺点
| 维度 | Monorepo | Polyrepo |
|---|---|---|
| 代码复用 | ✅ 内部包直接引用 | ⚠️ 走 npm 发包,依赖链路长 |
| 依赖管理 | ✅ 顶层统一装,节省磁盘 | ❌ 每个仓都装一遍 |
| 跨包重构 | ✅ 原子提交 | ❌ 要发 PR 到多个仓 |
| 版本管理 | ⚠️ 复杂(changesets 解决) | ✅ 各包独立 |
| 权限控制 | ❌ 所有人能看所有包 | ✅ 仓库级权限 |
| CI 速度 | ⚠️ 需要增量构建(turborepo) | ✅ 各自独立 |
| 适用规模 | 中小团队、多包复用 | 大厂、多业务线隔离 |
经验法则:包之间有 30% 以上复用率就值得 monorepo。
适用场景
- ✅ 组件库 + 业务应用(Vercel、字节、京东内部都在用)
- ✅ 工具库 + 多个使用方
- ✅ 同一团队维护多个紧密关联的项目
- ❌ 业务完全独立、团队也不重叠的多产品(建议多仓)
二、主流方案对比
1. 包管理 + Workspace
| 方案 | 特点 | 推荐度 |
|---|---|---|
| pnpm workspace | 硬链接节省磁盘、速度快、原生支持 | ⭐⭐⭐⭐⭐ |
| yarn workspaces | yarn 1.x 支持;v2+ berry 改用 PnP(争议) | ⭐⭐⭐ |
| npm workspaces | npm 7+ 内置支持,够用但弱 | ⭐⭐ |
pnpm 是 2024+ 事实标准,monorepo 场景几乎都是它。
2. 构建编排工具
| 方案 | 定位 | 特点 |
|---|---|---|
| Turborepo | 任务编排 + 远程缓存 | Vercel 出品、配置简单、增量构建 |
| Nx | 完整工程化平台 | nrwl 出品、CLI 强、依赖图分析 |
| Lerna | 版本管理 + 发包 | 已被 nx 团队接管、单独用变少 |
| Rush | 大型 monorepo 工具 | 微软出品、配置复杂、企业级 |
2024+ 推荐:
pnpm + turborepo(中小团队);pnpm + nx(大型企业)。
3. 版本管理 + Changelog
| 方案 | 特点 |
|---|---|
| Changesets | 现代推荐,配置文件驱动 |
| Lerna version | 老牌方案,已被 nx 整合 |
| 手动 | 小项目勉强能用 |
Changesets 是当前事实标准,pnpm / nx / turborepo 都推荐配合它。
三、推荐组合
| 场景 | 推荐 |
|---|---|
| 个人 / 小团队(< 10 人) | pnpm workspace + Changesets |
| 中小团队(10~50 人) | pnpm + Turborepo + Changesets |
| 大厂 / 多业务线 | pnpm + Nx + Changesets |
| 微软系 / 企业级 | Rush |
四、pnpm workspace 实战
1. 初始化
mkdir my-monorepo && cd my-monorepo
pnpm init
2. 配置 workspace
pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
- "!**/test/**"
pnpm 会自动把
apps/*和packages/*下的每个目录识别成独立的 workspace 包。
3. 目录结构
my-monorepo/
├─ apps/
│ ├─ web/ # 主应用(package.json: "web")
│ └─ admin/ # 后台(package.json: "admin")
├─ packages/
│ ├─ ui/ # 组件库(package.json: "@my/ui")
│ ├─ utils/ # 工具(package.json: "@my/utils")
│ └─ sdk/ # SDK(package.json: "@my/sdk")
├─ pnpm-workspace.yaml
├─ package.json
├─ .npmrc
└─ pnpm-lock.yaml
4. 跨包引用(workspace protocol)
在 apps/web/package.json:
{
"dependencies": {
"@my/ui": "workspace:*",
"@my/utils": "workspace:*"
}
}
workspace:*:始终使用工作区最新代码,不发包也能用workspace:^1.0.0:发包后的版本约束(用得少)
执行 pnpm install 后,会自动在 apps/web/node_modules/@my/ui 创建符号链接指向 packages/ui。
// apps/web/src/App.tsx
import { Button } from "@my/ui";
修改
packages/ui源码,apps/web立即生效(HMR 支持需要看工具链配置)。
5. 全局安装共享依赖
把所有包都用到的依赖(如 react)装到根目录,避免重复安装。
根 package.json:
{
"devDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.0.0"
}
}
子包如果要覆盖版本:
// packages/ui/package.json
{
"dependencies": {
"react": "^18.3.0" // 覆盖根的版本
}
}
6. 常用命令
# 在指定包运行命令
pnpm --filter web dev
pnpm --filter @my/ui build
# 所有包执行
pnpm -r build
pnpm -r --parallel test
# 给指定包加依赖
pnpm --filter web add axios
pnpm --filter @my/ui add -D vitest
# 跑某个包及其依赖
pnpm --filter web... build # web 及其依赖包
pnpm --filter ...web build # web 及依赖它的包
五、Turborepo 实战
1. 安装
pnpm add -D turbo
2. 配置
turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false
},
"lint": {
"outputs": []
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"clean": {
"cache": false
}
}
}
关键点:
dependsOn: ["^build"]:先构建所有依赖outputs:构建产物路径,用于缓存cache: false:不缓存的任务(dev server)
3. 根 package.json scripts
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --parallel",
"lint": "turbo run lint",
"test": "turbo run test",
"clean": "turbo run clean && rm -rf node_modules"
}
}
4. 远程缓存(团队提速)
# 登录 Vercel
npx turbo login
# 关联项目
npx turbo link
开启远程缓存后,同一团队成员跑过的任务会命中缓存,跳过执行。
六、版本管理:Changesets
1. 初始化
pnpm add -D @changesets/cli
pnpm changeset init
2. 工作流
# 1. 改完代码后,告诉 changeset 改了哪些包
pnpm changeset
# 交互式选择:哪些包、什么类型(major/minor/patch)、写 changelog
# 2. 版本自动 bump + 写 CHANGELOG.md
pnpm changeset version
# 3. 发包
pnpm changeset publish
3. CI 集成
.github/workflows/release.yml:
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm build
- run: pnpm changeset version
- run: pnpm changeset publish
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: pnpm/action-setup@v4
- run: git push --follow-tags
七、避坑指南
- 不要让子包独立装大版本:react、vue 等核心依赖全仓统一版本,避免运行时多实例
- 避免循环依赖:a 依赖 b、b 依赖 a,会导致构建死循环
- 避免
workspace:*发包:发包前要改回具体版本号(用pnpm changeset version自动处理) - 锁定 Node 版本:根目录加
.nvmrc+engines,否则不同环境行为不一致 - 公共依赖 hoist:用
.npmrc的public-hoist-pattern[]把所有子包共享的工具提到根 - CI 用
--frozen-lockfile:保证 lock 文件不被修改 - 避免巨型 monorepo:单个仓超过 50 个包就该考虑拆分
八、面试高频
Q:Monorepo 最大的好处是什么? A:代码复用 + 跨包重构原子化。Polyrepo 改一个工具库要在多个仓提 PR,链路长;Monorepo 一次 commit 改完所有调用方。
Q:pnpm 为什么比 npm/yarn 适合 monorepo? A:pnpm 用硬链接 + 内容寻址存储,所有子包共享同一份依赖磁盘占用,速度比 npm/yarn 快 2~3 倍。同时
workspace:*原生支持。Q:Turborepo 怎么做到增量构建? A:基于 hash 的缓存:每次跑任务前计算输入文件 + 环境变量的 hash,命中本地/远程缓存就跳过执行。配合 Vercel 远程缓存,团队成员复用彼此的构建结果。
Q:Lerna 现在还有用吗? A:单独用 Lerna 的人越来越少,基本被 Changesets 取代版本管理部分。Lerna 团队(nrwl)现在主推 Nx。
Q:什么时候不应该上 Monorepo? A:业务完全独立 + 团队不重叠 + 跨包复用率低 + 需要细粒度权限控制。强行 monorepo 反而拖慢效率。