Chapter 08

开发期也能 Turbo

构建任务能缓存,那 dev 这种长驻进程呢?——不该缓存,但 Turbo 仍然提供并行启动 + watch 重跑依赖 + 日志聚合。Turbo 2.x 的 turbo watchwith 字段让本地开发体验上了一个台阶。

持久任务(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
concurrentlyturbo run dev
配置命令行/scriptsturbo.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 选项:

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。

实战: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 的限制

本章小结