monorepo管理方案:利用lerna管理一个复杂的javascript项目

712 阅读8分钟

我报名参加金石计划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上。

image.png

在npm上注册一个组织:

image.png

lerna add 安装依赖

// 给所有的包安装lodash
lerna add lodash

// 给特点的包安装依赖
lerna add lodash packages/core

lerna clean:删除安装包

这个命令是删除所有包的依赖,即删除所有包里面的node_modules。

但是有个点需要注意,他不会删除包package.jsondependencies里面的内容,如果你再执行lerna add lodash packages/core,它会报错,这个时候需要手动删除dependencies里面的内容。

不过你可以采用lerna bootstrap这个命令,它的意思是重新安装依赖,因为此时dependencies里面是有内容的,所以它能帮你重新安装。

lerna link:包与包之间的相互依赖

现在本地的core包需要依赖本地的utils包:

image.png

首先在core包里面的package.json里面加上:

"dependencies": {
  "@yijiang-cli-dev/utils": "^1.0.0"
}

然后执行lerna link,就会发现在core包里面的node_modules有utils的包

image.png

【注意】node_modules里面的utils包它其实是一个软链接,它指向本地的utils文件夹

image.png

如果不借助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: 升级版本

image.png

lerna publish: 发布

它其实包含了lerna version的命令

image.png

这个命令需要注意几个点:

  • 首先要进行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都提供了对应的命令方法。