monorepo管理
对于维护过多个package(功能相近)的同学来说,都会遇到一个选择题,这些package是放在一个仓库里维护还是放在多个仓库里单独维护。Multirepo 是比较传统的做法,即每一个 package 都单独用一个仓库来进行管理。Monorepo 是管理项目代码的一个方式,指在一个项目仓库 (repo) 中管理多个模块/包 (package),不同于常见的每个模块建一个 repo。
目前各大流行开源项目其实都用了monorepo管理,例如Vue、React、Babel等等。monorepo管理的项目的第一级目录的内容以脚手架为主,主要内容都在 packages目录中、分多个 package 进行管理。
目录结构如下:
1 | ├── packages |
monorepo 最主要的好处
- 统一的工作流
- Code Sharing
比如我想看一个 pacakge 的代码、了解某段逻辑,不需要找它的 repo,直接就在当前 repo;当某个需求要修改多个 pacakge 时,不需要分别到各自的 repo 进行修改、测试、发版或者 npm link,直接在当前 repo 修改,统一测试、统一发版。只要搭建一套脚手架,就能管理(构建、测试、发布)多个 package。
lerna
Lerna是一个管理多个 npm 模块的工具,是 Babel 自己用来维护自己的 Monorepo 并开源出的一个项目。优化维护多包的工作流,解决多个包互相依赖,且发布需要手动维护多个包的问题。
安装
全局安装lerna
1 | npm i -g lerna |
初始化项目
1 | lerna init |
其中 lerna.json & package.json
1 | // package.json |
创建npm包
1 | lerna create pkg-1 |
在packages目录下新建了pkg-1、pkg-2
新增依赖
1 | lerna add chalk // 会给所有 package 增加 chalk 依赖 |
添加依赖
添加内部依赖
依赖包管理
上述1-4步已经包含了 Lerna 整个生命周期的过程了,但当我们维护这个项目时,新拉下来仓库的代码后,需要为各个 package 安装依赖包。
我们在 lerna add 时也发现了,为某个 package 安装的包被放到了这个 package 目录下的 node_modules 目录下。这样对于多个 package 都依赖的包,会被多个 package 安装多次,并且每个 package 下都维护 node_modules ,也不清爽。于是我们使用 –hoist 来把每个 package 下的依赖包都提升到工程根目录,来降低安装以及管理的成本。
1 | lerna bootstrap --hoist |
会发现根目录多了node_modules,里面的依赖就是之前在pkg-1、pkg-2安装的依赖
配置一些命令,方面我们运行依赖提升命令
1 | // package.json |
也可以配置lerna,节省 –hoist后缀
1 | // lerna.json |
对于之前被安装在各个package里面的多余依赖,可以使用相关命令做清理
1 | lerna clean |
清理前
清理后,node_modules被删除
Lerna + Monorepo 最佳实践
yarn workspace
环境搭建
普通项目:clone下来后通过yarn install,即可搭建完项目,有时需要配合postinstall hooks,来进行自动编译,或者其他设置。
monorepo: 各个库之间存在依赖,如A依赖于B,因此我们通常需要将B link到A的node_module里,一旦仓库很多的话,手动的管理这些link操作负担很大,因此需要自动化的link操作,按照拓扑排序将各个依赖进行link
安装yarn
1 | npm install yarn --global // 全局安装 |
配置workspace
1 | // package.json |
运行yarn install,安装依赖,通过使用workspace,yarn install会自动的帮忙解决安装和link问题
1 | yarn install # 等价于 lerna bootstrap --npm-client yarn --use-workspaces |
清理环境
在依赖乱掉或者工程混乱的情况下,清理依赖
- 普通项目: 直接删除node_modules以及编译后的产物。
- monorepo: 不仅需要删除root的node_modules的编译产物还需要删除各个package里的node_modules以及编译产物
解决方式:使用lerna clean来删除所有的node_modules,使用yarn workspaces run clean来执行所有package的清理工作
1 | lerna clean # 清理所有的node_modules |
安装|删除依赖
普通项目: 通过yarn add和yarn remove即可简单解决依赖库的安装和删除问题
monorepo: 一般分为三种场景
- 给某个package安装依赖:yarn workspace packageB add packageA 将packageA作为packageB的依赖进行安装
- 给所有的package安装依赖: 使用yarn workspaces add lodash 给所有的package安装依赖
- 给root 安装依赖:一般的公用的开发工具都是安装在root里,如typescript,我们使用yarn add -W -D typescript来给root安装依赖
对应的三种场景删除依赖如下
1 | yarn workspace packageB remove packageA |
项目构建
- 普通项目:建立一个build的npm script,使用yarn build即可完成项目构建
- monorepo:区别于普通项目之处在于各个package之间存在相互依赖,如packageB只有在packageA构建完之后才能进行构建,否则就会出错,这实际上要求我们以一种拓扑排序的规则进行构建。
我们可以自己构建拓扑排序规则,很不幸的是yarn的workspace暂时并未支持按照拓扑排序规则执行命令,虽然该 rfc已经被accepted,但是尚未实现, 幸运的是lerna支持按照拓扑排序规则执行命令, –sort参数可以控制以拓扑排序规则执行命令
1 | lerna run --stream --sort build |
会执行所有package下的build命令
版本升级及发包
项目测试完成后,就涉及到版本发布,版本发布一般涉及到如下一些步骤
- 条件验证: 如验证测试是否通过,是否存在未提交的代码,是否在主分支上进行版本发布操作
- version_bump:发版的时候需要更新版本号,这时候如何更新版本号就是个问题,一般大家都会遵循 semVer语义,
- 生成changelog: 为了方便查看每个package每个版本解决了哪些功能,我们需要给每个package都生成一份changelog方便用户查看各个版本的功能变化。
- 生成git tag:为了方便后续回滚问题及问题排查通常需要给每个版本创建一个git tag
- git 发布版本:每次发版我们都需要单独生成一个commit记录来标记milestone
- 发布npm包:发布完git后我们还需要将更新的版本发布到npm上,以便外部用户使用
我们发现手动的执行这些操作是很麻烦的且及其容易出错,幸运的是lerna可以帮助我们解决这些问题
yarn官方并不打算支持发布流程,只是想做好包管理工具,因此这部分还是需要通过lerna支持
lerna提供了publish和version来支持版本的升级和发布, publish的功能可以即包含version的工作,也可以单纯的只做发布操作。
代码提交
commitizen && cz-lerna-changelog
commitizen 是用来格式化 git commit message 的工具,它提供了一种问询式的方式去获取所需的提交信息。
cz-lerna-changelog 是专门为 Lerna 项目量身定制的提交规范,在问询的过程,会有类似影响哪些 package 的选择。如下:
我们使用 commitizen 和 cz-lerna-changelog 来规范提交,为后面自动生成日志作好准备。
因为这是整个工程的开发依赖,所以在根目录安装:
1 | yarn add -D commitizen |
安装完成后,在 package.json 中增加 config 字段,把 cz-lerna-changelog 配置给 commitizen。同时因为commitizen不是全局安全的,所以需要添加 scripts 脚本来执行 git-cz
1 | { |
参考文献
- 本文标题:使用 lerna 优雅管理多项目
- 本文作者:Lin.
- 创建时间:2022-05-18 20:42:02
- 本文链接:http://www.linzefan.top/hexo-blogs2022/05/18/初探lerna/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!