monorepo管理方案:利用lerna管理一个复杂的javascript项目
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第10篇文章,点击查看活动详情
什么是 Monorepo管理方案?
现在的前端项目体积已经越来越大,业内采用的方案是分包策略,就是把一个大项目分成多个子项目进行管理,这就是 multirepo
管理方案。但是这种方式有两个致命的问题:
multirepo
缺点一:复用难
在维护多个项目的时候,会经常出现这么一个场景:有一些包会被其他包多次引用,比如一些基础的组件、工具函数,或者一些配置。
如果直接把代码 copy 过来,虽然比较省事,但如果这些代码出现 bug、或者需要做一些调整的时候,就得修改多份,维护成本越来越高。
那如何来解决这个问题呢?比较好的方式是将公共的逻辑代码抽取出来,作为一个 npm 包进行发布,一旦需要改动,只需要改动一份代码,然后 publish 就行了。
现在又有一个场景:在开发过程中,A包引入了工具包B,此时工具包B出现了问题,并进行修复后重新发了一个新包,此时A包必须重新安装B包。
有可能B包只改动了一行代码,但是要走这么多流程,而且开发阶段很难保证不出 bug 的。
我们只是想复用一下代码,为什么每次修改代码都要这么复杂?
multirepo
缺点一:重复工作多
在 multirepo
中,各个项目的工作流是割裂的,每个项目都需要单独配置开发环境、配置 CI 流程、配置部署发布流程等等,甚至每个项目都有自己单独的一套脚手架工具。
可以发现,项目中很多基建的逻辑都是重复和不必要的,而且各个项目间存在构建、部署和发布的规范不统一的情况,这样维护起来就更加麻烦了。
于是就诞生了monorepo
管理方案,概念上很好理解,就是把多个项目放在一个仓库里面,相对立的是传统的 multiRepo
模式,即每个项目对应一个单独的仓库来分散管理。
monorepo 的优点
1. 工作流的一致性
由于所有的项目放在一个仓库当中,复用起来非常方便,如果有依赖的代码变动,那么用到这个依赖的项目当中会立马感知到。并且所有的项目都是使用最新的代码,不会产生其它项目版本更新不及时的情况。
2. 基建成本低
所有项目复用一套标准的工具和规范,这样只需要很少的人来维护所有项目的基建,维护成本也大大减低。
3. 团队协作也更加容易
一方面,大家都在一个仓库开发能够方便地共享和复用代码;另一方面,git commit
的历史记录也支持以功能为单位进行提交,之前对于某个功能的提交,需要改好几个仓库,提交多个 commit,现在只需要提交一次,简化了 commit 记录,方便协作。
lerna 简介
我们知道了这么多 monorepo
的好处,可是不知道怎么用?是直接把所有的代码全部搬到一个仓库就可以了吗? 当然不是,还需要考虑项目间依赖分析、依赖安装、构建流程、测试流程、CI 及发布流程等诸多工程环节。
因此,要想从零开始定制一套完善的 monorepo
的工程化工具,是一件难度很高的事情。不过社区已经提供了一些比较成熟的方案,比如lerna
。
Lerna 是一个优化基于 git+npm 的多 package 项目的管理工具。它的优势是:
- 大幅减少重复操作
- 提升操作的标准化
Lerna 是架构优化的产物,它揭示了一个架构真理:项目复杂度提升后,就需要对项目进行架构优化。架构优化的主要目标往往都是以效能为核心。减少重复操作是提升效能的表现,标准化是为了保证不出错,不出错也是提高效能的表现。
lerna 命令
创建package
lerna create 创建包
// 创建core包
lerna create core
输入包名,一般像core这样的包名肯定在npm上被注册了,所以我们需要使用一个组织来进行区别,这样最后才能上传到npm上。
在npm上注册一个组织:
lerna add 安装依赖
// 给所有的包安装lodash
lerna add lodash
// 给特点的包安装依赖
lerna add lodash packages/core
lerna clean:删除安装包
这个命令是删除所有包的依赖,即删除所有包里面的node_modules。
但是有个点需要注意,他不会删除包package.json
的dependencies
里面的内容,如果你再执行lerna add lodash packages/core
,它会报错,这个时候需要手动删除dependencies
里面的内容。
不过你可以采用lerna bootstrap
这个命令,它的意思是重新安装依赖,因为此时dependencies
里面是有内容的,所以它能帮你重新安装。
lerna link:包与包之间的相互依赖
现在本地的core包需要依赖本地的utils包:
首先在core包里面的package.json里面加上:
"dependencies": {
"@yijiang-cli-dev/utils": "^1.0.0"
}
然后执行lerna link
,就会发现在core包里面的node_modules有utils的包
【注意】node_modules里面的utils包它其实是一个软链接,它指向本地的utils文件夹
如果不借助lerna link
,那么你需要进入到core包中,然后执行npm link @yijiang-cli-dev/utils
才能完成引用,如果你的依赖很多这个工作量将会非常的大。
开发与测试
lerna exec 执行shell脚本
它表示在所有的包执行shell命令,比如删除所有包的node_modules:
npx lerna exec -- rm -rf node_modules
如果只想删除某个包下面的东西,可以加上--scope
,注意后面带的是包的名称,不是文件夹名。
npx lerna exec --scope=@yijiang-cli-dev/core -- rm -rf node_modules
lerna run 执行npm的脚本
如果每个包里面package.json
有如下npm脚本命令:
// core
"scripts": {
"test": "echo \"run at core\" "
}
// utils
"scripts": {
"test": "echo \"run at utils\" "
}
当执行lerna run test
时,就会执行每个包里面的npm run test
命令。如果执行某个特点的包的npm命令,加上--scope=@yijiang-cli-dev/core
即可。
lerna 发布命令
lerna version: 升级版本
lerna publish: 发布
它其实包含了lerna version的命令
这个命令需要注意几个点:
- 首先要进行npm的登录
npm login
- 在每个包中的
package.json
中加上:
"publishConfig": {
"access": "public"
}
- 在根目录下加上一个
LICENSE.md
的文件
脚手架开发为什么要选用monorepo
模式
脚手架开发项目一般都是比较复杂的项目,而是现在前端流行的包,比如babel
, vue-cli
, create-react-app
都是采用monorepo
这种方式管理的,如果有一天让你接手babel
的管理,你有信心吗?因此,学习monorepo
管理模式也是高级工程师的必修课。
下面我们来看下一个复杂的项目,有什么痛点:
痛点一:重复操作
多 Package 本地 link
我们已经知道,在一个项目中 package A 包引用了 package B 包是通过npm link
来实现的,但是如果一个项目中包含很多包,比如babel
这个项目里面有几十个包,如果有一个工具包被这几十个包引用,难道我们需要执行几十次npm link
吗?
多 Package 依赖安装
现在有一个需求,需要把babel
下面几十个包的node_modules
都删除掉,然后再执行npm install
,如果手动的执行,将浪费大量的时间。
多 Package 单元测试
如果我需要对babel
里面的几个包进行单元测试,难道我每次都要执行这样的命令npm run test
吗?就需要有个工具能统一执行测试命令。
多 Package 代码管理
如果把babel
下面每个包都建立一个git仓库的话,是将无法管理的,所以都把是这个包放在一个git仓库进行管理。
多 Package 代码发布
发布和代码管理不一样,我们需要把每个包都发成一个单独的npm
包,这个工作将会耗费非常多的工作量,还有包的version
需要统一升级等。如果没有相应的工具,对于大型项目将会无从下手。
痛点二:版本一致性
发布时版本一致性
我希望每个包的版本保持一致,当然像babel
这样的包是无法做到的,但是对于我们自己开发的项目保持版本一直就非常多的好处,能省去很多麻烦,要升一起升,要降一起降。
发布后相互依赖版本升级
比如一个包的版本现在从1.0.0
升级为2.0.0
,那么其他依赖这个包的package.json
都要进行升级,这如果手动操作,工作量将会非常大。
package 越多,管理复杂度越高,如果你的包只有两三个,则手动管理即可。
总结
本文首先分析mutirepo
的缺点,它存在复用难和重复工作的问题,于是业界对于复杂项目普遍采用monorepo
的管理模式。
目前社区流行的monorepo
工具是lerna
,它能方便我们创建、管理、发布我们的项目,省去了很多重复的工作。
最后介绍了lerna
的使用,包括在开始时如何创建包,在开发和测试过程中怎么管理包,以及最后发布到npm上。lerna
都提供了对应的命令方法。
转载自:https://juejin.cn/post/7147532488682766372