Monorepo 项目的按变更自动部署
当一个仓库里放了多个子项目,它们共享同一套底层代码,每次推送如果全部重新构建和部署,既慢又浪费构建额度。更好的做法是:改了哪个项目,就只部署哪个项目。
共享代码的困境
典型的 monorepo 目录结构:
my-project/
├── apps/
│ ├── web/ # 主站
│ ├── admin/ # 后台
│ └── docs/ # 文档站
├── packages/
│ ├── shared-ui/ # 公共组件
│ └── utils/ # 工具函数
└── package.json
apps/ 下的三个项目都依赖 packages/ 里的公共代码。公共代码一改,三个项目都可能受影响;但只改 admin 的业务页面,web 和 docs 就没必要动。
思路
在 GitHub Actions 里加一个"变更检测"步骤,判断这次提交改了哪些目录。如果只改了 apps/web,就只构建和部署 web;如果改了 packages/utils,所有依赖它的项目都触发部署。
推送代码 → 检测哪些目录变了 → 只构建和部署变更涉及的项目 → 未变更的跳过
示例
name: Deploy
on:
push:
branches: [main]
jobs:
# 第一步:检测变更
detect-changes:
runs-on: ubuntu-latest
outputs:
web: ${{ steps.changes.outputs.web }}
admin: ${{ steps.changes.outputs.admin }}
docs: ${{ steps.changes.outputs.docs }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
web:
- 'apps/web/**'
- 'packages/**'
admin:
- 'apps/admin/**'
- 'packages/**'
docs:
- 'apps/docs/**'
- 'packages/**'
# 第二步:按条件部署
deploy-web:
needs: detect-changes
if: needs.detect-changes.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... 构建和部署 web
deploy-admin:
needs: detect-changes
if: needs.detect-changes.outputs.admin == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... 构建和部署 admin
deploy-docs:
needs: detect-changes
if: needs.detect-changes.outputs.docs == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... 构建和部署 docs
关键设计
变更规则怎么写
每个子项目的 filters 要同时包含自己的目录和公共包目录:
filters: |
web:
- 'apps/web/**' # 自己的业务代码
- 'packages/**' # 公共依赖
admin:
- 'apps/admin/**'
- 'packages/**'
这样公共包一改,所有子项目都会触发部署,不会遗漏。
如果公共包内部也可以区分为"仅 A 项目用"和"B、C 共用",可以写得更细:
filters: |
web:
- 'apps/web/**'
- 'packages/shared-ui/**'
- 'packages/utils/**'
admin:
- 'apps/admin/**'
- 'packages/utils/**'
条件跳过
通过 needs 拿到上一步的输出结果,用 if 决定是否执行:
deploy-admin:
needs: detect-changes
if: needs.detect-changes.outputs.admin == 'true'
没变化的项目这个 Job 直接跳过,不浪费 CI 时间。
支持手动全量部署
加一个手动触发入口,允许绕开变更检测,强制部署所有项目:
on:
push:
branches: [main]
workflow_dispatch:
inputs:
deploy_all:
description: '强制全量部署'
type: boolean
default: false
然后在 Job 的条件里加判断:
if: |
needs.detect-changes.outputs.admin == 'true' ||
github.event.inputs.deploy_all == 'true'