持久任务(persistent)
{
"tasks": {
"dev": {
"cache": false,
"persistent": true
}
}
}
cache: false
dev 是长进程,没有"产物"概念,缓存没意义。必须关。
persistent: true
告诉 Turbo 这是永远不会退出的任务——不要作为其他任务的依赖(会死锁)。
并行跑多个 dev
pnpm turbo run dev
• Running dev in 3 packages: @myorg/web, @myorg/api, @myorg/worker • Remote caching disabled @myorg/web:dev: > next dev -p 3000 @myorg/api:dev: > tsx watch src/server.ts @myorg/worker:dev: > tsx watch src/worker.ts [@myorg/web:dev] ready on http://localhost:3000 [@myorg/api:dev] listening on :4000 [@myorg/worker:dev] queue worker ready
三个 dev 并行,日志有前缀——一个终端看所有输出,不用开 3 个 tab。
with 字段(Turbo 2.x)
{
"tasks": {
"@myorg/web#dev": {
"cache": false,
"persistent": true,
"with": ["@myorg/api#dev", "@myorg/worker#dev"]
}
}
}
pnpm turbo run dev --filter=@myorg/web # 启动 web 时自动带起 api 和 worker
前端开发者只想启 web,但 web 需要 api 和 worker 才能跑——with 字段让 Turbo 自动拉起它们。
with vs dependsOn
dependsOn 要求上游先跑完再跑自己——对 dev 这种不会退出的任务是死锁。with 表示一起跑,不分先后,专为持久任务设计。
Watch 模式
pnpm turbo watch dev
Turbo 2.x 新命令:监听文件变化,只重跑受影响的任务。
1. 你启动:pnpm turbo watch dev 2. web/api/worker 都跑起来了 3. 你改 packages/ui/src/Button.tsx 4. Turbo 识别出:ui 的依赖者是 web(不是 api) 5. 只给 web 发"重启"信号(Next.js HMR 本来就能处理)
Watch 和 build 组合
pnpm turbo watch build
库开发场景——把 packages/ui 编译产物放在 ui/dist,web dev 引用的是 dist,不是 src。
1. 改 packages/ui/src/*.ts 2. turbo watch 识别 ui:build 需要重跑 3. 自动跑 ui:build → 更新 ui/dist 4. web 用的是 ui/dist,Next HMR 感知变化,浏览器刷新
watch 的 inputs 控制
{
"tasks": {
"build": {
"inputs": ["src/**", "!src/**/*.test.ts"],
"outputs": ["dist/**"]
}
}
}
watch 只关心 inputs 里列的文件——改 test 文件不触发 build 重跑。
dev 任务的 env
{
"dev": {
"cache": false,
"persistent": true,
"env": ["PORT", "NODE_ENV"]
}
}
dev 虽然不缓存,但 strict 模式下仍然只能读声明的 env——忘声明会导致 process.env.PORT 读不到。
与 concurrently 对比
# 传统做法:concurrently concurrently "cd apps/web && pnpm dev" "cd apps/api && pnpm dev" # Turbo pnpm turbo run dev
| concurrently | turbo run dev | |
|---|---|---|
| 配置 | 命令行/scripts | turbo.json 集中 |
| 日志 | 有前缀 | 有前缀 + 颜色 |
| 依赖图 | 不懂 | with 字段表达 |
| watch 依赖重跑 | 不支持 | turbo watch |
| 过滤子集 | 拆命令 | --filter |
场景:前端改共享库实时生效
目录: apps/web ← Next.js packages/ui ← 共享组件 问题:改 packages/ui/src/Button.tsx,web 要重 build ui 才能看到
方案 A(推荐):源码引用
// packages/ui/package.json { "main": "./src/index.ts", // 直接指向 src(前端可 tsconfig path) "types": "./src/index.ts", "exports": { ".": { "development": "./src/index.ts", "default": "./dist/index.js" } } }
开发环境 Next.js 直接吃 src/.ts,改完 HMR 秒生效——不用 ui:build。
方案 B:watch + dist
pnpm turbo watch dev # 同时 watch ui:build 和 web:dev
Parallel 模式
pnpm turbo run dev --parallel # 忽略 dependsOn,所有任务立即并行启动 # 适合 dev 任务:不需要等上游"完成"
单个任务输出
pnpm turbo run dev --filter=@myorg/web --output-logs=new-only # 只输出新的日志,减少干扰
output-logs 选项:
full:全部日志(默认)new-only:只新的 cache-miss 任务hash-only:只任务 hasherrors-only:只错误none:啥都不打
TUI 模式(Turbo 2.x)
pnpm turbo run dev --ui=tui
┌─ @myorg/web:dev ─────────────┐ ┌─ @myorg/api:dev ──────────────┐ │ ready on http://localhost:3000│ │ listening on :4000 │ │ ✓ compiled / in 1s│ │ [POST] /users 201 (45ms) │ │ ... │ │ ... │ └────────────────────────────────┘ └────────────────────────────────┘ ┌─ @myorg/worker:dev ──────────────────────────────────────────────┐ │ queue worker ready │ │ job processed: email-welcome │ └───────────────────────────────────────────────────────────────────┘
每个任务一个窗口,切换看——比混流日志清爽多了。
// turbo.json 默认开 TUI { "ui": "tui" }
dev 任务里的 inputs
{
"dev": {
"cache": false,
"persistent": true,
"inputs": ["src/**"] // 给 turbo watch 用
}
}
dev 本身不缓存,但 turbo watch 需要知道关心哪些文件——inputs 告诉它。
老版本问题:所有 dev 一起退
Ctrl+C 行为
Turbo 1.x 有时候 Ctrl+C 只杀了 turbo 进程,子进程还在——变成僵尸,下次启动端口占用。Turbo 2.x 已经修好,所有 persistent 任务会被正确 kill。如果还遇到,检查 turbo 版本 ≥ 2.0。
Turbo 1.x 有时候 Ctrl+C 只杀了 turbo 进程,子进程还在——变成僵尸,下次启动端口占用。Turbo 2.x 已经修好,所有 persistent 任务会被正确 kill。如果还遇到,检查 turbo 版本 ≥ 2.0。
实战:4 进程全家桶
{
"tasks": {
"dev": {
"cache": false,
"persistent": true
},
"@myorg/web#dev": {
"cache": false,
"persistent": true,
"with": ["@myorg/api#dev", "@myorg/worker#dev", "@myorg/ui#watch"]
},
"@myorg/ui#watch": {
"cache": false,
"persistent": true
}
}
}
pnpm turbo run dev --filter=@myorg/web # 一行启动:web dev + api dev + worker dev + ui watch # 四进程并发,统一日志,一键全挂
watch 的限制
- 不支持
dependsOn——watch 只能跑叶子任务,不能传染重跑 - outputs 变化不触发上游重跑(比如 A build 完的产物被 B 用——B 不会自动重跑)——用
dev/HMR 解决 - inputs 要写清楚,否则要么漏监听要么监听一大堆
本章小结
- dev 任务必须
cache: false+persistent: true - Turbo 2.x 的
with字段让持久任务并发启动,替代 concurrently turbo watch监听 inputs 变化自动重跑受影响的任务- TUI 模式给每个任务独立窗口,日志不串
- 共享库选 "源码引用 + exports conditions" 模式,省掉中间 build 步骤