Monorepo架构深度解析:从Lerna到Turborepo的工具链进化
Monorepo 把多个相关项目统一在一个仓库管理,核心挑战是依赖管理、任务编排与构建缓存。 从 Lerna 手动编排到 Turborepo 增量缓存,工具链进化是手动控制到智能增量的缩影,面试常考它解决什么痛点与缓存原理。
一句话概括
Monorepo 将多个相关项目的代码统一在一个仓库管理,核心挑战在于依赖管理、任务编排和构建缓存。从 Lerna 的手动编排到 Turborepo 的自动缓存计算,工具链的进化史就是”从手动控制到增量智能”的缩影。
核心知识点
1. 为什么需要 Monorepo
Multi-Repo 的典型痛苦:修改一个 API → 改 A 库 → 提 PR → 合并 → 发布 npm → 更新 B 库的依赖 → 提另一个 PR。两个 PR、一次发布、多次 CI,一个改动要等一整天。
Monorepo 的价值:原子变更。所有相关包的改动在一次 PR 中完成,内部依赖的变更立即可见,不需要等待发布。
2. Turborepo 的 Task Graph
Turborepo 的核心创新是 dependsOn 定义任务依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // ← ^ 表示先构建上游依赖包
"outputs": ["dist/**"],
"cache": true
},
"test": {
"dependsOn": ["build"], // 先构建,再测试
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"dependsOn": [], // lint 不依赖其他任务
"cache": false
}
}
}
^build 的含义:packages/ui 的 build 依赖 packages/utils(ui 的依赖)先完成 build。Turborepo 自动按拓扑排序执行,同一层的并行跑:
1
2
3
4
5
6
# 构建所有包,自动拓扑排序
turbo run build
# 只构建 ui 包及其依赖
turbo run build --filter=@my-app/ui
# 只构建相比 main 分支有变更的包
turbo run build --filter=[main...HEAD]
3. 哈希缓存:没变就不跑
Turborepo 缓存的核心是计算输入哈希:
1
2
3
4
5
6
7
8
// 缓存 key 的组成(伪代码)
taskHash = sha256(
globHash('src/**/*'), // 源码文件内容哈希
depOutputHashes, // 依赖包构建输出的哈希
globHash(['tsconfig.json']), // 全局依赖
hashEnv(['NODE_ENV']), // 环境变量
task.command, // 命令本身
);
哈希没变 → 直接从缓存恢复 dist/ 目录(0ms)。一个 50 包 Monorepo,全量构建 10 分钟 → 增量构建 1-2 分钟。
4. 版本发布:Changesets
1
2
3
4
5
6
7
8
9
# 1. 开发完成后,创建 changeset(记录变更)
npx changeset
# 交互式:选择哪些包有变更 → 选择 major/minor/patch → 输入说明
# 2. CI 自动生成版本号 + CHANGELOG
npx changeset version
# 3. 发布到 npm
npx changeset publish
版本模式选项:
- 独立模式:每个包独立版本号(recommended)
- 固定模式:所有包统一版本号(如 React 家族)
- 链接模式:关联包保持版本关系
5. 三工具对比
| 工具 | 缓存 | 远程缓存 | 适用场景 |
|---|---|---|---|
| Lerna | ❌ 无 | ❌ | 简单工作流(已过时) |
| Turborepo | ✅ 哈希缓存 | ✅ Vercel RC | 大部分项目,上手快 |
| Nx | ✅ 哈希 + AST 依赖分析 | ✅ Nx Cloud | 依赖关系复杂,需要更多控制 |
Turborepo 更简单开箱即用,Nx 更强大(能分析源码 import 语句发现隐式依赖)。
其实你每天都在用
turbo run build第二次跑秒级完成:哈希缓存命中,直接从缓存恢复 dist 目录- 改了一个 utils 函数,只构建 ui 和 app:Turborepo 只跑受影响的包,不是全量 50 个
- pnpm workspace 的
workspace:*协议:开发时指向本地包,发布时自动替换为真实版本号 - PR 的 CI 只跑变更包的测试:
--filter=[main...HEAD]只测试你改过的包 - Changesets 自动生成 CHANGELOG:从变更记录推导语义版本号,不再手动维护
常见误解(FAQ)
❌ 误区 1:「Monorepo 就是把所有代码放在一个文件夹」
只放一起不做任务编排和缓存,Monorepo 是灾难:每次改动全量构建所有包。Turborepo/Nx 的增量构建和缓存才是 Monorepo 真正产生价值的地方。
❌ 误区 2:「Turborepo 和 Nx 功能重复,选一个就行」
两者思路不同:Turborepo 基于哈希缓存,简单开箱即用;Nx 做静态依赖分析(解析 import 语句),能发现”虽然没明确声明依赖但实际上引用了”的隐式依赖。大团队或依赖关系复杂的项目可能更适合 Nx。
❌ 误区 3:「Lerna 已经死透了」
Lerna 在 2022 年被 Nx 团队接手后(Lerna v7+),底层默认使用 Nx 做任务编排,本质上成了 Nx 的上层封装。如果团队习惯 Lerna 的 API,可以继续用但享受 Nx 的缓存能力。
❌ 误区 4:「Monorepo 构建太慢,不如拆仓库」
慢的是没有缓存的 Monorepo。Turborepo 的哈希缓存 + 远程缓存可以让 CI 构建时间从全量的 10 分钟降到增量的 30 秒。问题不在 Monorepo 本身,在于是否用了正确的工具。
一句话总结
Monorepo 工具的核心价值不是”把代码放一起”,而是把”人知道怎么编排”变为”工具自动发现并编排”——配置好 turbo.json 后,每次 turbo run build 都是一次精确的拓扑排序 + 增量计算 + 缓存命中的自动化工程。