rush.js 管理 monorepo 实践

本文首发于公众号:符合预期的CoyPan

写在前面

大概4年以前,我就初次接触了rush.js,并进行了初步的探索。

juejin.cn/post/684490...

但那个时候并没有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作为包管理器。这里的考量可以参考这篇文章:

rushjs.io/zh-cn/pages...

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 startrushx 命令相当于 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-clirush会确保当前命令所需依赖已经安装(如果没安装,则会自动安装依赖) ,然后运行test-cli 下的 index.js

common/config/rush/command-line.jsonparameters,还可以设置命令的参数。比如下面的例子:

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的一些基本实践,后续会根据使用情况继续进行总结,欢迎各位交流讨论。

相关推荐
贩卖纯净水.34 分钟前
网站部署及CSS剩余模块
前端·css
Justinc.43 分钟前
CSS3_BFC(十二)
前端·css·css3
梅子酱~1 小时前
Vue 学习随笔系列十七 -- 表格样式修改
javascript·vue.js·学习
刺客-Andy1 小时前
React第四节 组件的三大属性之state
前端·javascript·react.js
黄毛火烧雪下1 小时前
React 表单Form 中的 useWatch
前端·javascript·react.js
爱健身的小刘同学1 小时前
钉钉免登录接口
前端·javascript·钉钉
啵咿傲1 小时前
跨域相关的一些问题 ✅
前端
命运之光2 小时前
生日主题的烟花特效HTML,CSS,JS
前端·javascript·css
Cshaosun2 小时前
js版本之ES5特性简述【String、Function、JSON、其他】(二)
前端·javascript·es
__WanG2 小时前
Flutter将应用打包发布到App Store
前端·flutter·ios