Monorepo 工具链
本页给出可直接运行的 Monorepo 配置:Nx project.json target 与 Turborepo turbo.json pipeline(含 dependsOn 依赖关系)、nx affected 与 turbo run --filter 按影响范围运行、Nx Cloud 与 Turborepo 远程缓存设置、限制跨包导入的 ESLint boundary 规则;让 Agent 配置出增量可复现的 CI 流水线。
SKILL 应写清包边界与 workspace: 协议依赖;内部版本策略(固定、changesets、单版本线)一旦选定就不要在 PR 里临时发明第三种,避免幽灵依赖与隐式提升。
任务图必须声明输入输出与缓存键;对生成代码、环境变量与密文排除规则单列,防止错误命中远程缓存。与 CI 矩阵对齐:分片测试、受影响子集与 main 全量校验的分工要写进技能正文。
一页摘要
- 先定工作区与包发布模型,再选编排器:Nx 偏项目图与插件生态;Turborepo 偏轻量 pipeline 与
turbo.json任务声明。 - 缓存正确性优先于命中率:inputs/outputs、环境变量白名单、生成物路径必须在 SKILL 与仓库配置中一致。
- PR 分支跑 affected(或等价过滤);main 定期全量;发布与 Docker 多阶段构建需说明如何裁剪 monorepo 上下文。
本地到 CI 的任务流(skill-flow-block)
[ clone / fetch ]
│
▼
┌─────────────┐ 工作区安装:pnpm/npm/yarn + lockfile 校验
│ 依赖解析 │──── 约束:禁止隐式依赖;内部包用 workspace 协议
└─────────────┘
│
▼
┌─────────────┐ Nx graph / turbo run:声明任务边与缓存范围
│ 任务图编排 │──── 本地:命中 .cache / remote;记录 env 排除表
└─────────────┘
│
▼
┌─────────────┐ PR:affected + 分片;main:定时全量 + 产物晋升
│ CI 矩阵 │──── 远程缓存:读写令牌分环境;fork PR 默认只读或禁用写
└─────────────┘
Agent 改配置时先对照「任务名是否在根 scripts / project 中一致」,再动缓存键;否则会出现本地绿、CI 误命中或反过来的情况。
Nx 与 Turborepo
二者都可做任务缓存与并行;差异主要在默认心智模型与扩展方式。SKILL 里应点名团队实际使用的那一种,并附上官方任务/缓存字段名,避免混用术语。
Nx
- 项目图、
affected、插件(Vite、Cypress 等)一体化。 project.json/package.jsontargets;inputs/outputs在 target 级声明。- 适合多技术栈、需要迁移工具与代码生成约束的大型仓。
Turborepo
turbo.json中tasks管道;与包管理器工作区天然贴合。- 远程缓存协议简单;常配合 pnpm
workspace:*。 - 适合以 package 为中心、希望最少样板的前后端同仓。
// Nx project.json — 完整 target 配置示例
// packages/payment-ui/project.json
{
"name": "payment-ui",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/payment-ui/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"options": { "outputPath": "dist/packages/payment-ui" },
"dependsOn": ["^build"] // 依赖所有上游包先完成 build
},
"test": {
"executor": "@nx/vite:test",
"outputs": ["{workspaceRoot}/coverage/packages/payment-ui"],
"inputs": [
"default",
"^default",
{ "externalDependencies": ["vitest"] }
],
"options": { "coverage": true }
},
"lint": {
"executor": "@nx/eslint:lint",
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"outputs": ["{options.outputFile}"]
}
}
}
// Turborepo turbo.json — pipeline 配置示例
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"], // ^ 表示先等依赖包的 build 完成
"inputs": ["src/**", "package.json", "tsconfig.json"],
"outputs": ["dist/**", ".next/**"],
"env": ["NODE_ENV", "API_URL"] // 这些 env 变量参与缓存键
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "**/*.test.ts", "vitest.config.ts"],
"outputs": ["coverage/**"],
"env": ["CI"]
},
"lint": {
"inputs": ["src/**", ".eslintrc*", "../../.eslintrc.json"],
"outputs": [] // 无文件输出,缓存 exit code
},
"dev": {
"cache": false, // 开发服务器不缓存
"persistent": true
}
},
"remoteCache": {
"signature": true // 验证远程缓存签名
}
}
# pnpm workspace 配置
# pnpm-workspace.yaml
packages:
- "packages/*"
- "apps/*"
- "tools/*"
# 包间引用使用 workspace 协议(防止幽灵依赖)
# packages/checkout/package.json
# {
# "dependencies": {
# "@acme/payment-ui": "workspace:*",
# "@acme/shared-utils": "workspace:^"
# }
# }
# 按影响范围运行(affected)
# Nx:仅运行受当前改动影响的包的 test
npx nx affected --target=test --base=origin/main --head=HEAD
# Turborepo:使用 --filter 过滤受影响包
# (需配合 turbo run ... --filter=[HEAD^1])
turbo run build test --filter="...[origin/main]"
# 远程缓存设置
# Nx Cloud: nx.json 中配置(已通过 npx nx connect 关联)
# Turborepo: 设置 TURBO_TOKEN 和 TURBO_TEAM 环境变量
# export TURBO_TOKEN="your-token"
# export TURBO_TEAM="your-team"
# turbo run build # 自动使用远程缓存
工作区与包边界
- 过滤:按路径或项目图运行子集命令(
nx affected、turbo run --filter等)。 - 约束:eslint/tsconfig 继承与根级 override 策略;公共配置包单独版本或 workspace 引用写清。
- 发布:artifact 提升与 Docker 多阶段构建中的 monorepo 裁剪(只拷贝构建所需包与锁文件)。
// ESLint 包边界规则:限制跨包导入(@nx/enforce-module-boundaries)
// .eslintrc.json(根目录)
{
"plugins": ["@nx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "scope:checkout",
"onlyDependOnLibsWithTags": ["scope:checkout", "scope:shared"]
},
{
"sourceTag": "scope:payment",
"onlyDependOnLibsWithTags": ["scope:payment", "scope:shared"]
},
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": ["type:lib", "type:feature"]
},
{
"sourceTag": "type:lib",
"onlyDependOnLibsWithTags": ["type:lib"]
// lib 层禁止导入 feature 或 app 层
}
]
}
]
}
}
// packages/payment-ui/project.json 中添加 tags:
// "tags": ["scope:payment", "type:lib"]
任务图与缓存声明
任务图需声明输入输出与缓存键;对生成代码、环境变量与密文排除规则要单列,防止错误命中缓存。将「会改变产物但未列入 inputs」的路径视为缺陷,而不是靠 --force 长期兜底。
- inputs:源码 glob、配置文件、依赖 lockfile、共享工具链版本文件。
- outputs:dist、类型声明、覆盖率目录等;与
.gitignore对齐以免上传脏缓存。 - env:仅白名单参与哈希;CI 密钥与 feature flag 默认值变更要显式列入或排除并文档化。
CI:affected 与矩阵
与 CI 矩阵对齐:分片测试、受影响子集与 main 分支全量校验的分工写清。fork 与 dependabot 类 PR 对远程缓存写入策略单独说明(通常禁止写或隔离 bucket)。
- PR:默认 affected + 必要门禁;大改可开关全量(标签或 manual workflow)。
- main:夜间或每次合并后全量回归视体量选择;发布流水线与缓存只读一致。
- 分片:按项目或测试文件分片时声明随机种子与重试策略,避免片间顺序依赖。
# .github/workflows/ci.yml — Nx affected + 远程缓存
name: CI
on:
push:
branches: [main]
pull_request:
env:
NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }}
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # affected 需要完整 git 历史
- uses: pnpm/action-setup@v3
with: { version: 9 }
- uses: actions/setup-node@v4
with: { node-version: 20, cache: pnpm }
- run: pnpm install --frozen-lockfile
# PR:仅运行受影响的 lint + test + build
- name: Affected (PR)
if: github.event_name == 'pull_request'
run: |
npx nx affected \
--target=lint,test,build \
--base=origin/${{ github.base_ref }} \
--head=HEAD \
--parallel=3
# main:全量运行(每次合并后确保整体健康)
- name: Full run (main)
if: github.ref == 'refs/heads/main'
run: |
npx nx run-many \
--target=lint,test,build \
--all \
--parallel=3
# Fork PR:禁止写远程缓存(只读模式)
# Nx Cloud 自动检测 fork,降级为只读;Turborepo 需手动:
# if: github.event.pull_request.head.repo.fork == false
# env: { TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} }
SKILL 前置元数据示例
---
name: monorepo-tooling
description: 评审或生成 Nx/Turborepo 任务图、缓存配置与 CI affected 策略
---
# 工具选择
Nx: 多技术栈/插件生态/代码生成约束 → project.json targets
Turborepo: package 中心/轻量 pipeline → turbo.json tasks
pnpm workspace:* 协议避免幽灵依赖;内部包不发布到 npm
# Pipeline 配置
dependsOn: ["^build"] — 上游包先完成 build(^ 表示拓扑依赖)
inputs: 源码 glob + 配置文件 + lockfile + 共享工具链
outputs: dist/** / coverage/** 与 .gitignore 对齐
env: 仅白名单参与缓存哈希;CI 密钥显式排除
# 远程缓存
Nx Cloud: nx.json 配置 + NX_CLOUD_AUTH_TOKEN secret
Turborepo: TURBO_TOKEN + TURBO_TEAM 环境变量
Fork PR: 禁止远程缓存写入(只读或隔离 bucket)
# 包边界
@nx/enforce-module-boundaries: sourceTag → onlyDependOnLibsWithTags
tags: scope:{domain} + type:{app|lib|feature}
# CI 策略
PR: nx affected --base=origin/main --head=HEAD(仅受影响包)
main: nx run-many --all(全量保障)
分片: --parallel=3;随机种子固定,避免片间顺序依赖
任务 inputs 草稿实验室
根据所选工具生成一段可粘贴到评审意见或 SKILL 附录的草稿(非可执行配置);提交前仍需对照官方 schema 与仓库实际路径。
实验室输出仅作沟通草稿;真实 turbo.json / project.json 须与仓库内其它任务的 dependsOn、持久化输出约定一致。