点赞 + 收藏 === 学会🤣🤣🤣
本文首发于本人公众号:ObjectX前端实验室
本文不会去介绍分析
monorepo选型,想看选型和分析的可以去 这篇文章了解,本文的目的是讲解rush对于monorepo的实践操作,给出一个rush搭建的项目模板。
🥑 你能学到什么?
希望在你阅读本篇文章之后,不会觉得浪费了时间。如果你跟着读下来,你将会学到:
monorepo演进- 如何使用
rush管理monorepo pnpm workspace工作空间
系列文章
本系列是一个从0到1的实现过程,如果您有耐心跟着实现,您可以实现一个完整的react18 + ts5 + webpack5 + 代码质量&代码风格检测&自动修复 + storybook8 + rollup + git action实现的一个完整的组件库模板项目。如果您不打算自己配置,也可以直接clone组件库仓库切换到rollup_comp分支即是完整的项目,当前实现已经足够个人使用,后续我们会新增webpack5优化、按需加载组件、实现一些常见的组件封装:包括但不限于拖拽排序、瀑布流、穿梭框、弹窗等
- 【前端工程化】项目搭建篇-项目初始化&prettier、eslint、stylelint、lint-staged、husky
- 【前端工程化】项目搭建篇-配置changelog、webpack5打包
- 【前端工程化】项目搭建篇-引入react、ts、babel解析es6+、配置css module
- 【前端工程化】组件库搭建篇-引入storybook、rollup打包组件、本地测试组件库
- 【前端工程化】包管理器篇-三大包管理器、npm工程管理、npm发布流程
- 【前端工程化】自动化篇-Github Action基本使用、自动部署组件库文档、github3D指标统计
- 【前端工程化】自动化篇-手写脚本一键自动tag、发包、引导登录npm
- 【前端工程化】monorepo篇-rush管理monorepo实践
效果
我之前已经搭建过一个项目的了,仓库 在这里,可以先clone下来体验一下
安装一下依赖,注意这里用的是pnpm,如果没有安装自行安装一下
需要先全局安装下rush
pnpm add -g @microsoft/rush
使用pnpm全局安装如果是首次使用pnpm的话,需要设置下全局目录,使用如下命令
pnpm setup
后续会提示让你刷新bash配置,根据提示刷新一下再全局安装就行了
rush update
跑项目,start是我们用rush自定义的一个全局命令,后续会讲解模板项目是如何搭建的
rush start
一、仓库管理方式的演进
| 阶段 | 描述 | 优点 | 缺点 | 代码管理方式 |
|---|---|---|---|---|
| 阶段一 | 单仓库巨石应用,一个 Git 仓库维护项目代码 | 简单易管理,项目初期效率较高 | 随着业务复杂度增加,代码量增多,构建效率降低,最终导致单体巨石应用 | Monolith |
| 阶段二 | 多仓库多模块应用,将项目拆分为多个业务模块,在多个 Git 仓库中管理 | 模块解耦,复杂度降低,模块可独立编码、测试、发版,构建效率提升 | 跨仓库代码难共享,依赖管理复杂,工程管理难度增加 | MultiRepo |
| 阶段三 | 单仓库多模块应用,将多个项目集成到一个仓库下,统一工程配置并共享模块代码 | 跨模块代码共享方便,依赖管理简化,构建耗时减少 | 需要管理较大的单仓库,可能带来一定的工程复杂性 | MonoRepo |
二、rush的基本使用
简介
Rush 是由 Microsoft 开发的一个开源工具,用于管理 monorepo的构建和依赖管理。Rush 旨在解决 monorepo 项目中的常见问题,例如依赖管理复杂、构建效率低下、发布流程繁琐等。通过 Rush,可以更高效地管理大型项目,并确保团队成员之间的一致性。
概念&功能
- Monorepo:
Rush主要用于管理monorepo项目。在monorepo中,多个相关的项目存储在同一个代码库中,这样可以更方便地共享代码、管理依赖,并进行跨项目的变更跟踪。 - 项目依赖管理:
Rush提供了集中化的依赖管理方式,通过单一命令安装和管理所有项目的依赖,而不是每个项目独立管理。 - 版本策略:
Rush支持多个版本策略,如固定版本策略、变动版本策略等,帮助团队保持版本的一致性。 - 构建和发布工具链:
Rush提供了一系列工具,用于自动化构建和发布流程,支持增量构建和缓存构建结果,从而提高效率。
优点
- 依赖管理一致性:
Rush强制所有项目使用相同版本的依赖库,避免了依赖不一致导致的问题。它通过生成一个shrinkwrap文件,确保团队中所有成员使用相同的依赖版本。 - 构建效率高:
Rush支持增量构建和缓存机制,只对变更的部分进行重新构建,这大大提高了构建速度,特别是在大型monorepo中表现尤为明显。 - 自动化的发布流程:
Rush提供了版本控制和发布管理工具,使得发布新版本变得更加简单和可控。你可以轻松地管理多项目的版本更新和发布流程。 - 灵活的工作流配置:
Rush支持自定义工作流,允许开发者根据项目的需要定义不同的构建和发布步骤。 - 跨项目依赖管理:
Rush可以很好地处理多个项目之间的依赖关系,并确保这些依赖关系在构建和发布过程中得到正确处理。 - 社区支持:
Rush是由Microsoft维护的,拥有良好的社区支持和文档,适合大型企业使用。
缺点
- 学习曲线: 对于新手来说,
Rush的概念和配置可能比较复杂,需要时间来学习和掌握,特别是在与其他工具(如Yarn、Lerna等)对比时。 - 初始配置复杂: 初次设置
Rush时,可能需要进行较多的配置工作,尤其是对于现有项目迁移到Rush的过程中,配置可能会比较繁琐。 - 生态系统限制: 虽然
Rush兼容npm、Yarn、PNPM等工具,但在某些情况下,可能会遇到与其他工具或插件不兼容的问题。 - 特定场景适用性:
Rush主要面向大型monorepo项目管理,对于小型或独立项目,可能会显得过于复杂和笨重。 - 依赖于缓存机制:
Rush的性能提升部分依赖于其缓存机制,尽管这通常是一个优点,但在某些情况下,缓存可能会导致问题,例如缓存不一致或缓存失效。
总结
Rush 的概念和配置可能比较复杂,需要时间来学习和掌握,很多理念我们可能用不到,实践中我们仅用到了rush管理多包,rush自定义命令,这两个足以。
三、实践-实现一个rush管理的monorepo仓库模板
使用rush初始化
rush init
它将会生成以下文件:(更多信息可查阅 配置文件参考)
| 文件 | 用途 |
|---|---|
| rush.json | Rush 内主要的配置文件 |
| .gitattributes | (如果你不用 Git 可以删除) 告诉 Git 不要对哪些 shrinkwrap 文件进行合并,因为该操作并不安全 |
| .gitignore | (如果你不用 Git 可以删除) 告知 Git 不要跟踪哪些文件 |
| .travis.yml | (如果你不用 Travis 可以删除) 配置 Travis CI 服务来在 PR 中使用 Rush |
| common/config/rush/.npmrc | Rush 用该文件配置源,无论是 PNPM, NPM 或者 Yarn |
| common/config/rush/command-line.json | 用于自定义 Rush 的命令行命令或参数 |
| common/config/rush/common-versions.json | 用于指定 NPM 包的版本,它影响 Rush 仓库下所有项目 |
| common/config/rush/pnpmfile.js | (如果不使用 PNPM 可以删除) 用于解决 package.json 文件下错误的依赖关系 |
| common/config/rush/version-policies.json | 用于定义发布配置 |
注意: 如果你的分支中已经存在这些文件,rush init 将会发出警告并且不会覆盖已有的文件。版本不一致可能有一些不一致,问题不大,大多数不用关注
git初始化一下
保存下,方便后面出问题回退
增加项目
创建projects目录,可以用vite新建项目,或者搬已有的项目,模板仓库里面的都是我用vite创建,改的,我就直接复制过来了,一个test-core,一个test-ui,配置的话看过之前的文章应该都能理解,我们这里只讲解rush的配置
更改rush.json的projects为
"projects": [
{
"packageName": "test-ui",
"projectFolder": "projects/test-ui"
},
{
"packageName": "test-core",
"projectFolder": "projects/test-core"
}
]
两个项目的依赖关系是test-ui使用了test-core,我们需要在test-ui的package.json中加上工作空间
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"test-core": "workspace:*"
},
然后rush update首次初始化安装下依赖,你将会发现 rush update 创建了一些新文件:
- common/scripts/install-run-rush.js: 用于在 CI 任务中以一种可靠的方式来启动 Rush
- common/scripts/install-run.js: 用于在 CI 任务中以一种可靠的方式来启动任意工具
rush build构建一下,查看rush是否接管成功
autoinstaller: 编写自定义启动脚本
如何自定义命令
common/config/rush/command-line.json 下有一个配置文件用于自定义指令和参数,你的配置文件应该满足 command-line.schema.json 范式.
自定义指令: 你可以像 Rush 内置的指令(例如 rush build, rush check 等)一样自定义自己的指令,这有两种类型:
- bulk: bulk 类型的指令会在每个项目中被调用,其原理与
rush build. 设定"enableParallelism": true后,项目可以并行运行。 - global: global 类型的指令会在整个仓库内执行指定的脚本文件。
你也可以自定义命令行“参数”。一个参数可以通过 associatedCommands 来被一个或多个指令关联。你甚至可以将自定义参数关联到 Rush 内置的 build 和 rebuild 指令上。在上述示例中,我们将 --ship 参数关联到 rush build, rush rebuild 和自定义的 rush import-strings 上。
目前有三种 parameterKind 类型:
- flag: flag 是一个布尔属性的参数,例如
--ship. - choice: choice 是一个从列表选择的额外参数,例如
--locale fr-fr. - string: string 是一个可以接受任何字符串值的参数,例如
--name my-new-package.
修改command-line.json
如下增加自定义命令,parameters暂时不用管,后续文章会讲解
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
"commands": [
{
"commandKind": "global",
"name": "start",
"summary": "启动项目",
"description": "启动项目",
"safeForSimultaneousRushProcesses": true,
"shellCommand": "path脚本路径",
// 自动安装器名称,不写这个,脚本的依赖不会自动安装
"autoinstallerName": "script-cli"
}
],
"parameters": [
]
}
增加自动安装器
脚本都写着新建的这个安装器下面,使用此命令后common目录下会新增一个autoinstallers目录,common/autoinstallers/script-cli
rush init-autoinstaller --name script-cli
增加脚本
增加utils.js,rushx 是 Rush 工具中的一个命令,用于执行在项目的 package.json 文件中定义的脚本中命令
const path = require("path");
// 引入 Node.js 的 path 模块,用于处理文件和目录路径
const shell = require("shelljs");
// 引入 shelljs 模块,用于在 Node.js 中执行 shell 命令
/**
* 在项目 projectName 中执行 command
* @param {string} projectName - 项目名称
* @param {string} command - 要在项目中执行的命令
* @param {function} cb - 可选的回调函数,在命令执行完成后调用
*/
function runProject(projectName, command, cb) {
// 使用 path.join 将项目路径拼接成一个完整的路径
const projectPath = path.join(__dirname, `../../../projects/${projectName}`);
// 使用 shell.exec 执行命令,将目录切换到项目路径并执行指定命令
shell.exec(
`cd ${projectPath} && rushx ${command}`,
(code, stdout, stderr) => {
// 检查命令的退出码,如果不为 0,表示命令执行出错
if (code !== 0) {
console.error(projectName, "执行命令出错:", stderr);
// 输出错误信息并退出当前进程
process.exit(1);
}
// 如果提供了回调函数,则执行回调函数
cb && cb();
}
);
}
/**
* 检查dist是否存在,如果不存在,需要先 build,之后再执行 command
* @param {Array} projectPaths - [打包顺序数组]
* @param {function} cb - 回调函数,在检查和构建完成后调用
*/
function preStart(projectPaths, cb) {
// 遍历传入的项目路径
projectPaths.forEach((projectPath) => {
// 将每个项目路径拼接成完整路径
const completePath = path.join(__dirname, `../../../${projectPath}`);
// dist 目录不存在
if (!shell.test("-d", `${completePath}/dist`)) {
// 输出提示信息,表示正在打包
console.log(`${projectPath} 不存在,正在打包中...`);
// 执行打包命令,使用 rushx build
const buildResult = shell.exec(`cd ${completePath} && rushx build`);
// 检查构建是否成功
if (buildResult.code !== 0) {
// 如果构建失败,输出错误信息并删除 dist 目录
console.error(`打包 ${projectPath} 失败,正在退出...`);
shell.exec(`rm -rf ${completePath}/dist`);
// 退出当前进程
process.exit(1);
}
}
});
// 构建完成后,调用回调函数
cb();
}
module.exports = {
// 导出 runProject 和 preStart 函数供外部使用
runProject,
preStart,
};
增加start.js,内容如下
const { preStart, runProject } = require("./utils");
preStart(["projects/test-core", "projects/test-ui"], () => {
runProject("test-ui", "start");
});
启动项目
这样就达到了我们之前想要的效果
rush start
总结
本篇文章我们只是简单的实现了一个用rush管理的monorepo项目,但是很多配置,比如代码风格统一,代码质量检测,commitlint什么的我们还没有去实现,但是rush也是支持的,后续我们会将组件库项目和一个手写的博客项目改为monorepo,用rush管理会去实现其他的东西
待办
- monorepo发包脚本
- 组件库项目&博客项目搬家
- rush代码风格、代码质量管理
- ...