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