Turborepo / Nx:前端 monorepo 缓存 + 构建

起因

公司项目演化:

  • 1 个 web app → 加 mobile app(React Native)
  • 加 admin dashboard
  • 加 marketing site
  • 加 design system 组件库
  • 加 shared TS types / API client

多 repo 痛点:

  • shared code 要 publish package
  • 跨 repo PR 难 coordinate
  • 升级 dep 各 repo 跟不齐

monorepo 解决但 build / test / cache 是新挑战。
Turborepo / Nx 解决"哪些 task 要跑 + 缓存什么"。

Turborepo

Vercel 收购,2022+。

monorepo/
├── apps/
│   ├── web/                  # Next.js
│   ├── mobile/               # React Native
│   └── admin/
├── packages/
│   ├── ui/                   # shared component
│   ├── api-client/
│   └── tsconfig/
├── package.json
├── turbo.json
└── pnpm-workspace.yaml

pnpm-workspace.yaml

packages:
  - 'apps/*'
  - 'packages/*'

turbo.json

{
    "tasks": {
        "build": {
            "dependsOn": ["^build"],
            "outputs": [".next/**", "dist/**"]
        },
        "test": {
            "dependsOn": ["build"]
        },
        "lint": {},
        "dev": {
            "cache": false,
            "persistent": true
        }
    }
}

^build = 先跑依赖 package 的 build。

用法

pnpm turbo build              # build 所有 package(按 dep order)
pnpm turbo test               # 同上
pnpm turbo dev                # 跑所有 dev server
pnpm turbo build --filter=web  # 只 build web app + 它依赖

第一次跑 build:跑全部 task。
第二次跑 build(无改动):全部 cache hit,零执行

cache 怎么工作

Turbo 把每 task 的:

  • input file content hash
  • env var
  • dependency hash

→ cache key。命中 cache:直接复用 output(dist/)。

✔  ui:build  cached (3.2s saved)
✔  api-client:build  cached
○  web:build  finished (cached) 4.5s

改 1 个 package 的 source → 它跟它的 dependent 重 build;其它仍 cache hit。

远程 cache

Turbo cache 默认本地。多人开发 / CI 同一计算重复跑。
remote cache:

npx turbo login
npx turbo link

把 cache 推 Vercel cloud / 自托管 → 同事 pull 后 cache 命中(同 commit
他不必再跑 build)。

我们 6 人团队 CI 时间从 12 分钟 → 2 分钟(90% cache hit)。

Nx

Nrwl 出,2017+。比 turbo 更老更全。

npx create-nx-workspace@latest myorg

Nx 提供:

  • task scheduling + cache(跟 Turbo 类似)
  • generator(一键 scaffold app / lib)
  • 依赖图可视化
  • 多语言(不只 JS,也支持 .NET / Go via plugin)
  • consistent project structure 强制

nx.json

{
    "targetDefaults": {
        "build": {
            "cache": true,
            "inputs": ["default", "^production"]
        }
    },
    "tasksRunnerOptions": {
        "default": {
            "runner": "nx/tasks-runners/default",
            "options": { "cacheableOperations": ["build", "test", "lint"] }
        }
    }
}
nx build web
nx affected -t test           # 只跑受影响 package
nx graph                      # 浏览器看 dependency 图

nx affected 是杀器:基于 git diff 判定哪 package 改了,只跑相关。

Turbo vs Nx

Turbo Nx
设计 轻量 task runner 全套 monorepo solution
学习曲线 中高
generator 强(scaffold app / lib)
依赖图 UI
多语言 JS only JS + 插件
远程 cache Vercel / 自托管 Nx Cloud / 自托管
强约束 多("Nx way")

Turbo:你已有 monorepo + 想加 cache → 5 分钟接入。
Nx:从 0 开始 + 想 batteries-included → Nx。

我个人 Turbo 多(不喜欢被框架强约束)。

内部 package 引用

// apps/web/package.json
{
    "dependencies": {
        "@myorg/ui": "workspace:*",
        "@myorg/api-client": "workspace:*"
    }
}

workspace:* 让 pnpm 链接到本地 package(不发 npm)。

// apps/web/app/page.tsx
import { Button } from '@myorg/ui';
import { fetchPosts } from '@myorg/api-client';

跟普通 npm package 一样 import。

shared tsconfig

// packages/tsconfig/base.json
{
    "compilerOptions": {
        "target": "ES2022",
        "module": "ESNext",
        "strict": true,
        "moduleResolution": "Bundler"
    }
}
// apps/web/tsconfig.json
{
    "extends": "@myorg/tsconfig/base",
    "compilerOptions": { ... }
}

config 一处改 全 repo 受用。

CI 优化

# GitHub Actions
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm turbo build test lint --concurrency=4

Turbo 跟 GH cache action 配合 → cache 远程拉。
PR 时间从 15 分钟 → 3 分钟(动几个 package 重 build,其余 cache)。

真实 case

我们 monorepo 演化:

最初: 单 Next.js 项目
  ↓
加 admin dashboard (Next.js) → 共享 component 想抽
  ↓
建 monorepo + pnpm workspace + Turbo
  ↓
packages/ui (shared component)
packages/api-types (类型 + zod schema)
packages/tsconfig
apps/web, apps/admin, apps/marketing
  ↓
后来加 packages/api-client (axios + types)
  ↓
team grow 到 8 人 → 加 remote cache

效果:

  • 改 ui component → web + admin 一起测
  • 共享 type 跨 frontend / backend(Node 后端也 import api-types)
  • CI 快 5x
  • 新成员 onboarding:clone 1 repo 全 setup

不要过早 monorepo

单 app + < 3 人 → 不必。
N app + shared code → monorepo + Turbo。

与 yarn workspaces / npm workspaces

pnpm 是 monorepo 最强:

  • 严格 dependency 隔离
  • 链接速度快
  • workspaces 原生

npm workspaces / yarn workspaces 也行但 pnpm 主流。

踩过的坑

  1. 依赖循环@myorg/ui 依赖 @myorg/utils → utils 依赖 ui →
    build 报错。nx graph / turbo 看依赖图理清。

  2. build output 没指定:turbo 不知道 cache 啥 → cache miss 一直。
    "outputs": [".next/**", "dist/**"] 必填。

  3. env 变量影响 cache:env 改了应该 invalidate cache。
    "env": ["NODE_ENV"] 配置。

  4. monorepo 大 后 IDE 慢:100+ package 后 TS server 慢。
    project references + 限 indexing scope。

  5. publish 仍麻烦:内部 package 不 publish OK。但要 publish
    shared design system → changesets 工具帮 version + changelog。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。