Turbo(Turborepo)使用介绍
一篇把 Turbo 讲透的实战向教程:它是啥、和谁一起用、咋工作的、怎么配、怎么玩缓存 ,都给你捋清楚。搞完 monorepo 用 pnpm workspace 装依赖、链包之后,任务编排和构建加速交给 Turbo 就对了。
一、Turbo 是啥?解决什么问题?
1.1 一句话定位
Turbo (即 Turborepo 里的核心 CLI)是一个 monorepo 任务编排 + 缓存 工具。它不替代 npm/pnpm/yarn,而是叠在 现有 workspace 之上:你照样用 pnpm workspace 管依赖、用 workspace:* 链本地包;Turbo 负责 按依赖图跑 task、做本地/远程缓存 ,让 build、test、lint 之类又快又稳。
1.2 它解决的具体问题
- 构建慢、重复干活 :多包 monorepo 里,
pnpm -r run build按拓扑顺序跑,但每次都全量 build;没改的包也重新编,CI 拉完代码又是一轮全量,耗时长。 - 不知道先跑谁、后跑谁 :包多了,谁依赖谁容易乱;并行 跑又可能「依赖还没 build 完就被用了」。需要按依赖图排好顺序,能并行的并行,不能的严格先后。
- 缓存难做 :想自己做「改了什么才 rebuild、否则用缓存」,要算 hash、管产物、区分包,很烦。Turbo 把 输入 hash → 输出缓存 都做好了,本地 + 远程都能用。
- CI 与本地不一致 :本地有缓存,CI 没有,结果一个快一个慢、甚至行为不一致。Turbo 的 Remote Cache 让 CI 和同事复用同一套缓存,既快又一致。
所以:Turbo = 依赖图驱动的任务调度 + 确定性缓存(本地 + 远程) 。和你配合使用的 ,主要是 pnpm / yarn / npm / bun 的 workspace ,以及根目录的 package.json scripts。
二、配合什么使用?
2.1 和包管理器、workspace 的关系
Turbo 必须 跑在一个 monorepo 里,且这个 monorepo 用的是带 workspace 的包管理器:
| 包管理器 | workspace 配置 | 说明 |
|---|---|---|
| pnpm | pnpm-workspace.yaml |
推荐;和《前端 pnpm workspace 架构详解》里那套完全契合。 |
| Yarn | package.json 的 workspaces |
Berry / Classic 都行。 |
| npm | package.json 的 workspaces |
npm 7+。 |
| bun | package.json 的 workspaces |
同理。 |
Turbo 会读 包管理器提供的 workspace 结构(有哪些包、谁依赖谁),据此建包级依赖图 ;再结合 turbo.json 里的 task 配置 ,建 task 图,决定「先跑谁、后跑谁、什么能并行」。
所以:先有 workspace,再叠 Turbo 。你这边用 pnpm workspace,就先把 pnpm-workspace.yaml、packages/*、apps/* 搭好,再在根目录加 turbo 和 turbo.json。
2.2 和根 package.json scripts 的关系
各包的 package.json 里会有 scripts,例如 build、dev、test、lint。Turbo 会按任务名 去找这些 script,并只跑 turbo.json 里配置过的 那些任务。
通常你会把「全局」的入口 script 放在根 package.json,例如:
json
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint"
}
}
然后 turbo run build 会:
- 扫描 workspace 里所有 定义了
buildscript 的包; - 按依赖图 +
turbo.json的dependsOn排好顺序; - 能并行的并行跑,不能的按拓扑顺序跑;
- 有缓存就复用,没有再执行。
因此:Turbo 驱动的是「脚本」 ,脚本本身还是你写(Vite、Next、Vitest、ESLint 等),Turbo 只负责何时、在哪些包里、以什么顺序 跑这些脚本,以及要不要用缓存。
2.3 和 Vite、Next、Nx 等的关系
- Vite / Next / Remix / ... :是你各个包 里用的构建/开发框架;每个包的
build、dev调的是它们。Turbo 不替代它们,只是** Orchestrator**:按图跑pnpm run build/pnpm run dev等。 - Nx:也是 monorepo 任务编排 + 缓存工具,和 Turbo 同类。可以二选一;Turbo 更轻、和 Vercel 生态扣得紧,Nx 功能更多、更「全家桶」。
- pnpm
-r/--filter:pnpm 自己也能pnpm -r run build按拓扑跑,但没有 缓存、没有远程缓存、没有「只跑受影响包」的--affected。Turbo 是在这之上加缓存 和更丰富的 filter。
总结:Turbo = workspace(pnpm/yarn/npm/bun)+ 根/包内 scripts + 可选 Vercel Remote Cache;构建框架随便选。
三、Turbo 的原理:依赖图、任务图、缓存
3.1 两层图:包图 + 任务图
Turbo 内部搞了两张有向无环图(DAG):
-
Package Graph(包图)
- 来自包管理器 :根据各包
package.json的dependencies/devDependencies(含workspace:*)算出来谁依赖谁。 - 图上的点是包 ,边是依赖关系 。Turbo 不自己解析
node_modules,完全信 workspace 提供的结构。
- 来自包管理器 :根据各包
-
Task Graph(任务图)
- 来自
turbo.json的tasks:每个 task 对应一个 script 名字(如build、test)。 - 通过
dependsOn声明 task 之间的依赖,例如「build依赖依赖包的build」→"dependsOn": ["^build"]。 - 图上的点是 「某包的某 task」 ,边是 dependsOn。
- Turbo 会根据 包图 + dependsOn 算出完整的 task 图,包括传递依赖(例如 A 依赖 B,B 依赖 C,则 A 的
build间接依赖 C 的build)。
- 来自
执行顺序:
- 按 task 图 的拓扑序跑;没有依赖关系的 task 会并行跑。
- 所以既不会「依赖没 build 就用了」,又能把能并行的尽量并行,加快总耗时。
3.2 dependsOn 的几种写法
在 turbo.json 的 tasks.<task>.dependsOn 里常见几种形式:
| 写法 | 含义 | 示例 |
|---|---|---|
^build |
先跑当前包所依赖的 workspace 包 里的 build |
包 A 依赖 B、C,则先跑 B、C 的 build,再跑 A 的 build |
build |
先跑当前包 自己的 build(同包内 task 顺序) |
少用;一般用 ^build 表「依赖包的 build」 |
#build |
先跑 根 package.json 的 build(根 task) |
根里有个 build 做全局准备时用 |
//#tsc |
同上,根 的 tsc task |
较新文档里根 task 的另一种写法 |
典型用法:
build:"dependsOn": ["^build"]→ 先 build 所有依赖包,再 build 自己。test:有时"dependsOn": ["build"],表示先 build 再 test;有时不依赖 build,看你怎么测。lint:多数不依赖别的 task,可并行。
3.3 缓存:输入 hash → 输出产物
Turbo 的缓存是 内容寻址 的:对每次要跑的 「某包的某 task」 算一个 输入指纹(hash) ,用这个指纹查缓存;命中则直接恢复输出文件,不执行 task;未命中才跑 task,再把输出写入缓存。
参与 hash 的输入通常包括:
- 该包目录下的文件 :默认会包含包内源码等;可通过
inputs精确指定(如src/**、*.config.ts),排除node_modules、.git等。 - 依赖包的本 task 产出 :例如
build的dependsOn: ["^build"],则依赖包的build输出也会进 hash;依赖包 build 结果变了,本包 hash 就变。 globalDependencies:根目录配置的全局文件(如tsconfig.base.json、tailwind.config.js);这些一变,所有 task 的缓存都失效。- 环境变量 :仅
env、globalEnv里配置的会参与 hash;globalPassThroughEnv只透传不参与。 - Task 配置 :
dependsOn、outputs、inputs等本身也会影响「什么算输入、什么算输出」,从而影响 hash。
查缓存与写缓存:
- 本地缓存 :默认在项目根目录的
.turbo;可用TURBO_CACHE_DIR或--cache-dir覆盖。 - 远程缓存 :需配置
TURBO_TEAM+TURBO_TOKEN(Vercel)或自建;先查本地,再查远程;写入时可按--cache控制只写本地、只写远程或都写。
确定性 :
缓存假定 相同输入 → 相同输出 。若 task 里用了时间戳、随机数、或未在 env 里声明的环境变量,同一输入可能产生不同输出,导致缓存不一致;排查时可用 --no-cache 对比。
为何要配 outputs :
Turbo 必须知道「要缓存哪些文件」;不配 outputs 可能不缓存或只用启发式规则,效果差。务必按各包真实产物目录配好(如 dist/**、.next/**)。
3.4 和「只跑改动的包」:--filter、--affected
--filter:只对指定包 跑 task,例如turbo run build --filter=@my/app;支持按包名、路径等过滤。--affected:只对相对某个 ref(如main)有变更 的包跑 task,常用在 CI:turbo run build --affected。
Turbo 会结合 git diff + 包图 判断「哪些包受影响」,只跑有变更的包 + 依赖了它们的包(dependents),其余用缓存或跳过,进一步省时间。
四、安装与最小跑通
4.1 前置条件
- 已有一个 pnpm workspace (或 yarn/npm/bun workspace):根目录有
pnpm-workspace.yaml、packages/*等。 - Node 18+(推荐)。
4.2 安装
在根目录:
bash
pnpm add -D turbo -w
-w 表示装到 workspace root。若用 yarn/npm,则 yarn add -D turbo / npm i -D turbo 在根目录执行即可。
可选装全局,方便手敲 turbo:
bash
pnpm add -g turbo
4.3 最小 turbo.json
在根目录新建 turbo.json:
json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": [],
"outputs": []
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
}
}
}
build:依赖依赖包的build;产物dist/**、.next/**会进缓存。dev:不缓存、长期跑;persistent: true表示常驻进程,Turbo 会正确处理。lint:通常无前置 task、无产物;可outputs: []或省略,Turbo 仍会缓存「lint 是否通过」等元数据。test:示例里依赖build(先 build 再 test);若你测的是源码可直接跑,可改dependsOn: []。有 coverage 时把coverage/**写上。
各包 package.json 里已有同名 script 即可;Turbo 只跑 tasks 里配置过的。
4.4 根 package.json 里用 Turbo 跑
json
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint"
}
}
然后:
bash
pnpm build
pnpm dev
pnpm build 会执行 turbo run build:按依赖图 + 缓存跑所有包的 build。
4.5 从零跑通(基于现有 pnpm workspace)
若你已有《前端 pnpm workspace 架构详解》里那套 pnpm workspace (根目录 pnpm-workspace.yaml、packages/*、apps/*),按下面做即可接上 Turbo:
- 装 Turbo :根目录
pnpm add -D turbo -w。 - 建
turbo.json:根目录新建,内容同 4.3;按你实际用的框架调整outputs(如只有 Vite 就dist/**,有 Next 再加.next/**)。 - 根
package.json的 scripts :加上"build": "turbo run build"、"dev": "turbo run dev"等;若原来用pnpm -r run build,改成turbo run build。 - 各包 script :确保
packages/*、apps/*里要有build、dev等(名字和turbo.json的tasks一致)。 - 跑一次 :根目录
pnpm build。首次会全量执行并写入缓存;再跑一次,没改动的包应显示cache hit,直接复用缓存。
验证:
turbo run build --graph可导出 task 图,确认依赖关系是否符合预期。- 改某个包的源码再
pnpm build,应只重跑该包及其依赖方,其余 cache hit。
五、turbo.json 配置详解
5.1 顶层结构
-
$schema:用 IDE 补全、校验,推荐写上。 -
支持
turbo.jsonc:可加//注释。 -
tasks:任务名 → 配置;任务名和package.json的 scripts 对齐。 -
globalDependencies:所有 task 的 hash 都会包含这些文件;它们一变,全局缓存失效。例如:json"globalDependencies": ["tsconfig.base.json", "tailwind.config.js", ".env.example"]适合放跨包共享的配置、模板 env 等。
-
globalEnv:所有 task 的 hash 都包含这些环境变量;一变,全局缓存失效。 -
globalPassThroughEnv:给 task 透传 的环境变量,不 参与 hash;如NODE_ENV、CI、GITHUB_ACTIONS等。
5.2 单个 task 的配置
每个 tasks.<name> 里常用字段:
| 字段 | 含义 | 示例 |
|---|---|---|
dependsOn |
本 task 依赖哪些别的 task(含 ^build) |
["^build"] |
outputs |
本 task 的产物目录,会进缓存 | ["dist/**", ".next/**"] |
inputs |
参与 hash 的输入文件;默认包含包内文件 | ["src/**", "tsconfig.json"] |
env |
本 task 专属的、参与 hash 的环境变量 | ["VITE_APP_VERSION"] |
cache |
是否缓存本 task | true(默认)/ false |
persistent |
是否长期运行(如 dev server) | true 时通常配 cache: false |
outputMode |
日志输出风格 | full / hash-only / new-only / none |
5.3 outputs 一定要配对
没配 outputs :Turbo 不知道要缓存什么,可能不缓存或只按默认规则,容易白跑。
配错 :例如产物在 dist/ 却写了 build/**,缓存用不上。
常见框架示例:
- Vite :
["dist/**"] - Next.js :
[".next/**", "!.next/cache/**"](有时排除.next/cache) - TS 仅声明 :
["dist/**"] - Rollup :看
output.dir,一般["dist/**"]
5.4 inputs 与默认行为
不写 inputs 时,Turbo 通常把该包目录下 的相关文件当输入(会排除 node_modules、常见忽略文件等)。
若你希望只有部分文件 变动才让缓存失效,可显式写 inputs,例如:
json
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**", "public/**", "vite.config.ts", "tsconfig.json"],
"outputs": ["dist/**"]
}
这样改 README.md 之类的不会导致 build 缓存失效。
5.5 环境变量与 hash
env:只影响本 task 的 hash;如["VITE_APP_VERSION"],该变量变了,本 task 缓存失效。globalEnv:影响所有 task;如["NODE_ENV"],一变全失效。globalPassThroughEnv:传给 task 用,不 参与 hash;如["CI","GITHUB_ACTIONS"],CI 里不想让这些影响缓存时可放这里。
框架相关变量(如 NEXT_PUBLIC_*、VITE_*)Turbo 有不少自动推断,不一定全写;有特殊需求再补。
5.6 根目录专属 task(//#)
若根 package.json 有 只跑在根 的 script(例如 prepare、tsc 做全库类型检查),可把它当「根 task」用 //# 引用:
json
"build": {
"dependsOn": ["//#tsc", "^build"],
"outputs": ["dist/**"]
}
表示:先跑根目录的 tsc,再按包图跑各包 build。
六、常用 CLI 与参数
6.1 turbo run <task>
bash
turbo run build
turbo run build test lint
- 跑一个或多个 task ;Turbo 会跑所有有对应 script 的包,且符合 task 图 顺序。
- 多 task 时,按 task 拓扑 排;同层可并行。
6.2 --filter
bash
turbo run build --filter=@my/web
turbo run build --filter=./apps/*
turbo run build --filter=@my/ui...
turbo run build --filter=...@my/ui
| 写法 | 含义 |
|---|---|
--filter=@my/web |
只跑指定包 @my/web |
--filter=./apps/* |
只跑 apps/ 下所有包 |
--filter=@my/ui... |
跑 依赖了 @my/ui 的包(含自身,dependents) |
--filter=...@my/ui |
跑 @my/ui 所依赖的包(含自身,dependencies) |
--filter=@scope/* |
通配 ,所有 @scope 下的包 |
组合示例:只 build 某 app 及其所有依赖 → turbo run build --filter=...@my/web。多 filter 可组合使用(满足其一即跑),具体以官方文档为准。
6.3 --affected
bash
turbo run build --affected
turbo run build --affected --base=main
- 只跑相对
--base(默认main)有变更的包,以及依赖了这些包的包(changed + dependents);其余用缓存或跳过。 - CI 里
git diff main...HEAD一类场景常用,避免全量 build。注意 :--affected与--filter互斥,不能同时用。
6.4 --cache-dir、--no-cache、--force
--cache-dir:指定本地缓存目录。--no-cache:不用缓存,也不写缓存;排查「缓存导致结果不对」时有用。--force:跳过缓存查找,强制重跑,但跑完仍会写缓存 ;和--no-cache不同。
6.5 --graph
bash
turbo run build --graph
- 生成 task 图 的可视化(如 HTML);方便看谁依赖谁、哪些会并行。
6.6 --dry-run
- 只打印将要跑的任务,不执行;用来确认 filter、affected 是否符合预期。
七、远程缓存(Remote Cache)
7.1 干啥用
把 Task 缓存 存到远端,多人、多机器、CI 共用:
- 别人 / CI 已经 build 过的 hash ,你这台机器直接命中,不用再 build;
- 你 build 完推上去,别人 / CI 也能复用。
7.2 Vercel 免费 Remote Cache
- 登录 :
turbo login,用 Vercel 账号。 - 建 / 选 Team:在 Vercel 建个 Team(或用已有的)。
- 链到 repo:在 Vercel 里把当前 git 仓库连到该 Team(若还没)。
- 环境变量 (本机调试可不设,用
turbo login即可;CI 要设):TURBO_TEAM:Vercel Team 的 slug (如my-team)。TURBO_TOKEN:有权限访问 Remote Cache 的 token(Vercel 或 Turbo 后台生成)。
CI 里通常:
bash
turbo run build --affected
只要 TURBO_TEAM、TURBO_TOKEN 设好,就会自动用 Remote Cache :能读则读,能写则写(可按 --cache 细调读/写策略)。
7.3 --cache 细调读/写
--cache=local:rw,remote:rw(默认):本地、远程都可读可写。--cache=local:r,remote:rw:只读本地,只写远程(例如 CI 不想写本地)。--cache=local::不用本地缓存,仅远程。
7.4 自建 Remote Cache
Turbo 支持自建 Remote Cache 服务(按官方协议实现 API),把 TURBO_API、TURBO_TOKEN 等指过去即可;适合内网、不能连 Vercel 的场景。
八、CI 里怎么用
8.1 典型流程
- 检出代码
- 启用 Corepack (若用
packageManager锁 pnpm):corepack enable - 装依赖 :
pnpm install --frozen-lockfile - 跑 Turbo :
turbo run build --affected或turbo run build(全量) - 若有 test、lint:
turbo run test lint --affected等
8.2 环境变量
TURBO_TEAM、TURBO_TOKEN:用 Remote Cache 时必设。TURBO_REMOTE_ONLY:1时只用远程缓存,不写本地(CI 常见)。
8.3 --affected 的 base
CI 里往往以 主分支 为基准算 affected,例如:
bash
turbo run build --affected --base=origin/main
具体 ref 依你们分支策略来(main、master、origin/develop 等)。
九、和 pnpm workspace 的配合总结
- pnpm :管 依赖安装 、workspace 包 (
workspace:*)、lockfile ;pnpm install在根目录执行。 - Turbo :管 任务编排 (
build/dev/test/lint)和 缓存 ;turbo run build等由根package.json的 scripts 调用。
和 pnpm -r 的对比:
| 能力 | pnpm -r run build |
turbo run build |
|---|---|---|
| 按依赖图顺序跑 | ✅ | ✅ |
| 可并行(同层) | ❌ 默认顺序 | ✅ |
| 本地缓存 | ❌ | ✅ |
| 远程缓存 | ❌ | ✅(需配置) |
--affected 只跑改动 |
❌ | ✅ |
--filter 限定包 |
✅ | ✅(语法类似) |
推荐:
- 根
package.json:"build": "turbo run build","dev": "turbo run dev"等;pnpm build、pnpm dev即走 Turbo。 turbo.json:配好tasks.build、tasks.dev的dependsOn、outputs;build用^build,dev用cache: false+persistent: true。- Remote Cache:能上就上(Vercel 或自建),CI 和本地都能受益。
十、常见问题与排查
- 缓存错了 / 结果不对 :用
--no-cache重跑对比;检查outputs、inputs、env是否把该算的算进去、不该算的排除。 - 一直 cache miss :看
inputs是否包含变动频繁却与构建无关的文件;env/globalEnv是否把随机、时间戳类变量算进去了。 - Turbo 找不到包 :确认 workspace 配置正确(
pnpm-workspace.yaml等),turbo run在根目录执行。 - 某些包没跑 :看
--filter、--affected;以及该包是否真有对应 script(如build)。
十一、参考与延伸
配合 《前端 pnpm workspace 架构详解》 一起看,先把 workspace 搭稳,再叠 Turbo 做任务编排与缓存,monorepo 的「装依赖 + 构建 + 缓存」一条龙就齐了。