Skip to content

Monorepo 管理

Monorepo(单一仓库)是将多个项目或包放在同一个 Git 仓库中管理的策略,与之对应的是 Polyrepo(每个项目独立仓库)。

Monorepo 的优缺点

优点

  • 原子提交:跨项目的改动可以在一次提交中完成
  • 代码共享:共享库不需要发布即可引用
  • 统一工具链:统一的 lint、测试、CI/CD 配置
  • 简化依赖管理:内部包版本始终一致
  • 更好的可见性:所有代码在一个地方,易于搜索和理解

缺点

  • 仓库体积大:随着项目增多,仓库越来越庞大
  • 权限粒度粗:难以对特定子项目限制访问
  • 克隆成本高:必须克隆整个仓库
  • CI/CD 复杂:需要判断哪些项目被影响
  • 学习成本:需要工具支持,配置复杂

稀疏检出在 Monorepo 中的应用

稀疏检出(Sparse Checkout)是大型 Monorepo 的核心优化手段:

bash
# 1. 克隆但不检出文件
git clone --filter=blob:none --no-checkout https://github.com/company/monorepo.git
cd monorepo

# 2. 启用 cone 模式(性能最好)
git sparse-checkout init --cone

# 3. 只检出需要的包
git sparse-checkout set packages/frontend packages/shared

# 4. 检出代码
git checkout main

# 现在只有 packages/frontend 和 packages/shared 在工作区
ls packages/
# frontend/
# shared/
# (backend/ 等其他包不在工作区)

# 5. 需要其他包时追加
git sparse-checkout add packages/backend

为不同角色配置稀疏检出:

bash
# 前端开发者
git sparse-checkout set packages/frontend packages/shared packages/ui-lib

# 后端开发者
git sparse-checkout set packages/backend packages/shared packages/proto

# DevOps
git sparse-checkout set .github infrastructure scripts

路径过滤提高性能

在 Monorepo 中,很多 Git 操作支持路径过滤:

bash
# 只查看特定包的提交历史
git log --oneline -- packages/frontend/

# 只 diff 特定包
git diff HEAD~5 HEAD -- packages/backend/

# 只暂存特定包的改动
git add packages/frontend/

# 只获取特定路径的改动(配合稀疏检出)
git fetch --filter=blob:none origin

使用 pathspec 语法:

bash
# 匹配所有 TypeScript 文件(在任意包中)
git add '*.ts'

# 只匹配特定目录下的文件
git add 'packages/frontend/**/*.ts'

# 排除特定目录
git add -- ':!packages/legacy/'

常用 Monorepo 工具与 Git 的配合

Turborepo(React/Node.js 生态)

bash
# 安装
npm install --save-dev turbo

# turbo.json 配置
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"]
    }
  }
}

# 只构建受影响的包(基于 git diff)
turbo build --filter=[HEAD^1]

# 只测试改动的包
turbo test --filter=...[origin/main]

Nx(Angular/React 生态)

bash
# 查看哪些项目受到影响
npx nx affected:projects

# 只测试受影响的项目
npx nx affected:test

# 生成改动影响图
npx nx affected:graph

Lerna(Node.js 传统工具)

bash
# 查看改动的包
npx lerna changed

# 只发布改动的包
npx lerna publish

# 在所有包中运行命令
npx lerna run build

pnpm Workspaces

yaml
# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'
  - 'tools/*'
bash
# 在特定包中运行命令
pnpm --filter frontend build

# 在所有受影响的包中运行(基于 git diff)
pnpm --filter "[HEAD^1]" test

# 并行在所有包中运行
pnpm -r build

Monorepo 的 CI/CD 策略

基于路径过滤的 CI 触发

GitHub Actions 示例:

yaml
# .github/workflows/frontend.yml
name: Frontend CI
on:
  push:
    paths:
      - 'packages/frontend/**'
      - 'packages/shared/**'
      - '.github/workflows/frontend.yml'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          sparse-checkout: |
            packages/frontend
            packages/shared
      - run: pnpm install
      - run: pnpm --filter frontend test

Monorepo 的分支策略

bash
# 功能分支按包命名
git switch -c feature/frontend/new-dashboard
git switch -c fix/backend/auth-bug

# 或者统一命名,在 PR 中指定影响范围
git switch -c feature/user-onboarding-flow
# PR 描述中说明影响 frontend、shared 等

权限管理:CODEOWNERS

使用 CODEOWNERS 文件实现细粒度的代码所有权:

# .github/CODEOWNERS(或 .gitlab/CODEOWNERS)

# 全局默认
*  @platform-team

# 特定包的所有者
/packages/frontend/  @frontend-team
/packages/backend/   @backend-team
/packages/shared/    @architecture-team
/infrastructure/     @devops-team

# 特定类型文件
*.ts    @typescript-leads
*.sql   @database-team

总结

工具特点适用场景
Turborepo构建缓存,速度极快Node.js 项目
Nx功能丰富,插件多Angular/企业级
pnpm Workspaces原生,包管理效率高Node.js 包管理
Lerna传统,发布管理好npm 包管理
sparse-checkoutGit 原生,无额外依赖减少工作区大小

Monorepo 的核心挑战是规模管理:如何让大型仓库依然保持快速的 CI/CD 和流畅的开发体验。结合 sparse-checkout、部分克隆和智能的增量构建工具,可以让 Monorepo 既享受代码统一管理的好处,又不受仓库规模的限制。