Lerna 时代(2015-2020)
Lerna 诞生于 Babel 团队自建多包管理工具的需求——把 babel-core、babel-parser 等 40 多个包放同一仓库。它解决了:
lerna bootstrap:装所有子包依赖lerna run build:每个包跑一遍 buildlerna publish:批量发版
但三个硬伤让它在 2022 年被 Nx 团队"收养"(实际上是进入维护期,不再主动迭代):
Lerna 的三个痛
- 没有任务缓存——每次 CI 都全仓跑一遍,Next + Storybook + 20 个子包要 10 分钟
- bootstrap 慢且脆弱——基于 npm install,没有 pnpm 的 store;版本冲突频出
- task graph 靠约定,不强制——你得手动保证依赖顺序,否则会构建失败
pnpm workspaces 补了一半
pnpm 2020 年起的 workspaces 接管了"装包":
- content-addressable store — 0 重复
- symlink 布局 — 无幽灵依赖
pnpm -r build— 拓扑排序跑任务pnpm -F "...[origin/main]" build— changed-since 过滤
但 pnpm 没做的事:
- 任务输出缓存——改个 README,所有包还是要重跑 build,只是跑出来的东西和上次一样
- 细粒度的任务图——pnpm 只看包依赖,不懂"build 依赖 build"还是"build 依赖 test"
- 远程缓存共享——我本地构建的产物没办法给同事用
Turborepo 的定位
┌─────────────────────────────────────────┐
│ pnpm workspaces │
│ • 装包(store + symlink) │
│ • 版本管理(Catalog / workspace:) │
│ • 基本的 -r 运行 │
└──────────────┬──────────────────────────┘
│ 之上加一层
▼
┌─────────────────────────────────────────┐
│ Turborepo │
│ • 任务 DAG(用 JSON 定义) │
│ • 基于 inputs/outputs 的缓存 │
│ • 远程缓存(跨机器、跨人共享) │
│ • 增量:只跑 hash 变了的任务 │
└─────────────────────────────────────────┘
Turborepo不替代 pnpm——它站在 pnpm 之上,专心做好"跑哪些任务 / 哪些任务可以跳过"。
类比:Bazel 的 JS 版本
Turborepo 的核心思路和 Google Bazel、Meta Buck、Airbnb Pants 同源:
- 任务是有输入(源码 + 依赖产物 + 环境变量)和输出(dist/**)的黑盒
- 对输入做 hash,相同 hash 的输出可以复用
- 缓存存本地,也可以存远程(多人共享)
Bazel 为什么没普及到 JS?
Bazel 要求你用它的
Bazel 要求你用它的
BUILD 文件声明每个模块,迁移成本极高——前端人没耐心。Turborepo 把这套"简化"到一个 turbo.json,复用 npm scripts,5 分钟上手。牺牲了一些正确性(比如不强制沙盒),换来了普及度。
vs Nx
| Turborepo | Nx | |
|---|---|---|
| 定位 | 轻量编排 + 缓存 | 全功能框架 |
| 配置 | 一份 turbo.json | nx.json + 每包 project.json |
| 代码生成 | 无 | 强大的 generator |
| 任务执行 | 只跑 npm scripts | 自带 executors |
| 学习曲线 | 低(半小时) | 中(1-2 天) |
| 运行时 | Rust(v2) | Node.js |
| 远程缓存 | Vercel / 自建 | Nx Cloud |
| 适合 | 已有 pnpm workspaces 的项目 | 企业级 Angular/React 全家桶 |
大多数团队:Turborepo 就够用。只有需要 Angular 深度集成、大量代码生成、任务类型超出 npm scripts 范围时,才上 Nx。
vs Bazel / Buck / Pants
那些大型构建系统语言无关、正确性极高、缓存跨语言——但:
- 学习曲线陡峭(Starlark 语法、严格沙盒)
- JS 生态集成要写大量 rules
- 配置动辄上千行
Turborepo 的哲学是"够用就好":95% 的 JS monorepo 场景用 turbo.json 就能搞定,剩下 5% 需要 Bazel 的极致复杂度。
什么样的项目适合 Turborepo
5 个以上子包的 monorepo
包多了编排才有价值,单包项目上 Turbo 没意义。
CI 时间 > 2 分钟
加 Turbo 缓存通常立即省到 30 秒内;少于 2 分钟的项目,收益不明显。
团队 5 人以上
远程缓存收益来自"你的 CI 构建被同事本地复用",团队越大越划算。
已经在用 pnpm/Yarn workspaces
Turbo 需要一个包管理器兜底——pnpm 最搭配。
什么样的不适合
- 单包项目(没东西可编排)
- 不跑 CI(只本地开发)
- 构建任务全是非确定性的(每次输出不同)——这种缓存失效,白忙一场
- 跨语言混合仓库(部分 Rust/Go/Python)——Turbo 只管 JS 部分,另一半还得 Bazel/Make
生态现状(2026)
- Vercel 所有官方模板(Next.js、Storybook、SvelteKit)首选 Turborepo
- Shopify Hydrogen、Chakra UI v3、Storybook monorepo 全迁 Turbo
- Changesets + Turbo + pnpm 成为 JS 库发版事实标准
- Bun 1.2 官方文档推荐 Turborepo 搭配
- Rust 重写后性能 10 倍提升,启动 50ms 以内
本章小结
- Lerna 已停更,Nx 重、Bazel 学习成本高——Turborepo 是 JS monorepo 的最佳平衡
- 分工:pnpm 装包 + Turborepo 跑任务,各司其职
- 核心能力:任务 DAG + inputs/outputs 哈希缓存 + 远程缓存共享
- 适合 5+ 子包、CI > 2 分钟、团队多人的 JS/TS 项目
- 2024 年起 Rust 重写(v2),性能和二进制大小大幅改进