关于 monorepo-management
面向设计或维护 JavaScript 和 TypeScript 单体仓库的团队,提供架构与工具配置指导,涵盖工作区、依赖图、任务缓存、CI 和版本管理。
主要功能
- 01单体仓库工具选择
- 02工作区包结构设计
- 03依赖图与任务流水线
- 04远程缓存配置
- 05按变更筛选 CI 包
- 06使用 Changesets 管理版本
适用场景
- 01为新单体仓库选择工具
- 02配置工作区包与构建顺序
- 03仅对变更包运行 CI
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill monorepo-management安装后在 Claude Code 中输入/monorepo-management确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.claude-plugin/plugin.jsonreferences/turborepo-patterns.mdworkspace-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 或规划从单仓库迁移到单体仓库时,请参阅此文档。