monorepo是什么?为什么选用 monorepo?

455 阅读8分钟

上一节 # 专栏导论说明

下一节 # # 搭建monorepo的前置知识(webpack5+pnpm+babel,五万字长文,做好心里准备)

一、monorepo是什么?

monorepo是指将多个项目源码管理在单一仓库中一种开发模式("mono" 来源于希腊语 μόνος 意味单个的,"repo",是指repository 的缩写)。老祖宗可是耳提面命的告诫我们,不要把鸡蛋放到一个篮子里,你们怎么就是不听呢,事实证明,这样搞确实对于大型项目有着很大的利好,我们在网上可以看到Google、faceBook等一线互联网龙头,也可以发现包括vue、babel等社区开源库都用monorepo架构来管理他们的源码,babel源码示例:

CleanShot 2023-11-21 at 18.50.23.png 好,经过上面的图文并貌,我们只是浅显的理解了原来monorepo并不是一种新技术,而是将多个仓库改为一个仓库,其实这只是表象,我们可以预见到,如果只是单纯将项目源码排列组合到单一库中,那我们是不是面临着以下几个痛点:

(1)各个项目的包依赖冲突,我们知道npm、yarn会将依赖拍平放到node_modules中,这个时候这么多项目如何科学的管理依赖呢?

(2)各种开发时的配置文件(TS、Babel、webpack/vite、eslint等)冗余繁杂,每个工程可能不是同一个团队开发,如果单纯的全放到一个仓库里,那我们得维护多少重复的配置啊?

(3)CI/CD没法处理啊,这么多项目,得单独写多少脚本命令来区分执行呢?

...

好,如果有以上思考,那么恭喜你,你马上就能通过以下的学习来深入了解monorepo架构的好处,并且能够清晰的认识到,大佬们是如何解决上面这些问题的。我们继续探索吧。

二、为什么选用 monorepo? 既然谈到为什么选择monorepo模式来管理源码,我们先来说一下之前mutirepo架构带来的问题

(1)代码重复问题

我们在日常开发中肯定会有一套自己项目封装的工具utils函数,如果使用mutirepo模式,各个项目分散在不同的仓库中,我们偷懒的做法从A项目复制一段工具函数代码到B项目中,这种肯定会造成每个项目有大量冗余代码,更好一点的做法是我们将工具类单独抽一个npm包,发布到私服或者npm官网,这样虽然解决了代码冗余问题,但是每次更改utils都要重新走一遍发包流程,非常的麻烦。不同的仓库会被人为的割裂开来,复用成本高,开发时的体验很差

(2)依赖包管理版本问题

上面我们知道,单独抽的每一个依赖包都要独立发包控制版本,这个时候就会有一个问题,例如

CleanShot 2023-11-21 at 19.27.30@2x.png

通过图例中我们可以发现,对于每个项目依赖的版本控制心智负担极大,而且根本不敢随意升级包版本依赖,因为有可能子项目A某次大版本更新后会完全更改某个组件/方法的使用方式,这个时候出现问题后我们就需要把注意力放到依赖包的版本管理上,非常的麻烦。

(3)构建工具混乱,后期维护难度大

子项目A的开发团队熟悉webpack,那显然他们的构建工具会是webpack,子项目B的开发团队比较紧跟潮流,那可能就会选择vite,子项目C的团队可能平均年龄比较大,钟情于grunt、gulp,更有甚者,老项目D用webpack3版本,项目F又用的webpack5,我滴个龟龟,想想都头大,头发都得抓掉光了,这虽然有些夸张,但是也是实际情况的一种反应。这还只是开发中,如果上线领导又想全部搞成CI/CD来发布,那脚本写的头更大了

monorepo能解决上面几点让人头疼的问题嘛,答案是能,不但能而且非常的能。monorepo具有以下优点:

  • 可见性(Visibility):每个人都可以看到其他人的代码,这样可以带来更好的协作和跨团队贡献——不同团队的开发人员都可以修复代码中的 bug,而你甚至都不知道这个 bug 的存在。
  • 更简单的依赖关系管理(Simpler dependency management):共享依赖关系很简单,因为所有模块都托管在同一个存储库中,因此都不需要包管理器。
  • 唯一依赖源(Single source of truth):每个依赖只有一个版本,意味着没有版本冲突,没有依赖地狱。
  • 一致性(Consistency):当你把所有代码库放在一个地方时,执行代码质量标准和统一的风格会更容易。
  • 共享时间线(Shared timeline):API 或共享库的变更会立即被暴露出来,迫使不同团队提前沟通合作,每个人都得努力跟上变化。
  • 原子提交(Atomic commits):原子提交使大规模重构更容易,开发人员可以在一次提交中更新多个包或项目。
  • 隐式 CI(Implicit CI):因为所有代码已经统一维护在一个地方,因此可以保证持续集成。
  • 统一的 CI/CD(Unified CI/CD):可以为代码库中的每个项目使用相同的 CI/CD部署流程。
  • 统一的构建流程(Unified build process):代码库中的每个应用程序可以共享一致的构建流程。

monorepo这么多优点是不是没缺点呢,世界上没有完美的技术,我们来对比一下:

场景multirepomonorepo
代码管理❌ 包管理权限按照各自owner划分,当出现问题时,需要到依赖包中进行判断并解决
✅ 对需要代码隔离的情况友好,开发者只关注自己核心管理模块本身
✅ 各个团队可以控制代码权限,也几乎不会有项目太大的问题。
✅ 所有代码都只一个仓库中,相同依赖无需多分磁盘内存。
代码一致性❌ 各自项目eslint配置文件需要拉齐,难以做到代码风格统一✅ 所有代码放在一个库时,代码风格统一
唯一来源❌ 子包引用的相同依赖的不同版本的包,容易出现版本冲突✅ 每个依赖项的一个版本意味着没有版本冲突,也没有幽灵依赖。
开发体验✅ 仓库体积小,模块划分清晰。
❌ 多仓库来回切换(编辑器及命令行),项目一多真的得晕。如果仓库之间存在依赖,还得各种 npm link。
✅ 只需在一个仓库中开发,编码会相当方便。
✅ 代码复用高,方便进行代码重构。
❌ 项目如果变的很庞大,那么 git clone、安装依赖、构建都会是一件耗时的事情。
工程配置❌ 各个团队可能各自有一套标准,新建一个仓库又得重新配置一遍工程及 CI / CD 等内容。✅ 工程统一标准化配置
依赖管理❌ 依赖重复安装,多个依赖可能在多个仓库中存在不同的版本,npm link 时不同项目的依赖可能会存在冲突问题。✅ 共同依赖可以提取至 root,版本控制更加容易,依赖管理会变的方便。
依赖管理❌ 依赖重复安装,多个依赖可能在多个仓库中存在不同的版本,npm link 时不同项目的依赖可能会存在冲突问题。✅ 共同依赖可以提取至 root,版本控制更加容易,依赖管理会变的方便。相同依赖无需多分磁盘内存。
部署❌ multi repo 的话,如果各个包之间不存在依赖关系倒没事,一旦存在依赖关系的话,开发者就需要在不同的仓库按照依赖先后顺序去修改版本及进行部署。❌ 而对于 mono repo 来说,有工具链支持的话,部署会很方便,但是没有工具链的话,存在的问题一样蛋疼。(社区推荐pnpm、lerna)
持续集成❌ 每个repo需要定制统一的构建部署过程,然后再各自执行✅ 可以为 repo 中的每个项目使用相同的CI/CD部署过程。
✅ 同时未来可以实现更自动化的部署方式,一次命令完成所有的部署.

总体对比以后我们发现,当业务发展到一定规模时,monorepo 的升级相比 multirepo 来说,是利远大于弊的。 monorepo架构这么好,我们如何搭建使用呢,且听下回分解。

参考大佬文章列表:

5 分钟搞懂 Monorepo:

为什么越来越多的项目选择 Monorepo?:

Monorepo,大型前端项目管理模式实践: