monorepo 的 Rush.js 方案
前言
记录一下之前使用 Rush 的一些知识点,我记得 2022 年的时候, rush.js 的方案还是比较有价值的,但是在现在看来, turborepo 和 nx 的方案更加有价值。
Rush 是微软 Rush Stack 中的一个工具。
场景
我有一个 web 在线文档项目, 里面有很多的模块,他们之间有复杂的依赖关系,所以我通过 rush.js 来管理这些模块。
优点
- 文档有中文
- 构建有缓存,可以加快构建速度
- 自身管理了包管理工具的版本,项目环境容易搭建
- 解决包之间的幽灵依赖,所有依赖都更加清晰, 依赖不才有隐式声明
- 确保了多个模块,依赖的同一个库的版本的一致性
- 自带了 hooks 方案,不需要 husky
- 自定义能力强, 可扩展支持自定义命令
特殊之处 (可能是缺点)
- 根目录下没有
package.json
,node_modules
都在common/temp/node_modules
下,然后子包下有node_modules
会链接到这儿目录中,这是为了保证Rush
的幻影依赖能正常工作- Rush 如何解决问题的: rush install 指令可以扫描所有潜在的父目录并在发现 node_modules 中存在幻影依赖时发出警告。
- 所有命令都是
rush
,rushx
封装的 - 没有项目维度的包管理, 需要子包自己安装, 比如
typescript
,eslint
等 - 和 VSC 结合,需要配置一些工具识别, 比如
eslint
,prettier
插件, 他们是如何找本地版本的?开发状态下,都是打开子包开发的, 子包内有对于的node_moudles
可查找?
使用示例
- microsoft/rush-example: Illustrates a sample monorepo configuration for use with the Rush tool
- microsoft/rushstack-samples: Code samples for the Rush Stack family of tools.
安装
npm install -g @microsoft/rush
- 可在
$ZSH_CUSTOM
目录中,创建rush-completion.zsh
, 添加 completion 功能: Configuring tab completion | Rush - 最好远程仓库也设置好,方便对比版本。
初始化
- 如果是搭建一个新项目, 使用
rush init
初始化项目, 创建一个新的仓库 | Rush, 然后rush update
更新common
相关资源 - 如果是开发别人的 rush 项目,使用
rush update
更新依赖, 以开发者的身份开始 | Rush, 日常命令就是这些: Everyday commands | Rush
文件约定
/common/
: 放置配置文件, rush 自带的/apps/
: 放置应用类型项目, 比如 landing Page, 文档站点./packages/
: 放置需要发布的包./libs/
或者/features/
: 放置内部共享的代码, 可能不需要编译, 也不需要发布. 比如共享的类型定义/tools/
or/scripts/
: 放置一些脚本文件, contains scripts that act on your code base. This could be database scripts, custom executors, or workspace generators.
一些子命令
rush install
进入项目后,安装全部依赖rush add
: 安装或者更新依赖, 使用rush add
而不是pnpm add
添加项目依赖. 添加后会自动执行rush update
更新整体项目依赖.rush update
: 增量更新rush rebuild
: 强制重新构建rush check
: 检查多个项目是否依赖同一个库的不同版本,在 monorepo 环境下,这种行为是不可取的。rushx start
: 进入子项目目录后运行,用户开发项目
rushx
指令与 npm run
的指令类似,但它输入更简单、报错提示更友好、同时还有命令行帮助信息。rush
命令,可以仓库中任意子目录中执行。
实操
当新项目建立后,我们需要:
- 管理包之间的关系:添加到
rush.json
配置中 - 为包添加依赖
- 添加项目共享的依赖, 比如开发时候,使用的
prettier
.安装为共享开发依赖项, 需要手动修改common/config/rush/common-versions.json
文件,然后执行rush update
, 后续可以使用rushx
调用
创建需要管理的项目
手动配置 rush.json
, 添加 projects
, rush 才能感知要管理的 package
.然后再执行 rush update
, 会更新 common
下的一些文件。
配置一些 config
比如建议开启 ensureConsistentVersions
, strictPeerDependencies
, allowMostlyStandardPackageNames
等。
配置 git hooks
hooks 文件放在 common/git-hooks/
下, 通过 rush install
时候, 自动拷贝到 .git/hooks/
下。可以在 hook 中通过 install-run-rush
脚本调用配置在 commoand-line
下的命令。比如在 common/git-hooks/pre-commit
中:
#!/bin/sh
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
# Invoke the "rush prettier" custom command to reformat files whenever they
# are committed. The command is defined in common/config/rush/command-line.json
# and uses the "rush-prettier" autoinstaller.
node common/scripts/install-run-rush.js prettier || exit $?
配置自定义命令
扩展 rush xx
命令。command kind 有两种类型:
-
global
: 全局配置调用 shell 或者autoinstaller
模块
bulk
: Rush 的 “bulk” 类型指令将会在每个项目中都被调用,Rush 会寻找项目 package.json 内的 “scripts” 字段中匹配该命令行的字段。
pnpm 结合的问题
hoist 无法生效,只能提升到 common/temp/node_modules
下, cli 一般可以搞定
VSC 的结合问题
由于没有 root 的 package.json 和 node_moldules
, 大概率会影响 eslint 等插件找对于命令的路径。可能找不到, rushstack/eslint/eslint-config at main · microsoft/rushstack
- 配置 workspace
- 能单独打开子目录,调用
rushx
开发 - 子目录下,还是有
node_modules
的, 所以通过monorepo
单独打开某个模块,是能正常工作的 - root 目录下, 不建议配置
prettier
这种全局的配置, 因为这一层没有node_modules
,
配置 workspace
- 创建
xxx-workspace
文件,指定相关的 dolfers. - 安装 Monorepo Workspace - Visual Studio Marketplace 插件,自动识别 rushjs 中的项目, 并且可以选择对于的子项目打开。
eslint 的问题
vscode 加载 .eslint
配置文件的时候, 可能无法找到对于的 node_modules
, 从而失败。
// This is a workaround for https://github.com/eslint/eslint/issues/3458
require('@rushstack/eslint-config/patch/modern-module-resolution');
prettier 的问题
默认 prettier 也是会使用 node_module
的。 这是它 resolution 逻辑: Prettier - Code formatter - Visual Studio Marketplace, Version 10.0.0 of this extension will include prettier 3.0.0 after it is out of preview.
CI 构建
执行 rush install 或者 rush update 的时候,会生成 common/scripts
相关文件, 文件在这里 rushstack/libraries/rush-lib/src/scripts at main · microsoft/rushstack
新项目, 需要先执行 rush update
, 不然直接运行 rush install
报错:
ERROR: The standard files in the "common/scripts" folders need to be updated for this Rush version. Please run "rush update" and commit the changes.
启用 CI | Rush, 项目初始化, 就会带有 common/scripts/xxx.js
脚本。这些脚本的作用:
install-run-rush.js
- 寻找 rush.json 文件
- 读取指定在该文件内的 rushVersion
- 自动在 common/temp/install-run 目录下安装该版本的 Rush
- 使用仓库内的 .npmrc 文件进行适当的设置
- 之后调用 Rush 工具链,并传递给它任何你提供的命令行参数
install-run-rushx.js
, install-run-pnpm.js
都是封装了 install-run-rush
的调用。
install-run.js
执行任意的 NPM 包。比如: node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io
,
它必须要包含包名和其版本(可以是语义化版本,但最好写确定版本),它还需要第二个参数来指令可执行文件的具体名称
一些 Tips
没有 root 的 package.json
的问题
对于大多数 monorepo 的项目结构, 都会有 root 层级的 package.json
, 里面会安装上对整个项目全局而言的共享依赖, 比如 typescript
, eslint
。如果 rush.js 没有这个 package.json
以及对于的 node_modules
那么 IDE 的插件识别上, 可能会有问题。并且如何使用 husky
这种默认要求 root 有 package 的包, 就会冲突。
但对于子包而言,通常他们自身也是独立可运行的项目,顶层的 ”共享“ 依赖, 他们自己也有依赖声明和安装。
解决方案:
- 寻找 rush.js 内置的替代方案, 比如
git hook
- 包通过全局安装,比如
prettier
- 创建自动安装程序, Autoinstallers | Rush,这相当于一个自定义的 npm 包, 配置我们自定义一个
commonad
调用:启用 Prettier | Rush install-run.js
去执行
如何配置 eslint/prettier/husky 等工具
- How can I integrated rush with ‘lint-staged’ package? · Issue #10 · microsoft/rush-example
- 启用 Prettier | Rush
- 自带了 hook 可替代 husky