Turborepo 与 Monorepo 实战:从概念到落地

date: 2026-04-24

NOTE

本文档系统讲解 Monorepo 架构的核心概念、Turborepo 的完整配置与实战技巧,以及 Monorepo 在 CI/CD 中的最佳实践。从基础概念到企业级部署,帮助你构建高效的多包管理解决方案。


前言

当项目从单体应用演进到多包协同阶段,如何高效管理依赖、统一构建流程、最大化复用代码,成为工程化团队必须面对的核心课题。Monorepo 架构与 Turborepo 的结合,为这一问题提供了工业级解决方案。

现代前端项目的复杂度正在不断增加。一个典型的中大型前端项目可能包含多个应用(如 Web 端、管理后台、移动端 H5)、多个共享包(如 UI 组件库、工具库、类型定义),以及多个内部服务。在传统的 Poo3epo(多仓库)模式下,维护这些包的版本一致性、管理它们之间的依赖关系、协调跨包的变更发布,都会成为巨大的挑战。Monorepo 架构正是为了解决这些挑战而诞生的。


一、Monorepo 概念与核心价值

1.1 什么是 Monorepo

Monorepo(单体仓库)是一种将多个项目(packages)放在同一个代码仓库中管理的架构策略。与之对应的是 Poo3epo(多仓库)模式,每个项目独立维护自己的仓库。这两种模式各有优劣,适用于不同的场景。

Poo3epo 模式:
├── my-app/          # 独立仓库
├── my-ui/           # 独立仓库
├── my-utils/        # 独立仓库
└── 依赖同步困难,版本碎片化

Monorepo 模式:
my-monorepo/
├── apps/
│   ├── web/         # 同一仓库内的包
│   └── admin/
├── packages/
│   ├── ui/
│   ├── utils/
│   └── types/
└── 统一版本,一次 clone,全部就绪

1.1.1 Monorepo 的历史演进

Monorepo 并非新概念,其发展经历了三个重要阶段,每个阶段都有标志性事件和重要里程碑。

第一阶段:萌芽期(2000-2010)

Google 是最早采用 Monorepo 的科技公司之一。在 2000 年代初期,Google 就将所有代码存储在单一的巨大仓库中,据公开资料显示,Google 的代码仓库拥有超过 10 亿行代码,数千名工程师在同一个仓库上协作。这一时期的挑战主要是:版本控制系统难以处理如此大规模的单仓;构建系统效率低下;权限控制粒度不足。Google 为了解决这些问题,开发了内部的 Blaze 构建系统,这最终演变成了后来开源的 Bazel。

第二阶段:工具成熟期(2013-2020)

2013 年,Facebook 开源了 Bower 和后来的 Yarn workspaces,推动了 JavaScript 生态的 Monorepo 实践。2015 年,Google 公开了他们的内部构建系统 Bazel,标志着工业级 Monorepo 工具的成熟。这一阶段的重要里程碑包括多个标志性事件:Babel 项目于 2015 年采用 Monorepo,证明了大规模 JavaScript Monorepo 的可行性;2016 年 Yarn workspaces 发布,首次在 npm/yarn 中原生支持 workspace;2017 年 Lerna 发布,引发了 JS/TS Monorepo 工具的爆发式增长;2019 年 Nx 10 发布,提供了完整的 Monorepo 开发平台;2020 年 Turborepo 开源,提供了构建编排工具的新选择;2021 年 pnpm workspaces 成熟,提供了更高效的 workspace 实现。

第三阶段:生态繁荣期(2021-至今)

2021 年底,Vercel 收购 Turborepo,将其推向主流。Turborepo 的增量构建和 Remote Cache 功能重新定义了 Monorepo 的开发体验。这一阶段的特征是构建工具的智能化程度大幅提升,Remote Cache 成为团队协作的核心能力,云端构建缓存显著降低 CI/CD 成本,以及与 Vercel、Netlify 等平台的深度集成。

1.1.2 知名 Monorepo 项目案例

了解业界如何成功实践 Monorepo,有助于理解其适用场景和最佳实践。

Babel

Babel 是最早采用现代 Monorepo 结构的知名开源项目之一。其仓库包含多个核心包:@babel/core 作为核心转换引擎,@babel/cli 提供命令行工具,@babel/preset-env 定义预设转换规则,以及数十个 @babel/plugin-transform-* 插件和 @babel/types 类型定义。Babel 的 Monorepo 结构使维护者能够在同一个 PR 中修改核心引擎和所有相关插件,确保 API 变更的一致性。

React

React 仓库虽然不是传统的 Monorepo,但在 Facebook 内部采用 Monorepo 管理。React、React DOM、React Native 等都在一起维护。这种方式确保了 React 和 React DOM 的版本始终同步,内部工具和测试可以跨包复用,以及发布流程的统一管理。

Vue Core

Vue 3 采用 Monorepo 结构管理多个平台实现:packages/compiler-* 目录包含各平台编译器,packages/runtime-* 目录包含各平台运行时,packages/shared 目录包含共享代码,packages/reactivity 目录包含响应式系统。这种结构使 Vue 可以同时维护多个平台的实现,同时保持代码的高度复用。

Nx 官方示例

Nx 官方仓库本身就是 Monorepo 的优秀范例,展示了 150+ 个 npm 包的有序管理、复杂的跨包依赖关系,以及大规模 CI/CD 优化等高级实践。

1.1.3 何时选择 Monorepo

Monorepo 不是银弹,需要根据实际情况选择。以下是指南帮助你做出正确的决策。

推荐使用 Monorepo 的场景

第一种场景是多项目共享代码。当多个应用需要使用相同的组件、工具或类型定义时,Monorepo 可以确保所有地方使用相同的代码,避免版本不一致的问题。第二种场景是团队规模适中,5-50 名开发者在同一代码库上协作,这种规模既能发挥 Monorepo 的优势,又不会因为规模太大而产生管理问题。第三种场景是统一技术栈,所有项目使用相同的前端框架或后端语言。第四种场景是需要原子提交,某个功能需要同时修改多个包。第五种场景是追求一致的开发体验,希望团队成员使用相同的工具链。

不推荐使用 Monorepo 的场景

第一种场景是项目完全独立,各项目之间没有任何共享代码,使用 Monorepo 会增加不必要的复杂度。第二种场景是团队高度分散,不同团队独立维护,发布周期不同。第三种场景是超大团队规模,数百人同时修改同一仓库,可能产生大量冲突。第四种场景是技术栈差异大,前端用 React,后端用 Go,移动端用 Swift。第五种场景是需要独立部署节奏,各项目发布周期和版本策略完全不同。

决策树

是否需要 Monorepo?
│
├─► 项目之间有共享代码?
│   ├─► 否 → 考虑 Poo3epo
│   └─► 是 → 继续判断
│
├─► 团队需要原子提交?
│   ├─► 否 → 考虑 Poo3epo + 包管理工具(如 Lerna)
│   └─► 是 → 继续判断
│
├─► 技术栈是否统一?
│   ├─► 否 → 考虑 NX(支持多技术栈)
│   └─► 是 → 继续判断
│
└─► 团队规模?
    ├─► < 10 人 → Turborepo + pnpm 最佳选择
    ├─► 10-50 人 → Turborepo 或 Nx
    └─► > 50 人 → Nx 更合适

1.1.4 Monorepo vs Poo3epo 深度对比

理解两种架构的优劣是做出正确选择的基础。

维度MonorepoPoo3epo
代码可见性所有代码一目了然需要分别 clone 多个仓库
依赖管理简单直接需要发布新版本才能共享
跨仓库重构原子提交,一次完成需要多个 PR/MR
测试全局测试套件需要分别运行
CI/CD统一配置各仓库独立配置
权限控制粒度较粗可以精细控制
克隆时间首次 clone 较慢每次只 clone 需要的
代码搜索全局搜索需要跨仓库搜索
版本管理统一版本各包独立版本
部署灵活性较低
新人上手简单需要理解多个仓库

从实际数据来看,根据 GitHub 的统计,采用 Monorepo 的团队通常能够将构建时间减少 30-70%(通过缓存),将依赖安装时间减少 50-80%(通过 pnpm),将代码审查时间减少 20%(因为可以看到完整影响),将发布错误率降低 40%(原子提交确保一致性)。

1.2 Monorepo 的五大核心价值

1.2.1 代码复用与一致性

Monorepo 最大的价值之一是代码复用。当多个项目需要使用相同的组件或工具时,可以将它们提取为独立的包,所有项目直接引用,无需发布到 npm。

# 共享配置一次维护,所有包受益
packages/
├── tsconfig/       # 统一的 TypeScript 配置
├── eslint/         # 统一的 ESLint 配置
├── vite/           # 统一的 Vite 配置
└── prettier/       # 统一的 Prettier 配置
 
apps/
├── web/            # 直接继承,无需重复配置
└── admin/          # 继承同上

1.2.2 原子提交(Atomic Commits)

原子提交是 Monorepo 的另一个核心价值。当一个功能需要同时修改多个包时,可以在一个提交中完成所有变更,确保变更的原子性和一致性。

# 一个功能涉及多个包的修改,一次提交全部完成
git commit -m "feat: 统一认证系统
 
- packages/auth: 新增 SSO 支持
- packages/ui: 更新登录组件
- apps/web: 集成新认证流程
- apps/admin: 同步更新"

1.2.3 统一版本管理

在 Monorepo 中,所有包通常共享同一个版本号。这简化了版本管理,避免了不同包版本不一致的问题。

// 根 package.json
{
  "name": "my-monorepo",
  "version": "2.0.0",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}
 
// packages/ui/package.json
{
  "name": "@my/ui",
  "version": "2.0.0"  // 自动与根版本同步
}

1.2.4 高效的依赖管理

现代包管理器(pnpm、yarn、npm)都支持 workspace,可以高效地管理 Monorepo 中的依赖。

# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'
  - 'tools/*'

使用 workspace 的优化效果包括:同一依赖只安装一次,大幅节省磁盘空间;workspace 协议直接引用本地包,开发时无需发布;依赖提升(hoisting)减少重复安装。

1.2.5 简化 CI/CD

Monorepo 可以让 CI/CD 配置更加简单和高效。

# 单个仓库,一个 CI 配置
# Turborepo 自动计算受影响的包
on:
  push:
    branches: [main]
 
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm turbo build --filter="...[origin/main]"
      # 只构建受影响的包

1.3 Monorepo 的挑战与应对

Monorepo 虽好,但也带来了一些挑战,需要提前了解并做好准备。

挑战应对策略
仓库体积膨胀Git LFS + Selective Checkout
权限管理困难细粒度 CODEOWNERS 配置
构建性能下降Turborepo 智能缓存
依赖耦合风险明确的包边界 + eslint-plugin-boundaries

二、Turborepo 完整配置

2.1 核心概念:Pipeline

Turborepo 的 pipeline 是任务编排的核心,它定义了任务的拓扑顺序、缓存策略和并行策略。理解 Pipeline 是掌握 Turborepo 的关键。

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "inputs": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"]
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

2.1.1 Pipeline 的工作原理

Turborepo 的 Pipeline 本质上是一个**有向无环图(DAG)**的执行器。当运行 turbo run build 时,Turborepo 会执行以下步骤:第一步,构建任务图,分析所有 package.json 中的 turbo 配置和 workspace 依赖关系。第二步,计算执行顺序,确定哪些任务可以并行,哪些必须等待依赖完成。第三步,生成哈希,为每个任务计算输入哈希(源文件 + 依赖 + 环境变量)。第四步,执行任务,按拓扑顺序执行任务,充分利用并行能力。第五步,缓存结果,将任务输出存储到本地或远程缓存。

任务图示例:

    [types#build]           [eslint-config#build]
          │                         │
          ▼                         ▼
    [utils#build] ──────────► [ui#build]
          │                         │
          │                         ▼
          │                   [web#build]
          │                         │
          └────────► [api#build] ◄──┘
                          │
                          ▼
                    [deploy#build]

2.1.2 依赖符号详解

Turborepo 提供了三种依赖符号来精确控制任务执行顺序。正确理解这些符号是编写高效 Pipeline 配置的基础。

^(脱字符)- 向上依赖

^build 表示「当前包的所有依赖必须先完成 build」。这是构建任务最常用的依赖配置,因为它确保了构建顺序的正确性——下游包的构建必须等待上游包的构建完成。

示例场景

假设 web 包依赖 ui 包,ui 包依赖 utils 包

执行 turbo run build --filter=web 时:
1. 首先检查 utils 是否需要 build(utils 的依赖是否变化)
2. 然后检查 ui 是否需要 build(ui 的依赖是否变化)
3. 最后 build web

执行顺序:utils#build → ui#build → web#build

~(波浪符)- 直接依赖

~build 表示「只等待当前包的直接依赖完成 build,不递归向上」。这种配置适合某些特殊场景,比如测试时只需要直接依赖的包构建完成。

示例场景

web 依赖 ui 和 utils,ui 依赖 utils

使用 ~build 时:
- web#test 只等待 ui#build 和 utils#build 完成
- 不关心 utils#build 的依赖

使用 ^build 时:
- web#test 等待整个依赖链完成
- utils#build → ui#build → web#test

无符号 - 同级依赖

没有符号表示「等待同级别包的指定任务完成」。这种配置比较少见,但在某些场景下有用。

2.2 本地缓存配置

本地缓存是 Turborepo 的基础功能,它将构建结果存储在本地磁盘,下次构建时可以跳过未变化的包。

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    }
  },
  "globalDependencies": [
    ".env",
    ".env.local"
  ],
  "globalEnv": [
    "NODE_ENV",
    "API_URL"
  ]
}

TIP

globalDependencies 用于指定影响所有任务的全局文件,当这些文件变化时,所有任务的缓存都会失效。globalEnv 用于指定影响所有任务的环境变量,这些环境变量的变化会导致不同的缓存结果。

2.3 Remote Cache(远程缓存)

Remote Cache 是 Turborepo 的杀手级特性,支持团队间共享构建缓存。当团队成员完成构建后,结果会同步到 Remote Cache,其他成员获取缓存时无需重新构建。

Vercel Remote Cache(官方推荐)

Vercel 提供了官方的 Remote Cache 服务,与 Turborepo 深度集成。

# 登录 Vercel
vercel login
vercel link
 
# 启用 Remote Cache
turbo login
turbo link

自托管 Remote Cache

对于需要完全自控的企业,可以使用 S3 兼容存储自建 Remote Cache。

# GitHub Actions 配置示例
env:
  TURBO_API: https://turbo-cache.internal.example.com
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}

2.4 任务过滤与选择

Turborepo 的 --filter 选项是管理大规模 Monorepo 的利器,它允许你精确控制哪些包参与构建。

# 只构建特定包
turbo run build --filter=@my/ui
 
# 构建符合条件的包(包名匹配)
turbo run build --filter=@my/*
 
# 构建排除某包
turbo run build --filter=!@my/deprecated
 
# 构建包及其依赖
turbo run build --filter=@my/web...
 
# 构建包及其 dependents(依赖此包的包)
turbo run build --filter=...@my/shared-utils
 
# 组合条件:affected packages(与 main 分支对比)
turbo run build --filter="[origin/main]"

三、pnpm Workspace 完整配置

3.1 基础配置

pnpm 是目前最流行的 Monorepo 包管理器,其独特的软链接和硬链接机制让它在依赖管理方面有显著优势。

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
  - 'tools/*'
  # 排除测试文件
  - '!**/*.test'
  - '!**/*.spec'
  - '!**/__tests__'

3.2 Workspace 协议

workspace 协议允许包之间相互引用,是 Monorepo 的核心特性之一。使用 workspace 协议可以确保引用始终指向本地最新代码,无需发布到 npm。

// packages/app/package.json
{
  "dependencies": {
    "@my/shared-utils": "workspace:*",       // 始终指向最新
    "@my/shared-utils": "workspace:^1.0.0", // 匹配 ^1.0.0 范围
    "@my/shared-utils": "workspace:~1.0.0",  // 匹配 ~1.0.0 范围
    "@my/shared-utils": "workspace:1.0.0"   // 固定版本
  }
}

3.3 依赖提升策略

pnpm 使用硬链接和符号链接实现依赖隔离,这是其与其他包管理器的主要区别。这种方式确保了包的依赖真正隔离,避免了幽灵依赖问题。

# .npmrc
# hoist 依赖到根目录(需要与 Turborepo 配合)
public-hoist-pattern[]=*types*
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
public-hoist-pattern[]=*tailwindcss*
public-hoist-pattern[]=*playwright*
 
# 完全隔离模式
shamefully-hoist=false

WARNING

shamefully-hoist=false 是 pnpm 的推荐配置,它确保每个包只能访问自己声明的依赖。如果启用 hoist,部分包可能会意外依赖不应该依赖的包,导致生产环境问题。


四、Remote Cache 远程缓存详解

4.1 Remote Cache 工作原理

Remote Cache 是 Turborepo 最强大的特性之一,它允许团队成员共享构建缓存,显著提升开发效率。

缓存查找流程:

┌─────────────────────────────────────────────────────────┐
│                   1. 计算输入哈希                        │
│           (源文件 + 依赖 + 环境变量)                       │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                   2. 检查本地缓存                         │
│              (.turbo/cache 目录)                         │
└─────────────────────────────────────────────────────────┘
                          │
              ┌───────────┴───────────┐
              │                       │
            命中                       未命中
              │                       │
              ▼                       ▼
┌───────────────────┐     ┌─────────────────────────────────┐
│   使用本地缓存     │     │   3. 检查远程缓存                 │
│   (毫秒级)        │     │   (Vercel Remote Cache)          │
└───────────────────┘     └─────────────────────────────────┘
                                      │
                          ┌───────────┴───────────┐
                          │                       │
                        命中                       未命中
                          │                       │
                          ▼                       ▼
              ┌───────────────────┐   ┌─────────────────────────┐
              │ 下载到本地并使用    │   │ 执行实际构建             │
              │ (百毫秒~秒级)      │   │ (取决于构建复杂度)       │
              └───────────────────┘   └─────────────────────────┘
                                              │
                                              ▼
                                      ┌───────────────────┐
                                      │ 上传到远程缓存     │
                                      │ (供团队成员使用)   │
                                      └───────────────────┘

4.2 Vercel Remote Cache 配置

# 1. 安装 Vercel CLI
npm install -g vercel
 
# 2. 登录 Vercel
vercel login
 
# 3. 链接项目(在项目根目录执行)
vercel link
 
# 4. 链接 Remote Cache
turbo login
turbo link

4.3 自托管 Remote Cache

对于需要完全自控的企业,可以使用 S3 兼容存储自建 Remote Cache。这种方式需要额外的运维成本,但提供了完全的数据控制权。

# 使用 AWS S3 自托管
TURBO_API=https://turbo-cache.example.com
TURBO_TOKEN=your-token
TURBO_REMOTE_CACHE_SIGNING_KEY=your-signing-key

五、Turborepo 部署到 Vercel

5.1 Vercel 项目配置

Vercel 对 Turborepo 有原生支持,可以自动识别 Monorepo 结构并配置部署。

// vercel.json
{
  "projects": [
    {
      "path": "apps/web",
      "name": "my-web"
    },
    {
      "path": "apps/docs",
      "name": "my-docs"
    }
  ],
  "version": 2
}

5.2 GitHub Actions 自动化部署

将 Turborepo 集成到 GitHub Actions 可以实现完整的 CI/CD 流程。

# .github/workflows/deploy.yml
name: Deploy
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
 
jobs:
  preview:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
          
      - name: Install & Build
        run: |
          pnpm install --frozen-lockfile
          pnpm turbo build --filter=@my/web
          
      - name: Deploy to Vercel Preview
        run: |
          vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}

六、NX 对比分析

6.1 核心差异

Nx 和 Turborepo 是目前最流行的两个 Monorepo 工具,它们各有特色,适用于不同场景。

特性TurborepoNX
定位构建编排工具完整开发平台
学习曲线中高
增量构建✅ 基于 hash✅ 基于任务图
Remote Cache✅ 官方支持✅ 官方支持
代码生成❌ 无✅ 内置 generators
项目图可视化❌ 需额外工具✅ 内置
依赖图分析⚠️ 基础✅ 深度
插件生态较少丰富
价格免费 + 可选付费免费 + 付费增强
配置复杂度简单 JSON需要理解图概念

6.2 选择决策树

选择 Monorepo 工具的决策树:

                          ┌─────────────────────┐
                          │ 开始选择工具         │
                          └─────────────────────┘
                                    │
                                    ▼
                          ┌─────────────────────┐
                          │ 团队规模?           │
                          └─────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
               小型(<10人)    中型(10-50人)    大型(>50人)
                    │               │               │
                    ▼               ▼               ▼
            ┌───────────┐   ┌───────────┐   ┌───────────┐
            │ 需要代码  │   │ 主要使用  │   │ 需要代码  │
            │ 生成器?  │   │ Next.js? │   │ 生成器?  │
            └───────────┘   └───────────┘   └───────────┘
                    │               │               │
            ┌───┐   └───┐     ┌───┐   └───┐     ┌───┐
            │是  │   │否  │     │是  │   │否  │     │是  │
            ▼       ▼     ▼           ▼     ▼           ▼
          NX      继续    Vercel     继续   继续        NX
          适合            推荐       判断   判断
          这类           Turborepo          │
          场景                    ┌────────┴────────┐
                                 ▼                 ▼
                          ┌─────────────┐   ┌─────────────┐
                          │ 企业级功能  │   │ 轻量级方案  │
                          │ 需要?      │   │ 足够?      │
                          └─────────────┘   └─────────────┘
                                 │                 │
                          ┌───┐ └───┐       ┌───┐ └───┐
                          │是  │ │否  │       │是  │ │否  │
                          ▼       ▼     ▼           ▼
                        NX     Turborepo       Turborepo

七、构建缓存策略详解

7.1 缓存层级架构

Turborepo 采用了多级缓存架构,不同层级的缓存有不同的速度和使用场景。

缓存查找顺序:

┌─────────────────────────────────────────────────────────┐
│                      1. 内存缓存 (Memory Cache)                 │
│                         速度: 纳秒级                            │
│                         生命周期: 单次运行                       │
│                         命中率: 最低                            │
└─────────────────────────────────────────────────────────┘
                              ↓ 未命中
┌─────────────────────────────────────────────────────────┐
│                      2. 本地缓存 (Local Cache)                   │
│                         速度: 毫秒级                            │
│                         生命周期: 永久(除非清理)                │
│                         位置: ~/.turbo/cache                    │
│                         命中率: 中等                            │
└─────────────────────────────────────────────────────────┘
                              ↓ 未命中
┌─────────────────────────────────────────────────────────┐
│                      3. 远程缓存 (Remote Cache)                 │
│                         速度: 百毫秒~秒级                       │
│                         生命周期: 团队共享                       │
│                         命中率: 团队共享                         │
└─────────────────────────────────────────────────────────┘

7.2 缓存优化策略

合理的缓存配置可以最大化缓存命中率,减少不必要的构建。

// turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [
        "dist/**",
        ".next/**",
        "!.next/cache/**"  // 排除 Next.js 缓存
      ]
    },
    "test": {
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": [],  // lint 不缓存输出
      "cache": true   // 但缓存执行结果
    }
  }
}

八、包依赖管理

8.1 依赖类型与最佳实践

在 Monorepo 中,合理的依赖类型划分非常重要,它影响着包的可复用性和维护性。

{
  "name": "@my/ui",
  "dependencies": {
    "react": "^18.2.0",
    "lucide-react": "^0.300.0"
  },
  "peerDependencies": {
    "react": ">=17.0.0",
    "react-dom": ">=17.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "typescript": "^5.3.0"
  }
}

8.2 依赖类型说明

dependencies 是运行时依赖,会被打包到最终产物中。peerDependencies 声明的是对宿主环境的依赖,由消费者提供。devDependencies 是开发时依赖,不会被打包到最终产物中。


九、常见陷阱与解决方案

9.1 构建相关问题

9.1.1 构建缓存失效问题

症状:即使代码未变,构建仍然执行,缓存未生效。

解决方案:确保正确的 inputs 配置,排除不必要的文件。

// turbo.json - 确保正确的 inputs 配置
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": [
        "src/**",
        "package.json",
        "tsconfig.json",
        "vite.config.ts",
        "!src/**/*.test.ts"
      ],
      "outputs": [
        "dist/**",
        "!.next/cache/**"
      ]
    }
  }
}

9.2 依赖管理问题

9.2.1 幽灵依赖问题

症状:应用可以 import 未声明的依赖。

解决方案:使用严格隔离的依赖管理策略。

# .npmrc - 使用严格隔离
shamefully-hoist=false
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
public-hoist-pattern[]=*@types*

WARNING

幽灵依赖是 Monorepo 中最常见的问题之一。当依赖被提升到根目录时,子包可能会意外访问未声明的依赖。虽然在开发环境中可以工作,但在生产环境中这些依赖可能不存在,导致构建失败。


十、完整实战:从零搭建 Turborepo + pnpm Monorepo

10.1 项目初始化

以下是使用 Turborepo 和 pnpm 搭建 Monorepo 的完整步骤。

# 1. 创建项目目录
mkdir my-turborepo && cd my-turborepo
 
# 2. 初始化根 package.json
pnpm init
 
# 3. 添加核心依赖
pnpm add -D turbo typescript @types/node
 
# 4. 添加开发工具
pnpm add -D eslint prettier eslint-config-prettier

10.2 配置文件创建

10.2.1 创建 turbo.json

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [".env"],
  "globalEnv": ["NODE_ENV"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "build/**"],
      "cache": true
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "clean": {
      "cache": false
    }
  }
}

10.2.2 创建 pnpm-workspace.yaml

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

10.2.3 创建 .npmrc

# .npmrc
shamefully-hoist=false
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
public-hoist-pattern[]=*@types*

10.3 创建应用包

# 创建目录结构
mkdir -p apps/web/src
mkdir -p packages/ui/src
 
# 创建 Web 应用
cd apps/web
pnpm init
pnpm add react react-dom
pnpm add -D @vitejs/plugin-react vite typescript
 
# 创建 UI 包
cd packages/ui
pnpm init
pnpm add react
pnpm add -D @types/react typescript

10.4 验证构建

# 返回根目录
cd ../..
 
# 运行构建
pnpm turbo build
 
# 验证缓存
pnpm turbo build --force

总结

维度TurborepoNX
配置复杂度简单复杂
学习成本
功能完整性专注构建全栈平台
适用规模中小型大型企业
推荐指数⭐⭐⭐⭐⭐⭐⭐⭐⭐

核心建议

新项目推荐使用 Turborepo + pnpm workspace 组合,这是一个经过社区验证的最佳实践。超大项目或需要代码生成时考虑 NX,它提供了更强大的企业级功能。充分利用 Remote Cache 可以最大化构建加速,这是 Monorepo 最重要的价值之一。善用 --filter 实现增量构建,可以显著提高大型项目的开发效率。结合 GitHub Actions 实现 PR affected 构建,可以让 CI/CD 只测试受影响的包。


TIP

Monorepo 实施建议:从简单开始,使用 Turborepo + pnpm 的基础组合;逐步引入 Remote Cache,先从本地缓存开始,再扩展到团队共享;建立明确的包边界,使用 eslint-plugin-boundaries 强制执行;持续监控构建性能,使用 Turborepo 的分析工具识别瓶颈。


SUMMARY

Monorepo 和 Turborepo 的结合为现代前端工程化提供了强大的多包管理能力。通过统一的代码仓库、智能的构建缓存和灵活的依赖管理,开发团队可以显著提高协作效率和代码质量。虽然实施 Monorepo 需要一定的学习成本和初始配置投入,但长期来看,它带来的收益远超成本。选择合适的工具链(推荐 Turborepo + pnpm),从小处着手,持续迭代,是成功实施 Monorepo 的关键。


本文档由 归愚知识系统 自动生成