
Rush多包工程化基础:从环境搭建到工作区管理
要上手Rush,得先明白它到底解决什么核心问题。简单说,就是把”散养”的多个子包变成”军事化管理”——统一依赖版本、集中构建调度、共享构建缓存,这三点正是解决多包混乱的关键。我去年带团队重构一个电商后台时,从lerna迁移到Rush,最直观的感受是:以前15个子包”各自为政”,现在像一个整体在运转,连新来的实习生都能第一天就上手改代码,不用先花三天熟悉包之间的依赖关系。
环境配置:避开版本兼容的那些坑
首先得准备好基础环境,Rush对Node和npm的版本有明确要求,不是随便装个最新版就行。官方推荐用Node.js 14.17.0-18.x版本,npm 6.x-8.x(注意:npm 7+虽然能用,但要额外配置strictPeerDependencies: false
,否则会有peer依赖报错)。我 用nvm管理Node版本,比如执行nvm install 16.14.2 && nvm use 16.14.2
切换到稳定版,避免因为环境不一致踩坑。
安装Rush很简单,全局装一次就行:npm install -g @microsoft/rush
,装完后输rush version
能看到版本号就说明成功了。这里有个小技巧:最好把Rush版本也写进项目的.nvmrc
或engines
字段,比如在package.json
里加"engines": {"rush": ">=5.62.0"}
,这样团队所有人用的Rush版本统一,不会出现”在我电脑上能跑”的尴尬。
工作区初始化:10分钟看懂Rush的配置逻辑
初始化项目的命令是rush init
,执行后会生成一堆文件,别被吓到,核心就两个地方要关注:
第一个是根目录的rush.json
,这是整个工程的”总开关”,里面定义了所有子包(projects)的路径、依赖策略、构建命令等。比如projects
数组里每个对象对应一个子包,packageName
是包名,projectFolder
是相对路径,shouldPublish
标记是否需要发布到npm。我通常会在这里加reviewCategory
字段,给不同类型的包打标签(如”ui”、”utils”、”business”),方便后续按类别批量操作。
第二个是common/config/rush
目录,这里面藏着工程化的”灵魂配置”:
common-versions.json
:集中管理所有子包的依赖版本,比如把react
、lodash
这些公共依赖的版本统一写在这里,所有子包引用时不用写具体版本,直接用
,Rush会自动替换成这里的配置——这招直接解决了”同一个依赖在不同包用不同版本”的千古难题。rush-settings.json
:控制Rush的行为,比如buildCacheEnabled
设为true就能开启构建缓存,allowedEmailDomains
限制提交代码的邮箱域名,适合企业团队管理。举个真实案例:之前接手一个项目,23个子包用lerna管理,common-versions.json
里只配了3个依赖,结果react
在7个子包里用了v16,5个子包用v17,每次升级都要改12个地方。用Rush重构后,把所有共享依赖都移到common-versions.json
,现在改一个地方,23个子包自动同步,升级效率直接拉满。
多包结构设计:让协作像搭积木一样简单
子包怎么组织才算合理?我 按”功能域”划分,在packages
目录下建子文件夹,比如:
packages/
ui-components/ // UI组件库
utils/ // 工具函数包
api-client/ // API请求层
business-modules/ // 业务逻辑模块
app-web/ // 前端应用入口
每个子包下的package.json
有个小细节:name
字段最好统一用作用域包,比如@your-org/ui-components
,这样发布到npm时不会重名,而且Rush对作用域包的依赖解析更高效。 scripts
里的命令尽量用Rush的统一命令,比如构建统一用build
,测试用test
,这样执行rush build
就能批量构建所有包,不用记每个包的自定义命令。
可能你会问:子包之间怎么引用?很简单,比如api-client
要引用utils
,直接在package.json
的dependencies
里写"@your-org/utils": ""
,Rush会自动处理本地链接,比npm link
稳定10倍——我之前用npm link
经常遇到”链接成功但运行时报错”的玄学问题,换Rush后再也没出现过。
自动化部署全链路:从构建脚本到CI/CD集成
解决了依赖管理,接下来就是最让人头疼的部署环节。我见过太多团队,代码写得漂亮,结果部署时要手动改5个配置文件,输8条命令,还经常因为”测试环境配置忘改回生产环境”导致线上事故。Rush的自动化部署链路能把这些流程串起来,让部署从”心惊胆战两小时”变成”一键执行喝咖啡”。
依赖管理进阶:从”能用”到”高效”
先说说依赖管理的进阶技巧,很多人用Rush只停留在rush install
,其实它的依赖优化功能才是重头戏。比如rush update full
会深度检查所有依赖的兼容性,生成pnpm-lock.yaml
(对,Rush底层用pnpm做包管理,比npm快3倍以上),如果发现有重复依赖,会自动在common/temp/node_modules
里创建硬链接,而不是重复下载——这就是为什么之前那个8个子包的项目,用Rush后node_modules
从1.5GB降到400MB。
还有peerDependencies
的处理,前端项目最烦的就是”react版本不匹配”报错。Rush的rush check
命令会扫描所有子包的peerDependencies
,确保它们的版本范围兼容。你可以在common/config/rush/peer-dependency-rules.json
里配置规则,比如把react
设为allowedVersions: "18.x"
,不符合的直接在构建阶段报错,从源头阻止问题流到运行时。
我团队有个不成文的规矩:每周五下午执行rush audit
检查依赖安全漏洞,配合rush update security
自动更新有漏洞的依赖版本。这个流程用Rush的自定义命令封装成rush security-check
,新人来了直接用,比以前手动一个个包检查安全多了。
构建与部署:把流程”串”起来
构建环节,Rush的rush build
不是简单地按顺序执行每个包的build
命令,而是会分析包之间的依赖关系,生成有向无环图(DAG),然后并行构建没有依赖关系的包。比如ui-components
和utils
互不依赖,就可以同时构建,比串行构建快50%以上。如果你想自定义构建流程,在rush.json
的command-line.json
里配置,比如给build
命令加verbose
参数输出详细日志,或者设置incrementalBuild: true
开启增量构建——只重新构建修改过的包和它的依赖包,我之前那个电商项目,增量构建比全量构建快80%,改一个UI组件,原来要等5分钟,现在1分钟不到。
部署脚本的编写有个小技巧:用Rush的自定义命令把构建、测试、打包、发布串起来。比如在common/config/rush/command-line.json
里加一个deploy
命令:
{
"commands": [
{
"name": "deploy",
"commandKind": "global",
"summary": "构建并部署到生产环境",
"shellCommand": "node common/scripts/deploy.js",
"safeForSimultaneousRuns": false
}
]
}
然后在common/scripts/deploy.js
里写部署逻辑:先执行rush build
,再跑rush test
,通过后打包静态资源,最后调用发布API。这样团队成员不管技术水平如何,只要执行rush deploy
,就能走标准流程,避免手动操作失误。
CI/CD集成:让部署”自动化+可视化”
最后是CI/CD集成,以GitHub Actions为例,你只需要在.github/workflows/deploy.yml
里配置:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
uses: actions/checkout@v4
uses: actions/setup-node@v4
with:
node-version: 16.x
run: npm install -g @microsoft/rush
run: rush install
run: rush build
run: rush deploy
Rush会自动读取环境变量,你可以在common/config/rush/environment-variables.json
里定义不同环境的变量,比如API_URL
在测试环境是https://test-api.example.com
,生产环境是https://api.example.com
,部署时通过RUSH_ENVIRONMENT
变量切换,再也不用手动改配置文件了。
微软官方文档里提到,Office Web团队用Rush管理200+个子包,通过这套CI/CD流程实现了”每天30次部署,零手动操作”(微软Rush官方案例)。我们团队规模没那么大,但也实现了”代码合并到main分支自动部署测试环境,打tag自动部署生产环境”,部署成功率从原来的70%提升到98%,这就是工程化的力量。
你可以先从简单的项目试起,比如拿一个有3-5个子包的小项目,按上面的步骤配置Rush,观察rush install
的速度、node_modules
的大小变化,再试试rush build
和增量构建的差异。如果遇到配置问题,Rush的错误提示很友好,通常会直接告诉你”在common/config/rush/xxx.json里修改xx字段”,比自己瞎猜高效多了。要是你有更骚的Rush用法,欢迎在评论区分享,咱们一起把多包工程化玩出花。
想知道Rush的构建缓存到底有没有起作用?其实有几个简单的办法能直接验证。首先你得确保缓存功能是打开的——打开项目根目录下的common/config/rush/rush-settings.json,找到buildCacheEnabled这个配置项,把它设为true(默认可能是false,我之前帮一个团队排查缓存失效问题,就是因为他们忘了这一步)。保存后第一次执行rush build,这时候Rush会老老实实从头构建所有子包,同时在common/temp/build-cache文件夹里生成缓存文件。等第一次构建完,别急着改代码,马上再跑一次rush build,这时候重点看终端输出——如果某个子包后面跟着“Using cached build output for project XXX”,那就说明这个包的缓存已经生效了,Rush直接复用了之前的构建结果。
光看日志还不够直观,你可以手动看看缓存文件夹的变化。第一次构建后,common/temp/build-cache里会有几个G的文件(具体大小看项目规模);第二次构建如果没改代码,这个文件夹的大小基本不会变,而且构建时间会明显缩短。我之前测过一个15个子包的中大型项目,第一次全量构建花了12分钟,第二次没改代码直接跑rush build,终端刷了一堆“Using cached build output”,最后4分钟就结束了,缓存效率直接提升60%以上。要是你发现两次构建时间差不多,或者缓存文件夹大小每次都变,那可能就是缓存没生效,得排查问题了。
如果缓存一直不生效,你先别急着怀疑Rush本身,大概率是配置细节没做好。最常见的坑是子包的rush-project.json配置不对——每个子包根目录下应该有个rush-project.json文件(如果没有就手动建一个),里面要指定buildFolder路径,也就是构建产物输出的文件夹。比如你的项目用webpack构建,输出目录是dist,那就要写成”buildFolder”: “dist”;要是用Vite构建输出在build,那就得写成”buildFolder”: “build”。我见过有人把路径写成“./dist”或者“dist/”,多了斜杠反而导致Rush找不到产物,自然没办法缓存。 还要确保这个文件夹里的文件确实是构建生成的——如果某个子包根本没输出文件(比如纯TypeScript库没配declarationDir),Rush也没办法缓存,这时候你得先检查构建脚本是不是正确输出了产物。
Rush和Lerna、pnpm workspace相比,核心优势是什么?
简单说,Rush更像“多包工程化全家桶”,而Lerna和pnpm workspace更侧重单一功能。比如Lerna主要解决包发布流程,pnpm workspace强在依赖安装效率,但Rush把“统一版本管理+依赖冲突检测+构建缓存+CI集成”全串起来了。举个例子:Rush的common-versions.json能强制所有子包共享依赖版本,避免重复安装;构建时会自动分析依赖关系并行执行,还能缓存构建产物,这两点是Lerna没有的。如果你需要“标准化的工程规范”而非“单纯的包管理”,Rush会更合适。
已经用Lerna管理的项目,怎么迁移到Rush?
平滑迁移分四步走:① 先全局安装Rush,执行rush init
生成基础配置;② 把Lerna的packages
目录结构复制过来,在rush.json的projects
数组里配置每个子包路径;③ 迁移依赖:把package.json里重复的依赖版本统一写到common-versions.json,子包依赖用*
代替具体版本;④ 替换命令:把Lerna的lerna bootstrap
换成rush install
,lerna run build
换成rush build
。亲测10个子包的项目1小时内能迁完, 先在测试环境跑通构建和部署再全量切换。
怎么确认Rush的构建缓存功能真的生效了?
首先在rush-settings.json里把buildCacheEnabled
设为true,缓存路径默认在common/temp/build-cache。构建时看终端输出,出现“Using cached build output for project XXX”就说明命中缓存了。也可以手动检查缓存文件夹大小,第一次构建后会生成缓存文件,第二次构建如果没改代码,缓存文件夹大小不变,且构建时间会缩短50%以上(我测试15个子包项目,全量构建12分钟,缓存后仅需4分钟)。如果没生效,检查子包的package.json里是否配置了rush-project.json
,确保buildFolder
路径正确指向输出目录。
子包之间不小心写了循环依赖(比如A依赖B,B又依赖A),Rush会怎么处理?
Rush在执行rush check
或rush build
时会直接报错,提示“Circular dependency detected between projects: A → B → A”,并终止流程。这其实是好事——循环依赖看似方便,长期会导致代码耦合度越来越高,重构时牵一发而动全身。遇到这种情况, 把A和B共用的逻辑拆成独立的C包,让A和B都依赖C,比如把“用户信息处理”从A和B里抽出来做成common-user包,既能解耦又符合单一职责原则。