mcpField
M
monorepo-management

monorepo-management

github.com/curiositech开发263139 次安装2 天前更新
代码仓库

关于 monorepo-management

面向设计或维护 JavaScript 和 TypeScript 单体仓库的团队,提供架构与工具配置指导,涵盖工作区、依赖图、任务缓存、CI 和版本管理。

主要功能

  1. 01单体仓库工具选择
  2. 02工作区包结构设计
  3. 03依赖图与任务流水线
  4. 04远程缓存配置
  5. 05按变更筛选 CI 包
  6. 06使用 Changesets 管理版本

适用场景

  1. 01为新单体仓库选择工具
  2. 02配置工作区包与构建顺序
  3. 03仅对变更包运行 CI

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/curiositech/some_claude_skills --skill monorepo-management

安装后在 Claude Code 中输入/monorepo-management确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.claude-plugin/
plugin.json
references/
turborepo-patterns.md
workspace-architecture.md

详细介绍

Monorepo 管理

JavaScript 和 TypeScript 项目的 Monorepo 工具与工作区架构专家。涵盖工具选择、任务流水线配置、依赖关系图管理、CI 优化和版本策略。

适用场景

适用于:

  • 为新建或迁移的仓库选择 Turborepo、Nx、Lerna 或 Rush
  • 配置 turbo.json 任务流水线、远程缓存和 Docker 裁剪
  • 规划工作区包结构:应用、包、共享配置、内部库
  • 设置 pnpm 或 npm 工作区及正确的提升规则
  • 消除工作区包之间的循环依赖
  • 配置基于路径的 CI,并检测受影响的包
  • 使用 Changesets 管理包版本和变更日志

不适用于:

  • Git 子模块或多仓库协作 → 单独讨论利弊
  • 面向非 JavaScript Monorepo 的 Bazel、Pants 或 Buck
  • 单包仓库设置 → 使用项目级工具技能
  • Monorepo 内服务的容器编排 → 使用 docker-containerization

工具选择决策树

flowchart TD
    A[Monorepo tool needed] --> B{Team size and complexity?}
    B -->|Small team, apps-first| C{Need plugin ecosystem?}
    B -->|Large org, many teams| D[Rush or Nx]
    C -->|No — just fast builds| E[Turborepo]
    C -->|Yes — code gen, generators| F[Nx]
    D --> G{Microsoft/enterprise patterns?}
    G -->|Yes| H[Rush]
    G -->|No| I[Nx]
    E --> J{Package manager preference?}
    J -->|pnpm — recommended| K[pnpm + Turborepo]
    J -->|npm or yarn| L[npm/yarn workspaces + Turborepo]
    I --> M[Nx Cloud for remote caching]
    H --> N[Rush's own cache]
    K --> O[Vercel Remote Cache\nor self-hosted]
工具对比摘要
工具最适合远程缓存学习曲线
Turborepo以应用为主、快速构建、简单配置Vercel / 可自托管低
Nx库较多、代码生成、插件生态系统Nx Cloud / 可自托管中
Rush企业级、Microsoft 技术栈、严格隔离自定义高
Lerna遗留项目——从中迁移无(与 Turborepo 搭配使用)低

新项目建议:从 pnpm 工作区 + Turborepo 开始。如果需要高级代码生成或项目依赖图可视化,再迁移到 Nx。


工作区架构决策树

flowchart TD
    A[New package in monorepo?] --> B{Who consumes it?}
    B -->|Internal only, not published| C[Internal package:\nno versioning, workspace: protocol]
    B -->|Published to npm| D[Published package:\nChangesets, semver, CHANGELOG]
    B -->|Shared config only| E[Config package:\neslint-config-*, tsconfig-*]
    C --> F{What type?}
    F -->|UI components| G[packages/ui]
    F -->|Business logic / utilities| H[packages/utils or packages/core]
    F -->|Shared types| I[packages/types]
    F -->|API client| J[packages/api-client]
    D --> K[packages/publishable-name]
    A --> L{Is it an application?}
    L -->|Yes| M[apps/ directory:\nnext-app, api, docs-site]

依赖关系图和构建流水线

graph LR
    A[apps/web] --> B[packages/ui]
    A --> C[packages/utils]
    A --> D[packages/types]
    E[apps/api] --> C
    E --> D
    B --> D
    F[packages/ui-icons] --> D
    B --> F

    style A fill:#4a9d9e,color:#fff
    style E fill:#4a9d9e,color:#fff
    style B fill:#6b7280,color:#fff
    style C fill:#6b7280,color:#fff
    style D fill:#9ca3af
    style F fill:#9ca3af

图表解读:构建顺序从依赖项流向依赖方。packages/types(无依赖项)最先构建。packages/ui 在 types 之后构建。apps/web 最后构建。Turborepo 和 Nx 会自动计算此依赖关系图——你定义任务依赖项,它们负责安排执行顺序。


Turborepo 配置

turbo.json — 任务流水线
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "package.json", "tsconfig.json"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "inputs": ["src/**/*.ts", "src/**/*.tsx", "**/*.test.ts", "**/*.test.tsx"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "inputs": ["src/**/*.ts", "src/**/*.tsx", ".eslintrc*"]
    },
    "typecheck": {
      "dependsOn": ["^build"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

关键概念:

  • ^build 表示“先构建所有依赖项,再构建此包”
  • inputs 决定缓存键——输入范围之外的更改不会使缓存失效
  • outputs 会存入缓存,并在命中缓存时恢复
  • 对于长时间运行的任务(开发服务器、监视器),使用 cache: false
  • 对于不会退出的任务,使用 persistent: true
运行任务
# Run build for all packages
turbo build

# Run only for packages affected by changes since main branch
turbo build --filter=...[origin/main]

# Run for a specific app and its dependencies
turbo build --filter=web...

# Run in parallel across packages
turbo lint typecheck --parallel

# Dry run to see what would execute
turbo build --dry-run
远程缓存
# Login to Vercel remote cache (free for open source)
npx turbo login

# Link to team/project
npx turbo link

# CI: pass token via environment
TURBO_TOKEN=$TURBO_TOKEN turbo build

可自托管的替代方案:turbo-remote-cache 包,或 Turborepo 2.x 内置的 HTTP 缓存服务器。

参阅 references/turborepo-patterns.md,了解部署用 Docker 裁剪、范围过滤和高级流水线模式。


pnpm 工作区

pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
  - 'tools/*'
package.json 工作区依赖项
{
  "dependencies": {
    "@myorg/ui": "workspace:*",
    "@myorg/utils": "workspace:^1.0.0"
  }
}

对于应始终匹配本地版本的内部包,使用 workspace:*。仅当内部包也会发布,且你希望按 semver 范围解析版本时,才使用 workspace:^。

提升控制
# .npmrc — control hoisting behavior
hoist=true
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
shamefully-hoist=false  # never — breaks encapsulation

shamefully-hoist=true 是个陷阱:它会让所有包在所有位置都可用,但会破坏封装性。如果你的工具需要它,应改为修复该工具的依赖项。


使用 Changesets 管理版本

# Initialize in your monorepo
npx changeset init

# Create a changeset when making a change
npx changeset
# Prompts: which packages changed, major/minor/patch, description

# Preview what versions will be bumped
npx changeset status

# Bump versions and update changelogs (CI or release branch)
npx changeset version

# Publish to npm
npx changeset publish
.changeset/config.json
{
  "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": ["@myorg/app-web", "@myorg/app-api"]
}

对于开源包,设置 access: "public"。ignore 列出不应发布到 npm 的应用。

参阅 references/workspace-architecture.md,了解完整的 CODEOWNERS 设置、ESLint 配置共享和共享 tsconfig 模式。


基于路径的 CI(只测试有更改的部分)

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # required for turbo --filter to work correctly

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile

      - name: Build affected packages
        run: pnpm turbo build --filter=...[origin/main]
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

      - name: Test affected packages
        run: pnpm turbo test --filter=...[origin/main]

--filter=...[origin/main] 表示“为与 main 相比文件发生更改的包及所有依赖这些包的包(上游使用方)运行任务。”


反模式

反模式:不缓存任务(每次都重新构建)

新手:“我们运行 turbo build,即使只改了一个包,它也总是重新构建全部 40 个包。”

专家:Turborepo 会通过计算输入的哈希值(源文件 + 环境变量 + turbo 配置)来进行缓存。如果输入没有变化,输出会在几毫秒内从缓存恢复。缓存未命中最常见的原因是缺少 inputs 声明——Turborepo 会退而对整个包目录计算哈希值,其中包括 .DS_Store、node_modules 和 IDE 配置等经常变化的文件。

检测:运行 turbo build --verbosity=2,查看各个包旁边是否显示“MISS”。检查 .turbo 缓存条目是否存在,以及两次运行之间的哈希值是否一致。

修复:在 turbo.json 中显式声明 inputs,只包含会影响构建输出的文件。显式声明 outputs,以便缓存知道要存储哪些内容。将非源文件添加到 .gitignore 和 .turboignore。

大模型常见错误:教程经常省略 inputs 和 outputs,因为可运行的演示不需要它们。生产仓库需要显式声明,否则缓存命中率会一直接近 0%。


反模式:工作区包之间存在循环依赖

新手:“packages/auth 从 packages/api-client 导入内容,而 packages/api-client 又从 packages/auth 导入内容——这有问题吗?”

专家:有。循环依赖会导致无法确定构建顺序。Turborepo 检测到循环依赖时会报错。更重要的是,循环依赖意味着设计存在缺陷:两个包的关注点相互纠缠。解决方法是将共享类型或工具提取到第三个包中,让两个包都可以导入它,而不会形成循环。

检测:

# Turborepo detects cycles and refuses to run
turbo build  # "Error: Package graph cycle detected"

# Manual detection with madge
npx madge --circular --extensions ts packages/

修复:

Before:
  packages/auth → packages/api-client → packages/auth (cycle!)

After:
  packages/types (new: shared auth types, no dependencies)
  packages/api-client → packages/types
  packages/auth → packages/types
  packages/auth → packages/api-client (one-way, no cycle)

时间线:这不是新问题——自 npm v1(2010)以来,循环依赖一直是 JavaScript 打包中的问题。它在单体仓库中特别常见,是因为工作区包让开发者可以轻松跨包边界导入内容,而无需考虑依赖方向。


反模式:在 pnpm 中使用 shamefully-hoist=true

新手:“我的 CLI 工具找不到它的同级依赖。我会在 .npmrc 中添加 shamefully-hoist=true 来解决。”

专家:shamefully-hoist 会让 pnpm 的行为类似 npm/yarn classic,将所有包放在一个扁平的 node_modules 中。这能“解决”眼前的问题,却破坏了 pnpm 的严格隔离,而这正是使用 pnpm 的全部理由。正确的解决方法是将缺少的同级依赖添加到需要它的包中,或者使用 public-hoist-pattern,只提升特定的所需包。

检测:检查 pnpm 工作区中是否有任何 .npmrc 包含 shamefully-hoist=true。


参考资料

  • references/turborepo-patterns.md — 配置 turbo.json、设置远程缓存、使用 Docker 剪枝进行部署或调试缓存未命中时,请参阅此文档。
  • references/workspace-architecture.md — 设计包边界、共享 ESLint/TypeScript 配置、设置 CODEOWNERS 或规划从单仓库迁移到单体仓库时,请参阅此文档。

评论

支持 Markdown