Turborepo 任务编排:缓存、并行与增量构建
本节目标
- 用
turbo.json声明任务依赖图 - 理解 Turborepo 的「缓存」如何让第二次构建秒过
- 用
--filter只构建「受影响的包」
当包变多(10 个、50 个),你在根目录跑 pnpm build,会傻乎乎地把所有包都重新构建一遍,哪怕你只改了其中一个。Turborepo(简称 Turbo)就是来解决这个问题——它给每个任务建「依赖图」,只跑该跑的,并且把结果缓存下来。
先在根 package.json 装上 Turbo,并加几个「聚合脚本」:
// 根 package.json
{
"scripts": {
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint"
},
"devDependencies": {
"turbo": "^2.0.0"
}
}核心是 turbo.json——它描述「每个任务依赖什么、怎么算缓存」:
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"], // 先构建「依赖的包」(^ 表示上游)
"outputs": ["dist/**"] // 把 dist 目录作为缓存产物
},
"test": {
"dependsOn": ["build"], // 测试前先确保构建好
"outputs": [] // 测试无产物,不缓存输出
},
"lint": {
"outputs": []
}
}
}dependsOn: ["^build"] 是关键:构建 web 时,Turbo 会先按拓扑把 web 依赖的 ui、types 都构建好(上游优先)。而 outputs: ["dist/**"] 让 Turbo 把 dist 哈希缓存——第二次构建若源码没变,直接命中缓存、秒过,不再重复编译。
# 全量构建(Turbo 自动并行 + 缓存)
pnpm build
# 只构建「相对于上次 commit 改动的包」及其下游
pnpm build --filter=@my/web...
# 只构建某个具体包
pnpm --filter @my/ui build--filter=@my/web... 里的 ... 表示「web 以及所有依赖/被依赖它的包」,非常适合「我只改了 web,别的全跳过」的场景。结合远程缓存(把缓存上传到云端),团队里任何人第一次构建也能秒级复用别人的结果。
名词解释
Turborepo(Turbo):Monorepo 的任务编排器,按依赖图并行执行 build/test/lint,并用内容哈希做任务缓存,大幅缩短 CI 与本地构建时间。
Pipeline(任务管线):turbo.json 里定义的「任务清单 + 依赖关系」,告诉 Turbo 哪个任务依赖哪个、产物是什么。
任务缓存(Task Cache):Turbo 对「输入(源码+依赖)+ 输出(如 dist)」做哈希,命中则跳过执行直接复用结果,是「二次构建秒过」的原理。
--filter 与 ... 语法:Turbo/pnpm 的包选择器;--filter=@my/web... 选中 web 及其上下游相关包,实现「只动相关的」。
课后练习
练习 1:dependsOn: ["^build"] 里的 ^ 是什么意思?不加会怎样?
答案:
^表示「上游依赖包」。web的build会先确保它依赖的ui/types已构建。不加的话,可能web开始构建时ui还没好,导致找不到最新的ui产物而构建失败或用了旧版本。
练习 2:为什么 lint 的 outputs 通常是空数组?
答案:
lint不产出可复用的文件产物(只是检查报错误),没有「输出物」需要缓存,所以outputs: [];它的「输入」仍是源码,inputs变化时才会重跑。
总结
Turbo 解决的是 Monorepo 的「规模化」问题:用 turbo.json 描述任务依赖图,靠哈希缓存让重复构建秒过,靠 --filter 让改动只触动相关包。没有它,包一多构建就寸步难行;有了它,10 个包和 100 个包的体验差不了多少。记住 dependsOn: ["^build"] 是正确性的底线。最后一节我们谈「多包怎么发版」与那些绕不开的工程避坑。