Skip to content
On this page

背景

在了解 Monorepo 之前,先来了解一下 Multirepo 的概念。

Multirepo 风格,即每个项目对应着一个单独的仓库。在其内部遵循着 模块化组件化 的原则拆分代码,尽量不在一个项目中糅合太多东西,根据不同需求划分多个仓库,仓库间保持独立,每个项目都可以独立开发,独立部署,且不受其他项目影响。

vue-app
├─ src
└─ package.json
vue1-app
├─ src
└─ package.json
vue2-app
├─ src
└─ package.json
... # 另外多个仓库

痛点

时间一长,多个仓库共同管理的弊端就会暴露出来。

代码复用

我们一定都干过这个事情,就是在维护多个项目的时候遇到相同场景或者相同业务逻辑的时候,我们会将公共逻辑复制多份,去应用到多个项目中。

优点: 简单无脑 缺点:维护成本非常高

可能略微具备前端工程化知识的同学,会将公共逻辑进行抽取并将其封装成一个 npm包并发布,这样只需要在用到此逻辑的项目进行安装即可。

这样貌似解决了公共逻辑复用的问题,降低了维护成本(真的吗??)

如果公共逻辑出现 Bug ,就要修复后重新发布 npm ,之后再次安装。

不同于正常 npm 包的正常开发迭代,这种常用的包在开发阶段很难保证不出 Bug ,且有些 Bug 的修复,仅需要改动一行代码,所以真的有必要重复这些步骤吗?

发现 Bug ---> 修复 Bug ---> 发布 npm 包 ---> 重新安装 npm 包 --->发现新 Bug

这问题就是 Multirepo 的痛点,介于工作区的割裂,导致复用代码的成本剧增,开发调试的流程繁琐,在基础库改动频繁的情况下,会让开发人员的情绪暴躁,开发体验很差。

基建复用

与上述逻辑复用的情况相同,这个问题在项目基建中也一样存在。每个项目都会拥有自己的环境配置、构建、打包、CI/CD 等,这些代码块肯定也会存在很多公共部分被复用的情况。

比如基于 rollup 编写一个脚本去用来打包工具库,而打包组件库也可以用到,只是相比于工具库,需要加一些样式处理的逻辑,但是不能因此认为两种情况的逻辑是完全不同的就将它们区分开来。

有时发布一个需求可能要同时发布多个项目,项目之间存在的构建、测试、打包、部署和发布的规范不统一,这样维护起来就更麻烦了。

版本管理

管理多个项目时,每次定义和更新版本号都是一件很麻烦的事情。

确实,最近开源项目更新版本号打包每次都很烦躁

初始的时候每个项目都是 v1.0.0,但是随着迭代速度的不同,就会变得参差不齐。

假如依赖的一个组件库或工具库发生大版本迭代,api 或配置参数发生出入,就会导致引用该版本的项目产生一连窜错误。

随着项目数量的增多,依赖更新不及时的情况很常见,导致查漏补缺的时间成本大大提高。

该说说 Monorepo 了

相比于上文提到的 Multirepo,另一种风格叫做 Monorepo。Monorepo 其实不是一个新概念,它在软件工程领域已有十多年历史。它把每个项目放到不同仓库中,每个项目对应一个单独仓库分散管理。

对于 Monorepo 而言,只是把多个项目根据预设场景组织到一起,它的粒度还是保持原有的划分。对于团队某个成员而言,他的关注点还是在其中一个项目中。

用下图可以直观的看一下大致区别:

Monorepo 的项目结构一般根据以下目录划分:

project
├─ util
│  ├─ src
│  └─ package.json
├─ components
│  ├─ src
│  └─ package.json
├─ lib
│  ├─ src
│  └─ package.json
└─ ... # 另外多个仓库

Monorepo 带来的好处

简化仓库组织

随着业务需求的不断迭代,项目越来越复杂,就要在后期对项目进行拆分,使用 Monorepo 模式管理项目,可以有效简化项目结构组织。因为很多拆分都不是非必要原因的拆分,只是因为代码量多。

减少依赖

对于 npm install 这个烙印在前端骨子里的东西,带来了庞大的 node_modules ,但是许多项目所依赖的 npm 模块都是相同的。

使用 Monorepo 模式管理这些项目,可以把依赖提取出来,可以消除重复安装依赖所带来的影响。

跨域开发

如果同时对多个 npm 模块进行迭代,在调试的时候就需要不断的执行 npm link,使用 Monorepo 模式管理,就可以直接在本地进行联调,有效提高开发效率。

便于管理

对于一些大型项目或开源项目,多个仓库意味着要在多个地方处理 Issue 和 PR,所以更倾向于在一个仓库对其进行统一管理。

落地

如果还没接触过 Monorepo 管理模式的项目,会产生一个疑惑:

我是直接把多个项目直接合并成上述项目结构就完事了吗?

当然不是。

在实际场景去落地 Monorepo 模式,是需要有一套完整的工程体系支撑的,不是仅修改项目结构,将代码放到一起就完事,还需要考虑项目间的依赖分析、依赖安装、依赖卸载、构建流程、测试流程、打包流程、部署流程、发布流程等诸多工程环节,同时还要考虑项目规模到达一定程度后的性能问题,比如其中某流程的执行时间,在实现全面工程化的同时也需要兼顾性能问题。

社区为我们提供了一些较为成熟的方案,让我们可以在此基础上来构建一套较完善的,适合我们的 Monorepo 工程化工具。

比如其中的 lerna ,封装了依赖安装(卸载)、脚本批量执行等基本功能,但是它无法提供一套完整的构建、测试、打包、部署和发布功能的工具链,整体功能较弱,往往我们需要在其的基础上去封装,来提供更全面的工程支持能力。

总结

从 Multirepo 的角度来看,每个团队拥有自己的仓库,可用自己擅长的工具与工作流程。技术的多元化能促使各个团队尽可能地提升自己的效率,当然 Multirepo 的代价在于增加很多沟通成本。若在其中一个项目发现 Bug ,就必须修复后再次发布到 npm 并再次安装,然后再回到原来的项目中继续工作。

在不同仓库间,不仅需处理不同代码与工具,甚至是不同工作流程,甚至在无权限的情况下只能低声下气求人。

从 Monorepo 的角度来看,让不同团队走自己的路并不见得能提高生产力。虽然有些团队可能会找到自己最佳的工作方式,但他们的收益也会被其他团队不好的工作方式抵消。

相反,严格统一的管理更能提升开发效率,团队每个成员都可修改任何东西。

即,将鸡蛋都放到了同一个篮子里,因此也可以更好的去照顾鸡蛋。

若最终选择 Monorepo ,那主要的挑战自然是随着项目发展,其规模会变得很庞大,因此需使用很多工具应对这些挑战。

参考资料:从零到一落地前端工程化

Released under the MIT License.