Monorepo 工具链

本页给出可直接运行的 Monorepo 配置:Nx project.json target 与 Turborepo turbo.json pipeline(含 dependsOn 依赖关系)、nx affectedturbo 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.json targets;inputs / outputs 在 target 级声明。
  • 适合多技术栈、需要迁移工具与代码生成约束的大型仓。

Turborepo

  • turbo.jsontasks 管道;与包管理器工作区天然贴合。
  • 远程缓存协议简单;常配合 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 affectedturbo 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、持久化输出约定一致。

返回技能库 更多技能入口