随着前端工程日益复杂,某些业务或者工具库通常涉及到很多个仓库,那么时间一长,多个仓库开发弊端日益显露,由此出现了一种新的项目管理方式——Monorepo
。本文主要以 Monorepo
的概念、MultiRepo
的弊端、Monorepo
的收益以及 Monorepo
的落地这几个角度来认识和学习一下 Monorepo
,文末会有思考题,欢迎大家来踊跃讨论。
什么是 Monorepo
?
Monorepo
其实不是一个新的概念,在软件工程领域,它已经有着十多年的历史了。概念上很好理解,就是把多个项目放在一个仓库里面,相对立的是传统的 MultiRepo
模式,即每个项目对应一个单独的仓库来分散管理。
现代的前端工程已经越来越离不开 Monorepo
了,无论是业务代码还是工具库,越来越多的项目已经采用 Monorepo
的方式来进行开发。Google
宁愿把所有的代码都放在一个 Monorepo
工程下面,Vue 3
、Yarn
、Npm7
等等知名开源项目的源码也是采用 Monorepo
的方式来进行管理的。
一般 Monorepo
的目录如下所示,在 packages
存放多个子项目,并且每个子项目都有自己的package.json
:
├── packages
| ├── pkg1
| | ├── package.json
| ├── pkg2
| | ├── package.json
├── package.json
那 Monorepo
究竟有什么魔力,让大家如此推崇,落地如此之广呢?
MultiRepo
之痛
要想知道 Monorepo
的优势,首先得弄清楚之前的开发方式有什么痛点。
之前传统的方式 MultiRepo
当中,每个项目都对应单独的一个代码仓库。我之前也是用这种方式开发的,是真真切切地感受到了这种方式带来的诸多弊端。现在就和大家一一分享一下。
代码复用
在维护多个项目的时候,有一些逻辑很有可能会被多次用到,比如一些基础的组件、工具函数,或者一些配置,你可能会想: 要不把代码直接 copy
过来,多省事儿!但有个问题是,如果这些代码出现 bug
、或者需要做一些调整的时候,就得修改多份,维护成本越来越高。
那如何来解决这个问题呢?比较好的方式是将公共的逻辑代码抽取出来,作为一个 npm
包进行发布,一旦需要改动,只需要改动一份代码,然后 publish
就行了。
但这真的就完美解决了么?我举个例子,比如你引入了 1.1.0
版本的 A
包,某个工具函数出现问题了,你需要做这些事情:
- 去修改一个工具函数的代码
- 发布
1.1.1
版本的新包 - 项目中安装新版本的
A
。
可能只是改了一行代码,需要走这么多流程。然而开发阶段是很难保证不出 bug
的,如果有个按钮需要改个样式,又需要把上面的流程重新走一遍......停下来想想,这些重复的步骤真的是必须的吗?我们只是想复用一下代码,为什么每次修改代码都这么复杂?
上述的问题其实是 MultiRepo
普遍存在的问题,因为不同的仓库工作区的割裂,导致复用代码的成本很高,开发调试的流程繁琐,甚至在基础库频繁改动的情况下让人感到很抓狂,体验很差。
版本管理
在 MultiRepo
的开发方式下,依赖包的版本管理有时候是一个特别玄学的问题。比如说刚开始一个工具包版本是 v1.0.0
,有诸多项目都依赖于这个工具包,但在某个时刻,这个工具包发了一个 break change
版本,和原来版本的 API
完全不兼容。而事实上有些项目并没有升级这个依赖,导致一些莫名的报错。
当项目多了之后,很容易出现这种依赖更新不及时的情况。这又是一个痛点。
项目基建
由于在 MultiRepo
当中,各个项目的工作流是割裂的,因此每个项目需要单独配置开发环境、配置 CI
流程、配置部署发布流程等等,甚至每个项目都有自己单独的一套脚手架工具。
其实,很容易发现这些项目里的很多基建的逻辑都是重复的,如果是 10 个项目,就需要维护 10 份基建的流程,逻辑重复不说,各个项目间存在构建、部署和发布的规范不能统一的情况,这样维护起来就更加麻烦了。
分支管理
在 MultiRepo
的开发方式下,如果组内每个迭代承接业务需求很多,涉及到的工程可能多大五六个,有的业务需求走开发分支,有的业务需求走特性分支,有的需求在转测或者转演,有的要热修等等,这么多项目的分支管理特别复杂,很容易出现问题。
Monorepo
的收益
说清楚 MultiRepo
的痛点之后,相信你也大概能理解为什么要诞生 Monorepo
这个技术了。现在就来细致地分析一下 Monorepo
到底给现代的前端工程带来了哪些收益。
首先是工作流的一致性,由于所有的项目放在一个仓库当中,复用起来非常方便,如果有依赖的代码变动,那么用到这个依赖的项目当中会立马感知到。并且所有的项目都是使用最新的代码,不会产生其它项目版本更新不及时的情况。
其次是项目基建成本的降低,所有项目复用一套标准的工具和规范,无需切换开发环境,如果有新的项目接入,也可以直接复用已有的基建流程,比如 CI
流程、构建和发布流程。这样只需要很少的人来维护所有项目的基建,维护成本也大大减低。
再者,团队协作也更加容易,一方面大家都在一个仓库开发,能够方便地共享和复用代码,方便检索项目源码,另一方面,git commit
的历史记录也支持以功能为单位进行提交,之前对于某个功能的提交,需要改好几个仓库,提交多个 commit
,现在只需要提交一次,简化了 commit
记录,方便协作。
Monorepo
的落地
如果你还从来没接触过 Monorepo
的开发,到这可能你会疑惑了: 刚刚说了这么多 Monorepo
的好处,可是我还是不知道怎么用啊!是直接把所有的代码全部搬到一个仓库就可以了吗?
当然不是,在实际场景来落地 Monorepo
,需要一套完整的工程体系来进行支撑,因为基于 Monorepo
的项目管理,绝不是仅仅代码放到一起就可以的,还需要考虑项目间依赖分析、依赖安装、构建流程、测试流程、CI 及发布流程等诸多工程环节,同时还要考虑项目规模到达一定程度后的性能问题,比如项目构建/测试时间过长需要进行增量构建/测试、按需执行 CI
等等,在实现全面工程化能力的同时,也需要兼顾到性能问题。
因此,要想从零开始定制一套完善的 Monorepo
的工程化工具,是一件难度很高的事情。不过社区已经提供了一些比较成熟的方案,我们可以拿来进行定制,或者对于一些上层的方案直接拿来使用。
其中比较底层的方案比如 lerna,封装了 Monorepo
中的依赖安装、脚本批量执行等等基本的功能,但没有一套构建、测试、部署的工具链,整体 Monorepo
功能比较弱,但要用到业务项目当中,往往需要基于它进行顶层能力的封装,提供全面工程能力的支撑。
当然也有一些集成的 Monorepo
方案,比如 nx (官网写的真心不错,还有不少视频教程)、rushstack,提供从初始化、开发、构建、测试到部署的全流程能力,有一套比较完整的 Monorepo
基础设施,适合直接拿来进行业务项目的开发。不过由于这些顶层方案内部各种流程和工具链都已经非常完善了,如果要基于这些方案来定制,适配和维护的成本过高,基本是不可行的。
实例
我在搭建React
业务组件库时使用了lerna
管理多包存储库,单独发包,因为工程中只会使用某些业务组件,业务需求的变动只会改动某个业务组件,所以不能像通用组件库一样使用全体发布。
总结
总而言之,Monorepo
的开发模式就是将各自独立的项目,变成一个统一的工程整体,解决 MultiRepo
下出现的各种痛点,提升研发效率和工程质量。那最后我还有有一个问题,采用 Monorepo
解决了之前的痛点之后,产生了哪些新的问题呢?这些问题可以解决吗?欢迎大家在留言区一起讨论。
参考资料
[1] lerna: https://lerna.js.org/
[2] nx: https://nx.dev/latest/react/g...
[3] rushstack: https://rushstack.io/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。