Monorepo 与构建加速工具
一次普通的公共库改动,只让 CI 少跑了几个任务,合并后却有一个未被识别的下游应用崩溃;另一次构建看起来快了十倍,实际上只是远端缓存恢复了由高权限分支写入的旧产物。多项目仓库真正困难的不是把目录放进同一个 Git 仓库,而是证明“为什么运行这些任务、为什么可以跳过另外一些任务、恢复的产物究竟由什么输入产生”。
Monorepo 工具把仓库模型转换成项目图和任务图,再用 Git 变更、依赖传播、输入哈希与输出声明决定执行范围。速度只是结果之一,更重要的是建立可审查的不变量:漏掉的输入必须让反向实验暴露,错误的 base/head 必须让门禁拒绝,缓存命中必须能够追溯生产者,远端制品必须经过权限和完整性边界。
从现场信号选择工具
| 现场信号 | 工程入口 | 首先应拿到的证据 |
|---|---|---|
| JavaScript/TypeScript 多项目已经形成复杂依赖图,需要 affected、任务推断和 CI 分发 | Nx | 同一变更在项目图中传播到正确下游,冷构建与缓存恢复结果一致 |
| 包管理器 workspace 已稳定,希望用轻量任务管线、filter 和共享缓存加速 | Turborepo | dry-run 能解释任务集合与哈希输入,第二次运行可靠命中缓存 |
| 大型 Node.js 仓库需要集中依赖策略、版本策略、变更文件和分阶段构建 | Rush | 安装、增量选择、phased command 与缓存均由仓库配置统一约束 |
| 多语言、大规模或强可复现构建需要精确 target、sandbox、远端缓存乃至远端执行 | Bazel | target 在干净环境可重复构建,未声明输入被隔离,远端结果可验证 |
工具名称不能代替仓库诊断。几十个彼此独立的小项目放在同一目录,不一定值得引入复杂任务图;反过来,一个只有十个项目但构建超过一小时、跨语言依赖频繁变化的仓库,可能已经需要更严格的 target 和远端执行模型。先测量全量构建时间、增量命中率、关键路径、失败重跑成本和维护投入,再决定平台深度。
一次可信增量构建怎样形成
这条链路中任意一环错误,都可能得到“更快但不正确”的绿色流水线。浅克隆可能让 base 不存在,动态导入可能让项目图漏边,脚本读取未声明的环境变量可能让哈希不完整,输出目录声明错误可能让缓存只恢复日志不恢复文件。工程验收不能只看耗时,还要故意破坏每一类输入,确认受影响任务确实重新执行。
项目图和任务图解决不同问题
项目图描述项目之间的依赖,例如应用依赖组件库、服务依赖协议包;任务图描述一次命令中的具体操作及先后关系,例如 app:build 必须等待 lib:build,但 lib:test 可以与另一个项目的构建并行。工具可以从 package manifest、源码导入、插件或显式配置推断图,但推断结果仍需由团队验证。
图越准确,增量范围越小;图漏边时,速度会以漏测和漏构建为代价。仓库应保存可视化图或机器可读计划作为诊断 artifact,并定期用全量构建抽检增量结果。动态路径、代码生成、运行时插件发现和外部脚本尤其容易逃出静态依赖分析。
affected 和 filter 不是同一个承诺
affected 通常从 Git 变更出发,结合项目依赖传播计算必须运行的项目;filter 更常见的是由用户按名称、目录、依赖方向或 Git 范围选择任务。两者都依赖基线和图模型,不能把命令“返回了更少项目”当作正确性证据。
CI 应明确 PR、主干推送、定时任务和重跑分别使用哪个 base/head。若历史不足、merge-base 找不到或基线状态未知,应退化为全量门禁,而不是静默运行空集合。高风险目录、全局配置、lockfile、构建脚本和工具版本变化通常需要扩大影响面。
缓存首先是信任系统
任务缓存把输入摘要映射到日志和输出文件。只有确定性任务、完整输入和准确输出才适合缓存。时间戳、随机数、网络响应、宿主机绝对路径、未声明环境变量和外部服务状态会破坏这一前提。
远端缓存还引入制品供应链:谁能写入、谁能读取、不同分支或租户是否隔离、token 如何轮换、日志是否含秘密、产物是否校验完整性、污染后怎样吊销和清空。可信主干或隔离 CI 可以拥有写权限;来自 fork 或不受信任变更的任务通常只读或禁用远端缓存。任何缓存平台都不能替代从源码冷构建的周期性审计。
从普通 workspace 迁入
迁移先固定包管理器、运行时、lockfile 和现有全量命令,再只接入一个确定性任务。第一阶段让新旧入口并行运行并比较产物摘要、测试集合和退出码;第二阶段补齐输入、输出和依赖边;第三阶段才启用 affected 与远端缓存。不要同时重写目录结构、包管理器、构建系统和 CI,否则失败时无法定位变量。
回滚必须保留旧的权威命令和禁用缓存的开关。若新工具计算出的任务集合无法解释、冷构建与命中结果不一致,或 CI 因权限和网络无法稳定访问缓存,应立即回到全量构建,而不是为保持漂亮耗时继续放宽门禁。
团队运行底线
仓库固定运行时、包管理器、lockfile、工具版本和统一入口,本机与 CI 不各自维护隐藏参数。PR 的 base/head、浅克隆策略和失败退化规则写入流水线,并保存最终任务集合。每个可缓存任务声明完整输入、环境和输出;网络、时间、随机或外部状态无法固定时关闭缓存。
远端缓存按仓库、环境和信任级别隔离,写权限只授予受控身份,凭证不进入配置、日志和 artifact。定期执行禁用缓存的全量构建,与增量和缓存结果比较,并保留差异证据。统计命中率、关键路径、上传下载耗时、缓存容量、失败重跑和平台费用;缓存比执行更慢时应关闭对应任务。
项目边界和 owner 随代码评审维护,图模型异常、循环依赖和跨边界导入进入持续治理。缓存清理、工具升级、配置迁移和平台故障均有降级入口,不能让加速层成为交付单点。
当一条绿色流水线能够解释变更基线、项目传播、任务顺序、哈希输入、缓存生产者、恢复产物和最终门禁,并能在关闭缓存的干净环境得到同样结果,构建加速才是可信的工程能力。
