【Turbo】使用介绍

Turbo(Turborepo)使用介绍

一篇把 Turbo 讲透的实战向教程:它是啥、和谁一起用、咋工作的、怎么配、怎么玩缓存 ,都给你捋清楚。搞完 monorepo 用 pnpm workspace 装依赖、链包之后,任务编排和构建加速交给 Turbo 就对了。


一、Turbo 是啥?解决什么问题?

1.1 一句话定位

Turbo (即 Turborepo 里的核心 CLI)是一个 monorepo 任务编排 + 缓存 工具。它不替代 npm/pnpm/yarn,而是叠在 现有 workspace 之上:你照样用 pnpm workspace 管依赖、用 workspace:* 链本地包;Turbo 负责 按依赖图跑 task、做本地/远程缓存 ,让 buildtestlint 之类又快又稳。

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.jsonworkspaces Berry / Classic 都行。
npm package.jsonworkspaces npm 7+。
bun package.jsonworkspaces 同理。

Turbo 会 包管理器提供的 workspace 结构(有哪些包、谁依赖谁),据此建包级依赖图 ;再结合 turbo.json 里的 task 配置 ,建 task 图,决定「先跑谁、后跑谁、什么能并行」。

所以:先有 workspace,再叠 Turbo 。你这边用 pnpm workspace,就先把 pnpm-workspace.yamlpackages/*apps/* 搭好,再在根目录加 turboturbo.json

2.2 和根 package.json scripts 的关系

各包的 package.json 里会有 scripts,例如 builddevtestlint。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 里所有 定义了 build script 的包;
  • 依赖图 + turbo.jsondependsOn 排好顺序;
  • 能并行的并行跑,不能的按拓扑顺序跑;
  • 有缓存就复用,没有再执行。

因此:Turbo 驱动的是「脚本」 ,脚本本身还是你写(Vite、Next、Vitest、ESLint 等),Turbo 只负责何时、在哪些包里、以什么顺序 跑这些脚本,以及要不要用缓存

2.3 和 Vite、Next、Nx 等的关系

  • Vite / Next / Remix / ... :是你各个包 里用的构建/开发框架;每个包的 builddev 调的是它们。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)

  1. Package Graph(包图)

    • 来自包管理器 :根据各包 package.jsondependencies / devDependencies(含 workspace:*)算出来谁依赖谁。
    • 图上的点是 ,边是依赖关系 。Turbo 不自己解析 node_modules,完全信 workspace 提供的结构。
  2. Task Graph(任务图)

    • 来自 turbo.jsontasks:每个 task 对应一个 script 名字(如 buildtest)。
    • 通过 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.jsontasks.<task>.dependsOn 里常见几种形式:

写法 含义 示例
^build 先跑当前包所依赖的 workspace 包 里的 build 包 A 依赖 B、C,则先跑 B、C 的 build,再跑 A 的 build
build 先跑当前包 自己的 build(同包内 task 顺序) 少用;一般用 ^build 表「依赖包的 build」
#build 先跑 根 package.jsonbuild(根 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 产出 :例如 builddependsOn: ["^build"],则依赖包的 build 输出也会进 hash;依赖包 build 结果变了,本包 hash 就变。
  • globalDependencies :根目录配置的全局文件(如 tsconfig.base.jsontailwind.config.js);这些一变,所有 task 的缓存都失效。
  • 环境变量 :仅 envglobalEnv 里配置的会参与 hash;globalPassThroughEnv 只透传不参与。
  • Task 配置dependsOnoutputsinputs 等本身也会影响「什么算输入、什么算输出」,从而影响 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.yamlpackages/* 等。
  • 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.yamlpackages/*apps/*),按下面做即可接上 Turbo:

  1. 装 Turbo :根目录 pnpm add -D turbo -w
  2. turbo.json :根目录新建,内容同 4.3;按你实际用的框架调整 outputs(如只有 Vite 就 dist/**,有 Next 再加 .next/**)。
  3. package.json 的 scripts :加上 "build": "turbo run build""dev": "turbo run dev" 等;若原来用 pnpm -r run build,改成 turbo run build
  4. 各包 script :确保 packages/*apps/* 里要有 builddev 等(名字和 turbo.jsontasks 一致)。
  5. 跑一次 :根目录 pnpm build。首次会全量执行并写入缓存;再跑一次,没改动的包应显示 cache hit,直接复用缓存。

验证

  • turbo run build --graph 可导出 task 图,确认依赖关系是否符合预期。
  • 改某个包的源码再 pnpm build,应只重跑该包及其依赖方,其余 cache hit。

五、turbo.json 配置详解

5.1 顶层结构

  • $schema:用 IDE 补全、校验,推荐写上。

  • 支持 turbo.jsonc :可加 // 注释。

  • tasks :任务名 → 配置;任务名和 package.jsonscripts 对齐。

  • globalDependencies所有 task 的 hash 都会包含这些文件;它们一变,全局缓存失效。例如:

    json 复制代码
    "globalDependencies": ["tsconfig.base.json", "tailwind.config.js", ".env.example"]

    适合放跨包共享的配置、模板 env 等。

  • globalEnv所有 task 的 hash 都包含这些环境变量;一变,全局缓存失效。

  • globalPassThroughEnv :给 task 透传 的环境变量, 参与 hash;如 NODE_ENVCIGITHUB_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(例如 preparetsc 做全库类型检查),可把它当「根 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

  1. 登录turbo login,用 Vercel 账号。
  2. 建 / 选 Team:在 Vercel 建个 Team(或用已有的)。
  3. 链到 repo:在 Vercel 里把当前 git 仓库连到该 Team(若还没)。
  4. 环境变量 (本机调试可不设,用 turbo login 即可;CI 要设):
    • TURBO_TEAM :Vercel Team 的 slug (如 my-team)。
    • TURBO_TOKEN :有权限访问 Remote Cache 的 token(Vercel 或 Turbo 后台生成)。

CI 里通常:

bash 复制代码
turbo run build --affected

只要 TURBO_TEAMTURBO_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_APITURBO_TOKEN 等指过去即可;适合内网、不能连 Vercel 的场景。


八、CI 里怎么用

8.1 典型流程

  1. 检出代码
  2. 启用 Corepack (若用 packageManager 锁 pnpm):corepack enable
  3. 装依赖pnpm install --frozen-lockfile
  4. 跑 Turboturbo run build --affectedturbo run build(全量)
  5. 若有 test、lint:turbo run test lint --affected

8.2 环境变量

  • TURBO_TEAMTURBO_TOKEN:用 Remote Cache 时必设。
  • TURBO_REMOTE_ONLY1 时只用远程缓存,不写本地(CI 常见)。

8.3 --affected 的 base

CI 里往往以 主分支 为基准算 affected,例如:

bash 复制代码
turbo run build --affected --base=origin/main

具体 ref 依你们分支策略来(mainmasterorigin/develop 等)。


九、和 pnpm workspace 的配合总结

  • pnpm :管 依赖安装workspace 包workspace:*)、lockfilepnpm 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 buildpnpm dev 即走 Turbo。
  • turbo.json :配好 tasks.buildtasks.devdependsOnoutputsbuild^builddevcache: false + persistent: true
  • Remote Cache:能上就上(Vercel 或自建),CI 和本地都能受益。

十、常见问题与排查

  • 缓存错了 / 结果不对 :用 --no-cache 重跑对比;检查 outputsinputsenv 是否把该算的算进去、不该算的排除。
  • 一直 cache miss :看 inputs 是否包含变动频繁却与构建无关的文件;env / globalEnv 是否把随机、时间戳类变量算进去了。
  • Turbo 找不到包 :确认 workspace 配置正确(pnpm-workspace.yaml 等),turbo run根目录执行。
  • 某些包没跑 :看 --filter--affected;以及该包是否真有对应 script(如 build)。

十一、参考与延伸

配合 《前端 pnpm workspace 架构详解》 一起看,先把 workspace 搭稳,再叠 Turbo 做任务编排与缓存,monorepo 的「装依赖 + 构建 + 缓存」一条龙就齐了。

相关推荐
军军君012 小时前
Three.js基础功能学习十三:太阳系实例上
前端·javascript·vue.js·学习·3d·前端框架·three
打小就很皮...3 小时前
Tesseract.js OCR 中文识别
前端·react.js·ocr
wuhen_n4 小时前
JavaScript内存管理与执行上下文
前端·javascript
Hi_kenyon4 小时前
理解vue中的ref
前端·javascript·vue.js
落霞的思绪5 小时前
配置React和React-dom为CDN引入
前端·react.js·前端框架
Hacker_Z&Q5 小时前
CSS 笔记2 (属性)
前端·css·笔记
Anastasiozzzz5 小时前
LeetCode Hot100 295. 数据流的中位数 MedianFinder
java·服务器·前端
Exquisite.6 小时前
Nginx
服务器·前端·nginx
打小就很皮...6 小时前
dnd-kit 实现表格拖拽排序
前端·react.js·表格拖拽·dnd-kit