前言
作为一个使用 Rush 管理 Monorepo 的开发者,你是否也遇到过以下痛点:
- 切换 Git 分支后,必须重新构建所有项目
- 新同事 clone 项目后,第一次构建要等很久
rush build虽然支持增量构建,但一旦 clean 后状态全丢- 团队成员之间无法共享构建产物,每个人都在重复造轮子
如果你正被这些问题困扰,那么恭喜你,Rush 构建缓存(Build Cache) 正是你的解决方案!
本文将基于我自己的 Rush 项目实战经验,从零开始带你一步步开启构建缓存,并对比 local-only 本地磁盘缓存 和 http 远程缓存 两种模式的差异,让你的构建速度提升。
一、先看看我的项目长什么样
在讲缓存之前,先简单介绍一下我的 Rush Monorepo 结构,方便你对号入座:
bash
rush-cache/
├── common/
│ └── config/rush/
│ ├── build-cache.json # 构建缓存配置(本文重点)
│ └── rush.json # Rush 主配置
├── my-app/ # Vue 主应用
│ ├── config/rush-project.json
│ └── src/App.vue # 实际运行效果见下文
├── components/ # Vue 组件库(被 my-app 引用)
│ ├── config/rush-project.json
│ └── src/components/HelloWorld.vue
├── tools/ # 工具函数库(被 my-app 引用)
│ ├── config/rush-project.json
│ └── src/index.js
└── server/ # HTTP 缓存服务(http 模式专用)
├── index.js
└── rush-build-cache/ # 缓存文件存储目录
Workspace 依赖关系
通过 Rush 的 workspace 协议,my-app 直接引用本地包:
json
// my-app/package.json
{
"name": "my-app",
"dependencies": {
"vue": "^3.5.13",
"my-components": "workspace:*",
"my-tools": "workspace:*"
}
}
App.vue 的实际效果
来看 my-app 中的实际调用代码:
vue
<!-- my-app/src/App.vue -->
<script setup>
import { HelloWorld } from 'my-components'
import { isNumber } from 'my-tools'
console.log('isNumber', isNumber(123));
console.log('isNumber', isNumber('123'));
</script>
<template>
<HelloWorld msg="my-app" />
</template>
被引用的两个包的实现:
vue
<!-- components/src/components/HelloWorld.vue -->
<script setup>
defineProps({ msg: String })
</script>
<template>
<h1>这是 my-components 组件:{{ msg }}</h1>
</template>
javascript
// tools/src/index.js
const toString = (obj) => Object.prototype.toString.call(obj).replace(/\[object (\S+)\]/, '$1')
export const isNumber = (obj) => toString(obj) === 'Number'
export const isObject = (obj) => toString(obj) === 'Object'
export const isArray = (obj) => toString(obj) === 'Array'
// ... 其他工具函数
运行效果:
控制台输出:
scss
isNumber true <- isNumber(123) 正确判断数字
isNumber false <- isNumber('123') 正确排除字符串
页面显示:
perl
这是 my-components 组件:my-app
三个包通过 Rush workspace 完美联动,这也是接下来验证构建缓存是否生效的基础 ------因为缓存命中后,从 tar 还原的 dist 目录必须能正确支持 workspace:* 的引用。
二、先理解:什么是 Rush 构建缓存?
2.1 增量构建 vs 构建缓存
在 Rush 中,有两种「加速构建」的机制:
| 机制 | 原理 | 存储位置 | 切换分支后 | 团队共享 |
|---|---|---|---|---|
| 增量构建 | 对比文件哈希,跳过未变项目 | 项目文件夹内的临时文件 | 失效 | 不支持 |
| 构建缓存 | 将构建产物打包成 tar,按哈希缓存 | 本地磁盘 / HTTP 服务 / 云端 | 可用 | 支持(http/云端模式) |
简单来说,增量构建是「跳过」 ,而构建缓存是「还原」。构建缓存不仅能在同一台机器的不同分支间共享,http / 云端模式甚至可以让整个团队共享同一份缓存。
2.2 构建缓存的存储位置(四种 cacheProvider)
Rush 的 build-cache.json 中通过 cacheProvider 字段选择缓存后端:
cacheProvider |
存储位置 | 适用场景 | 是否需要额外基础设施 |
|---|---|---|---|
local-only |
本地磁盘 common/temp/build-cache |
个人开发、初次体验 | 否 |
http |
自建 HTTP 服务(本项目 server/index.js) |
团队共享、本地开发服务器 | 是(一个 Node 进程) |
azure-blob-storage |
Azure Blob Storage | 企业级云端共享 | 是(Azure 账号) |
amazon-s3 |
AWS S3 | 企业级云端共享 | 是(AWS 账号) |
本文将聚焦于前两种:local-only 和 http,因为它们都不依赖云厂商,是中小团队最容易上手的方案。
两种模式的核心配置思路完全一致,只是缓存后端不同,可随时切换。
三、实战一:开启 local-only 本地磁盘缓存
这是最简单的模式,适合先跑通流程,验证缓存是否生效。
Step 1:创建 build-cache.json 全局配置
在 common/config/rush/ 下创建(或修改)build-cache.json:
json
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/build-cache.schema.json",
/**
* 核心配置 1:开启构建缓存
*/
"buildCacheEnabled": true,
/**
* 核心配置 2:缓存提供者
* "local-only": 只使用本地磁盘缓存,零基础设施成本
*/
"cacheProvider": "local-only"
}
Tips :早期版本需要在
experiments.json中设置"buildCache": true,新版本已经统一用build-cache.json的"buildCacheEnabled"。
Step 2:为每个项目配置 rush-project.json
光开启全局缓存还不够,Rush 还需要知道哪些目录是构建产物(需要缓存还原的目录)。三个子包的配置完全一致:
json
// tools/config/rush-project.json
// components/config/rush-project.json
// my-app/config/rush-project.json
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-project.schema.json",
"operationSettings": [
{
"operationName": "build",
"outputFolderNames": ["dist"]
}
]
}
重要 :
outputFolderNames中的目录必须满足:
- 不被 Git 跟踪(在
.gitignore中已忽略)- 不能包含符号链接
- 是构建脚本真正的输出目录
Step 3:第一次构建写入缓存
bash
# 先安装依赖
rush install
# 执行重建并观察日志
rush rebuild --verbose
第一次构建(写入缓存)的日志示例:
erlang
==[ my-tools ]==============================================[ 1 of 3 ]==
This project was not found in the build cache.
Invoking: unbuild
...
Caching build output folders: dist
Successfully set cache entry.
"my-tools" completed successfully in 3.12 seconds.
==[ my-components ]==============================================[ 2 of 3 ]==
This project was not found in the build cache.
Invoking: vite build
...
Caching build output folders: dist
Successfully set cache entry.
"my-components" completed successfully in 5.47 seconds.
==[ my-app ]==============================================[ 3 of 3 ]==
This project was not found in the build cache.
Invoking: vite build
...
Caching build output folders: dist
Successfully set cache entry.
"my-app" completed successfully in 8.23 seconds.
此时可以在 common/temp/build-cache/ 下看到生成的 3 个 .tar.gz 缓存文件。
Step 4:验证缓存还原
bash
# 删除所有构建产物和临时文件(模拟:clean 后、切换分支后)
rush purge
# 再次构建 - 验证缓存还原
rush rebuild --verbose
缓存命中的日志:
erlang
==[ my-tools ]==============================================[ 1 of 3 ]==
Build cache hit.
Clearing cached folders: dist
Successfully restored output from the build cache.
my-tools was restored from the build cache.
==[ my-components ]==============================================[ 2 of 3 ]==
Build cache hit.
Clearing cached folders: dist
Successfully restored output from the build cache.
my-components was restored from the build cache.
==[ my-app ]==============================================[ 3 of 3 ]==
Build cache hit.
Clearing cached folders: dist
Successfully restored output from the build cache.
my-app was restored from the build cache.
原本需要十几秒的构建,现在瞬间完成!
local-only 模式小结
bash
缓存写入:rush build → common/temp/build-cache/[hash].tar.gz
缓存读取:rush build → 解压 [hash].tar.gz → 项目 dist/
优点 :零配置、零基础设施、开箱即用。 缺点:仅当前机器可用,无法团队共享。
接下来我们升级到 http 模式,把缓存集中存储到团队共享的 HTTP 服务上。
四、实战二:升级到 http 远程缓存
http 模式的核心思想是:把缓存写入到一个独立的 HTTP 服务,所有开发者都从同一个服务读写缓存,实现团队级共享。
4.1 准备 HTTP 缓存服务
本项目在 server/index.js 提供了一个最小化的实现(基于 Node 原生 http 模块,无任何依赖):
javascript
// server/index.js (核心逻辑摘录)
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 3000;
const CACHE_DIR = path.join(__dirname, 'rush-build-cache');
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
}
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const cacheKey = url.pathname.replace(/^\//, '');
const cacheFilePath = path.join(CACHE_DIR, cacheKey);
switch (req.method) {
case 'GET': // 读取缓存
// ...返回缓存文件或 404
case 'PUT': // 写入缓存
// ...将请求体写入文件
case 'DELETE': // 删除缓存
// ...
}
});
server.listen(PORT, () => {
console.log(`Rush build cache server running at http://localhost:${PORT}/`);
console.log(`Cache directory: ${CACHE_DIR}`);
});
完整代码见 server/index.js。它实现了 Rush HTTP 缓存协议要求的四个操作:
| HTTP 方法 | 用途 | Rush 调用时机 |
|---|---|---|
GET |
读取缓存 | 构建前查询缓存是否存在 |
PUT / POST / PATCH |
写入缓存 | 构建完成后上传产物 |
DELETE |
删除缓存 | 手动清理或缓存淘汰 |
OPTIONS |
CORS 预检 | 跨域场景 |
启动服务:
bash
node server/index.js
# 输出:
# Rush build cache server running at http://localhost:3000/
# Cache directory: .../server/rush-build-cache
生产环境建议:用 nginx 反向代理 + 持久化存储 + 鉴权,或直接换成 S3 / Azure Blob。
4.2 修改 build-cache.json 切换到 http 模式
json
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/build-cache.schema.json",
"buildCacheEnabled": true,
/**
* 切换为 http 模式
*/
"cacheProvider": "http",
/**
* http 模式专用配置
*/
"httpConfiguration": {
/**
* (Required) 缓存服务地址
*/
"url": "http://localhost:3000/",
/**
* (Optional) 写入缓存的 HTTP 方法,默认 PUT
*/
// "uploadMethod": "PUT",
/**
* (Optional) 自定义请求头,可用于鉴权
*/
// "headers": { "X-HTTP-Company-Id": "109283" },
/**
* (Optional) 鉴权 token 命令,输出会作为 Authorization 头
*/
// "tokenHandler": { "exec": "node", "args": ["common/scripts/auth.js"] },
/**
* (Optional) 缓存 key 前缀,用于多团队隔离
*/
// "cacheKeyPrefix": "my-company-",
/**
* (Optional) 是否允许写入缓存,默认 false
* 配合环境变量 RUSH_BUILD_CACHE_WRITE_ALLOWED 可在运行时覆盖
*/
"isCacheWriteAllowed": true
}
}
rush-project.json的配置与 local-only 模式完全相同,无需任何修改。
4.3 验证 http 模式缓存效果
确保 HTTP 服务已启动,然后:
bash
# 第一次构建,写入远程缓存
rush rebuild --verbose
日志与 local-only 模式几乎一致,区别在于缓存实际写到了 server/rush-build-cache/:
erlang
==[ my-tools ]==============================================[ 1 of 3 ]==
This project was not found in the build cache.
Invoking: unbuild
...
Caching build output folders: dist
Successfully set cache entry.
"my-tools" completed successfully in 3.12 seconds.
此时查看 server/rush-build-cache/ 目录,可以看到 3 个以哈希命名的缓存文件。
模拟 clean 后重新构建:
bash
rush purge
rush rebuild --verbose
缓存命中日志:
erlang
==[ my-tools ]==============================================[ 1 of 3 ]==
Build cache hit.
Clearing cached folders: dist
Successfully restored output from the build cache.
my-tools was restored from the build cache.
...
最后验证功能 :启动 my-app,浏览器看到与 local-only 模式完全一致的效果------控制台打印 isNumber true/false,页面显示 这是 my-components 组件:my-app。证明http 缓存还原不仅快,而且完全正确 ,workspace:* 的依赖关系通过还原的 dist 目录正常工作。
4.4 两种模式对比
| 维度 | local-only | http |
|---|---|---|
| 配置复杂度 | 极简(2 个字段) | 需配置 httpConfiguration + 运行 HTTP 服务 |
| 基础设施 | 无 | 需运行一个 HTTP 服务 |
| 缓存存储位置 | common/temp/build-cache/ |
server/rush-build-cache/(或远程) |
| 团队共享 | 不支持 | 支持 |
| 切换分支命中 | 支持 | 支持 |
| 适用场景 | 个人开发、初次体验 | 团队协作 |
切换模式只需修改 build-cache.json 的 cacheProvider 字段,rush-project.json 完全通用。
五、进阶:缓存键(Cache Key)是如何计算的?
很多人会好奇:Rush 怎么知道什么时候该用缓存,什么时候该重新构建?答案就在缓存键的计算逻辑中。
默认情况下,以下内容的哈希会参与缓存键的计算:
| 因素 | 说明 |
|---|---|
| 项目源文件 | 项目文件夹下的所有文件,排除 .gitignore 忽略的 |
| 依赖项目源文件 | 该项目依赖的其他 Rush 项目的源文件 |
| NPM 依赖版本 | 所有直接和间接依赖的版本(含 shrinkwrap 中的哈希) |
| 命令行参数 | 比如 rush build --mode dev 和 rush build --mode prod 会产生不同缓存键 |
| 缓存 key 前缀 | http 模式下 httpConfiguration.cacheKeyPrefix |
举个例子 :在我的项目中,修改 tools/src/index.js 中的 isNumber 实现 -> my-tools 缓存失效 -> 因为依赖链 my-app -> my-tools,my-app 的缓存也会同步失效(需要重建)。而修改 my-app/src/App.vue 的文案 -> 只有 my-app 缓存失效,my-tools 和 my-components 都命中。
自定义缓存输入
如果需要更精细的控制,可以在 rush-project.json 中配置:
json
{
"operationSettings": [
{
"operationName": "build",
"outputFolderNames": ["dist"],
"globalEnvNames": ["NODE_ENV"],
"globalSkipForDependenciesCommandLineArguments": ["--mode"]
}
]
}
六、常见问题 & 踩坑指南
Q1:为什么提示「Project does not support caching」?
原因 :项目缺少 rush-project.json,或没有配置 outputFolderNames。
解决:参照 Step 2,为每个项目补全配置。
Q2:http 模式下缓存不生效,日志显示「not found in the build cache」但确实构建了?
最常见的原因(按出现频率排序):
- HTTP 服务没启动 :
curl http://localhost:3000/看是否能连通 isCacheWriteAllowed为 false :httpConfiguration.isCacheWriteAllowed默认false,需要显式设为true,或通过环境变量RUSH_BUILD_CACHE_WRITE_ALLOWED=1覆盖- 环境变量覆盖了配置 :环境变量
RUSH_BUILD_CACHE_WRITE_ALLOWED=0被设置,导致只读 - 缓存键不一致:不同环境的 Node/rush/pnpm 版本不同,或命令行参数不同
- 服务端缓存目录权限问题 :
server/rush-build-cache/无写入权限
排查命令:
bash
# 1. 确认 HTTP 服务可访问
curl -i http://localhost:3000/
# 2. 用 --verbose 观察是否真的发起了 HTTP 请求
rush build --verbose
# 3. 检查服务端是否收到请求并写入了文件
ls server/rush-build-cache/
Q3:缓存命中率很低,经常不命中?
排查思路:
- 检查
rush-project.json是否把不该参与哈希的文件也算进来了 - 构建产物是否包含时间戳、随机数等不稳定内容(如 source-map 的随机注释)
- 确认不同环境使用的 Node、rush、pnpm 版本一致
- 用
--verbose参数观察日志,看看哪个输入变了 - http 模式下,确认所有环境访问的是同一个缓存服务地址
Q4:缓存还原后项目报错?
大概率是 outputFolderNames 不完整 。检查构建脚本是否还输出到了其他目录(比如 .next、.cache、types 等)。例如我的 tools 项目用的是 unbuild,它默认输出 dist,如果哪天改成了 lib,配置也要同步改。
Q5:rush rebuild 和缓存的关系?
rush rebuild= 强制重新构建,仍然会写缓存(不会从缓存读)rush build= 优先读缓存,未命中才构建- 如果想让
rebuild也不写缓存:
bash
RUSH_BUILD_CACHE_WRITE_ALLOWED=0 rush rebuild
Q6:本地缓存太大,怎么清理?
bash
# 清理 common/temp(包含 local-only 模式的 build-cache)
rush purge
# 手动删除 local-only 缓存目录
rm -rf common/temp/build-cache
# http 模式:清理服务端缓存目录
rm -rf server/rush-build-cache/*
# 或通过 HTTP DELETE 接口清理单个缓存
curl -X DELETE http://localhost:3000/<cache-key>
Q7:http 模式下如何切换回 local-only?
只需修改 build-cache.json:
json
{
"buildCacheEnabled": true,
"cacheProvider": "local-only"
}
rush-project.json 完全通用,无需改动。
七、最佳实践总结
应该做的
- 先跑通 local-only :验证缓存还原后 App.vue 还能正常运行,确保
rush-project.json配置正确 - 再升级到 http:在团队内搭建共享 HTTP 缓存服务,让团队共享缓存
- 定期清理旧缓存 :local-only 用
rush purge,http 模式定期清理server/rush-build-cache/ - 使用 rig 包统一配置 :项目多了之后,用 rig packages 复用
rush-project.json,避免每个项目拷贝一份
不应该做的
- 不要把
node_modules放进缓存 :rush install自己有优化 - 不要缓存
.rush临时目录:只缓存真正的构建产物 - 不要在 http 模式下暴露无鉴权的服务到公网 :本地开发够用,生产环境务必加鉴权(
tokenHandler)和反向代理
八、总结
开启 Rush 构建缓存,你只需要做这几件事:
build-cache.json设置buildCacheEnabled: true,选择cacheProvider(local-only或http)- 每个项目的
rush-project.json配置outputFolderNames(不要漏了任何一个子包) - 如果用 http 模式:启动
server/index.js缓存服务 - 验证缓存还原后项目功能正常(比如跑一下 App.vue 看看 HelloWorld 组件和 isNumber 工具函数是否正常)
整个过程不到 30 分钟,但能为团队每天节省数小时的构建等待时间。这就是工程化的魅力------一次投入,持续受益。
参考资料
项目源码
完整示例项目已开源:
GitHub: github.com/qq853580228...
项目文件索引
| 文件 | 说明 |
|---|---|
| build-cache.json | 构建缓存全局配置(local-only / http 切换) |
| rush.json | Rush 主配置(含 3 个项目注册) |
| server/index.js | HTTP 缓存服务最小化实现(http 模式专用) |
| App.vue | 主应用入口 |
| HelloWorld.vue | 组件库示例组件 |
| index.js | 工具函数库 |
| my-app/rush-project.json | my-app 项目级缓存配置 |
| components/rush-project.json | components 项目级缓存配置 |
| tools/rush-project.json | tools 项目级缓存配置 |