返回博客列表
MonorepoTurborepo工程化

Turbo 驱动的 Monorepo 工程化实践

从实际项目出发,分享使用 Turborepo 管理多应用 Monorepo 的架构经验与踩坑总结

作者: ekent·发布于 2026年1月10日

当项目规模从单一应用增长到多端(后端 API、管理后台、移动端)时,代码如何组织就成了一个绕不开的问题。我们在实际项目中采用了 Turborepo 来管理多应用 Monorepo,积累了一些值得分享的经验。

为什么选择 Monorepo

传统的多仓库(Polyrepo)模式下,共享代码靠发 npm 包,版本同步是噩梦。假设后端定义了一个订单接口的类型,前端要用,你需要:发包 → 升版本 → 各项目安装 → 祈祷没有遗漏。

Monorepo 把所有项目放在一个仓库里,共享代码变成了直接引用:

monorepo/
├── apps/
│   ├── api/          # 后端服务
│   ├── admin/        # 管理后台
│   └── mobile/       # 移动端
├── packages/
│   ├── shared/       # 共享类型与常量
│   └── api-client/   # 类型安全的 HTTP 客户端
├── turbo.json
└── package.json

改一个类型定义,所有消费方立即生效,不需要发包流程。

Turborepo 的核心价值

Turborepo 解决的核心问题是构建效率。在 Monorepo 中,你不希望改了一行后端代码,前端也跟着重新构建。

任务编排与缓存

turbo.json 定义了任务之间的依赖关系:

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

"dependsOn": ["^build"] 表示构建当前包之前,先构建它依赖的其他包。Turbo 会自动分析依赖图,并行执行不相互依赖的任务。

关键的是缓存机制——如果输入文件没有变化,Turbo 直接跳过构建,从缓存中恢复输出。在我们的项目中,这让 CI 构建时间从 8 分钟降到了 2 分钟左右。

选择性构建

日常开发中,你通常只关心某一个应用:

# 只构建 admin 及其依赖
turbo build --filter=admin

# 只运行 api 的开发服务器
turbo dev --filter=api

--filter 是日常使用频率最高的参数,避免了每次都全量构建。

工作区依赖管理

使用 pnpm 的 workspace:* 协议管理内部依赖:

{
  "dependencies": {
    "@myapp/shared": "workspace:*",
    "@myapp/api-client": "workspace:*"
  }
}

这里有一个容易踩的坑:共享包必须有正确的 exports 配置。如果 packages/shared 没有在 package.json 中声明入口,消费方会报模块找不到:

{
  "name": "@myapp/shared",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  }
}

类型安全的 API 客户端

Monorepo 最大的红利之一是跨应用的类型安全。我们把 API 的请求/响应类型放在 packages/shared 中,再用一个统一的 HTTP 客户端包装:

// packages/api-client/src/index.ts
import type { OrderResponse, CreateOrderRequest } from '@myapp/shared';

export async function createOrder(data: CreateOrderRequest): Promise<OrderResponse> {
  const res = await fetch('/api/orders', {
    method: 'POST',
    body: JSON.stringify(data),
  });
  return res.json();
}

后端改了字段,TypeScript 编译器会在前端代码中立即报错,而不是等到运行时才发现。

实践中的注意事项

1. 不要过度拆包。如果一段代码只有一个消费方,就别抽成独立包。包的管理本身有成本,只有真正被多方共享时才值得。

2. Docker 构建需要特殊处理。Monorepo 的 Docker 镜像构建不能简单地 COPY . .,否则每个应用的镜像都会包含所有代码。推荐使用 turbo prune 生成精简的构建上下文:

turbo prune --scope=api --docker

3. CI 中启用远程缓存。本地缓存只对个人有效,团队协作需要远程缓存。Vercel 提供了开箱即用的方案,也可以自建缓存服务器。

总结

Monorepo 不是银弹,但当你的项目确实需要多应用共享代码时,Turborepo 提供了一个务实的解决方案。它的核心优势在于:依赖图感知的任务编排、增量构建缓存、以及简洁的配置方式。如果你的团队正在被多仓库的版本同步问题困扰,不妨试试这个方案。