本文首发于公众号:符合预期的CoyPan
写在前面
大概4年以前,我就初次接触了rush.js,并进行了初步的探索。
但那个时候并没有monorepo的需求,也就没有在项目中进行实践。由于业务需要,近期实践了一下 rush.js 管理 monorepo,本文进行一些总结。
rush.js 基本使用
安装
安装rush.js之前,需要注意一下node.js的版本。最新版的rush.js支持以下的node版本:
json
"nodeSupportedVersionRange": ">=14.15.0 <15.0.0 || >=16.13.0 <17.0.0 || >=18.15.0 <19.0.0",
执行以下命令全局安装rush.js:
javascript
yarn global add @microsoft/rush
或者
javascript
npm install -g @microsoft/rush
初始化repo
新建一个目录,然后进入到目录中,执行命令:rush init
即可初始化一个monorepo
初始化完成后,【可能】需要做的事情有以下几件:
1、在rush.json
中,指定包管理器。rush.js默认使用pnpm作为包管理器。这里的考量可以参考这篇文章:
json
/**
* The next field selects which package manager should be installed and determines its version.
* Rush installs its own local copy of the package manager to ensure that your build process
* is fully isolated from whatever tools are present in the local environment.
*
* Specify one of: "pnpmVersion", "npmVersion", or "yarnVersion". See the Rush documentation
* for details about these alternatives.
*/
"pnpmVersion": "6.7.1",
// "npmVersion": "6.14.15",
// "yarnVersion": "1.9.4",
2、在 common/config/rush/.npmrc
中修改 npm registry,在 common/config/rush/.npmrc-publish 中设置npm账号信息。
在rush管理的monorepo中,不需要再手动安装 pnpm 等包管理工具,也不再提倡使用诸如 npm install , pnpm install 等命令进行依赖安装管理。因为rush会为依赖创建软连接来统一进行管理。使用传统的命令会干扰这些软连接,导致问题。
3、rush.json
中,设置项目的git相关配置,以便后续管理。
json
"repository": {
/* git远程链接 */
"url": "https://github.com/CoyPan/rush-monorepo-example.git",
/* git默认主分支 */
"defaultBranch": "master"
"defaultRemote": "origin"
},
新建项目
基础环境ok后,开始进入正式的项目开发。本文使用以下的项目结构作为例子:
monorepo中共包含了6个项目,其中两个页面A、B,依赖了组件C和组件D,组件C和组件D又依赖了组件E和组件F。上图可以看做是一个项目依赖拓扑图。
我们的monorepo中, apps 目录表示页面,components目录表示组件。在各自的目录下,新建项目,进行初始化(npm init等)。
想要rush帮我们管理项目,需要在rush.json中注册项目。
这里需要注意的是,packageName
的值必须要和对应的项目的package.json
中的name一一对应,并且projectFolder
要指定为项目的根目录,否则,rush无法接管项目。shouldPublish
字段,表示是否需要将当前项目发布到npm上。这里根据情况设置即可,一般情况下,组件包是需要发布到npm的。
页面开发
假设我们的page-a是用create-react-app
生成的。
这里建议再使用rush update
命令,让rush来接管一下页面的依赖管理。
rush update 是一个全局命令。它会依据 package.json 文件安装依赖,并按需更新 shrinkwrap 文件(shrinkwrap 文件是存储仓库内所有项目的依赖和版本的中心,它被放到 "common/config/rush"文件夹下)。注意,Rush 会在一次性给仓库内的所有项目安装。 当你从 Git 上拉去文件,或者修改完 package.json 文件后,需要执行 "rush update" 才能开始工作。如果无需更新,则 "rush update" 会在瞬时完成。
按照以往的经验,要把page-a跑起来,直接在page-a目录下执行 npm start
即可。在rush中,推荐使用 rushx start
。rushx
命令相当于 npm run
,它会调用 package.json 文件中 "scripts"
字段中定义的 shell 脚本。
接下来我们给 page-a 引入我们的 component-c 。执行以下命令:
javascript
// 使用 rush add -p <packagename> 进行依赖安装。如果需要安装到devDependence,需要增加 --dev 标识
rush add -p componet-c
安装完成后,可以看到 page-a 的package.json里,多了component-c的依赖
component-c 的版本号为:workspace:*,表示引用的是当前monorepo中的组件。这样的好处是,方便本地的开发调试,并且可以保证 page-a 的component-c一直都是最新的版本。
如果我们想让 page-a 一直引用一个固定版本的 component-c,而不是本地的版本,需要在rush.json中进行配置decoupledLocalDependencies
。
json
{
"packageName": "page-a",
"projectFolder": "apps/page-a",
"shouldPublish": false,
// 设置`decoupledLocalDependencies`这个属性,表示排除本地的组件包,而使用线上的组件包版本。
"decoupledLocalDependencies": [
"component-c"
]
},
组件的发布
在组件开发完成后,git add 将代码提交到缓冲区,然后执行全局命令 rush change
,这个时候,rush会自动比较当前代码和默认分支上代码的区别,并让你输入相关的修改信息。
不会比较 rush.json 中设置了 shouldPublish: false 的项目
rush 会在common/changes
下生成对应的修改信息。
json
{
"changes": [
{
"packageName": "component-c",
"comment": "modify",
"type": "patch"
}
],
"packageName": "component-c"
}
执行rush publish -a -p
,会将修改信息应用到对应项目(这里为 component-c)的package.json中,修改版本号,接着发布到 npm 上。发布完成后,会在 component-c 的目录下生成 changelog 文件。这里可以提前把npm账号信息设置在 common/config/rush/.npmrc-publish
中,以免由于未登录导致发布失败。
在进行组件发布的时候,rush会根据依赖关系图,自动更新所有相关项目。例如,component-c
依赖了 component-d
,如果我们对 component-d
进行了更新,在发布component-d
时,也会自动更新 component-c
的版本号。
rush的自定义命令
考虑以下场景:
1、monorepo中的页面,都有一定的相似性。作为一个页面开发者,进入monorepo后,希望可以直接初始化得到页面脚手架代码。
2、配置lint-staged,在提交代码时自动进行代码风格修复。
这个时候,就需要用到自定义命令。如何配置自定义命令呢?
执行以下代码,
shell
rush init-autoinstaller --name ${name}
rush 会在 common/autoinstallers
目录下,新建一个目录。例如:
这是一个单独的目录,可以说是一个独立的项目,可以在这个目录下单独安装依赖,有自己单独的 pnpm-lock文件。
在package.json
中声明依赖,执行 rush update-autoinstaller --name ${name}
,即可安装依赖,生成pnpm-lock
文件。
接下来在common/config/rush/command-line.json
,在commands
配置项中新增以下配置:
json
{
// global表示设置为全局命令。还可以设置为 bulk, 表示依次执行每一个项目下的对应命令。
"commandKind": "global",
// 全局命令的名称
"name": "test-cli",
"summary": "测试",
"description": "测试",
"safeForSimultaneousRushProcesses": true,
// 这里的名字需要和autoinstaller目录中对齐
"autoinstallerName": "test-cli",
// 全局命令执行的脚本
"shellCommand": "node common/autoinstallers/test-cli/index.js"
}
test-cli
中的 index.js
代码如下:
js
const shell = require('shelljs');
const chalk = require('chalk');
console.log(chalk.green('hello'));
shell.exec('echo "world"');
接下来,执行 rush test-cli
,rush会确保当前命令所需依赖已经安装(如果没安装,则会自动安装依赖) ,然后运行test-cli
下的 index.js
。
在common/config/rush/command-line.json
的parameters
,还可以设置命令的参数。比如下面的例子:
json
// 强制要求 test-cli 命令需要输入一个字符串格式的 -t 参数
{
"parameterKind": "string",
"argumentName": "SOME_TEXT",
"longName": "--test",
"shortName": "-t",
"description": "传入一个参数",
"associatedCommands": ["test-cli"],
"required": true
}
配置git钩子
上文介绍了如何设置自定义命令。那么如何借助自定义命令,来设置git钩子呢。以配置 lint-staged
为例。
首先按照上面的步骤,配置一个lint-staged
的全局命令。
接着在根目录新建lint-staged.config.cjs
javascript
const config = {
'*': "echo 'hello world'",
};
module.exports = config;
在common/git-hooks
目录下,新建pre-commit
文件,写入以下内容:
至此,我们的配置就基本结束了。在执行git commit
时,会执行pre-commit
钩子,而这个钩子,会执行rush lint-staged
这个全局命令。
效果如下:
额外一点总结
上面较为详细的介绍了rush.js的一些实践,主要是面向monorepo的维护者。
而作为一名开发者,进入monorepo后,找到自己需要迭代的项目,直接执行rush install -t ${项目名}
命令,即可安装对应项目的依赖。再根据项目的package.json
中提供的命令,使用 rushx
代替npm run
来进行项目的开发、打包等相关操作。配合monorepo的自定义指令,就可以很方便的完成各种工作流。
写在后面
本文记录了rush
的一些基本实践,后续会根据使用情况继续进行总结,欢迎各位交流讨论。