当前位置: 首页 > 科技观察

为什么现代前端工程越来越离不开Monorepo?

时间:2023-03-13 19:32:41 科技观察

随着前端工程越来越复杂,一些业务或工具库通常会涉及到很多仓库。随着时间的推移,多仓库开发的弊端越来越显露出来。出现了一种新的项目管理方法——Monorepo。本文主要通过Monorepo的概念,MultiRepo的缺点,Monorepo的好处,以及Monorepo的实现来了解和学习Monorepo。文末会有思考题,欢迎大家积极讨论。什么是单体仓库?Monorepo并不是一个新概念,它在软件工程领域已经存在了十多年。概念很好理解,就是把多个项目放在一个仓库里。与之相反的是传统的MultiRepo模式,即每个项目对应一个单独的仓库,进行分散管理。现代前端工程越来越离不开Monorepo。无论是业务代码还是工具库,越来越多的项目采用Monorepo进行开发。谷歌更喜欢将所有代码放在一个Monorepo项目下。Vue3、Yarn、Npm7等知名开源项目的源代码也由Monorepo管理。一般Monorepo的目录如下,多个子项目存放在packages中,每个子项目都有自己的package.json:├──packages|├──pkg1||├──package.json|├──pkg2||├──package.json├──package.json那么Monorepo到底有什么魔力,让大家如此受人尊敬,被如此广泛的应用呢?MultiRepo的痛点想要了解Monorepo的优势,首先要搞清楚以前的开发方式是什么。多么痛点。在之前MultiRepo的传统方式中,每个项目对应一个单独的代码仓库。之前我也是这样开发的,真正感受到了这种方式带来的诸多弊端。现在就一一分享给大家。1.代码复用在维护多个项目时,一些逻辑很可能会被多次使用,比如一些基础组件,实用功能,或者一些配置。你可能会想:直接复制代码怎么样?过来,省事!但是有个问题就是如果这些代码有bug或者需要做一些调整,就得修改多份,维护成本越来越高。那么如何解决这个问题呢?更好的方法是提取公共逻辑代码并将其发布为npm包。一旦需要更改,只需更改一个代码,然后发布即可。但这真的是完美的解决方案吗?让我举一个例子。比如你引入了1.1.0版本的A包,某个实用函数有问题,你需要做这些事情:修改某个实用函数的代码,发布1.1.1版本的新packageproject安装新版A,可能只是改了一行代码,需要走那么多流程。但是在开发阶段很难保证没有bug。如果有一个按钮需要改变样式,就需要把上面的过程再走一遍……静下心来想一想,这些重复的步骤真的很有必要么?我们只想重用代码。为什么我们每次修改代码都这么复杂?上面的问题其实是MultiRepo中的通病。由于不同仓库工作区的分离,代码复用成本非常高,开发调试过程繁琐,甚至在基础库频繁变更的情况下,让人抓狂,体验差.2、版本管理在MultiRepo的开发模式中,依赖包的版本管理有时是一个特别玄学的问题。比如一开始有一个toolkit版本是v1.0.0,很多项目都依赖这个toolkit,但是在某个时候这个toolkit发布了一个breakchange版本,和原来的API版本完全不兼容。事实上,有些项目并没有升级这个依赖,导致出现一些莫名其妙的错误。当项目过多时,很容易出现这种依赖更新不及时的情况。这是另一个痛点。3、项目基础设施因为在MultiRepo中,各个项目的工作流程是分离的,所以每个项目都需要分别配置开发环境、CI流程、部署发布流程等,甚至每个项目都有自己独立的一套Scaffolding工具。其实很容易发现,这些项目中很多基础设施的逻辑都是重复的。如果有10个项目,则需要维护10个基础设施流程。不说逻辑重复,各个项目之间还有构建、部署、发布规范。在统一的情况下,维护起来会比较麻烦。Monorepo的好处弄清了MultiRepo的痛点之后,相信大家大概能明白为什么要诞生Monorepo技术了。下面我们就来详细分析一下Monorepo给现代前端工程带来了哪些好处。首先是工作流程的一致性。由于所有的项目都放在一个仓库里,复用起来非常方便。如果依赖代码发生变化,使用这个依赖的项目会立即感知到。并且所有项目都使用最新的代码,所以不会出现其他项目版本没有及时更新的情况。二是降低项目基建成本。所有项目都在不切换开发环境的情况下重用一组标准工具和规范。如果接入新的项目,现有的基础设施流程也可以直接复用,比如CI流程、构建流程和Publish流程。这样一来,所有项目的基础设施维护只需要几个人,维护成本也大大降低。此外,团队合作也更容易。一方面,大家在一个仓库中开发,可以方便的共享和复用代码,方便项目源码的检索。另一方面,gitcommit的历史也支持以函数为单位的提交。以前,某个功能的提交,需要修改几个仓库,提交多次commit。现在只需要提交一次,简化了提交记录,方便了协作。Monorepo的实现如果你从来没有接触过Monorepo的开发,你可能会一头雾水:Monorepo的好处我都说了那么多,但我还是不知道怎么用!我只是把所有的代码都搬到一个仓库里够不够?当然不是。在实际场景中实现Monorepo,需要一个完整的工程体系来支撑,因为基于Monorepo的项目管理绝不仅仅是把代码拼在一起,还需要考虑项目间的依赖分析、依赖安装、构建过程、测试过程、CI和发布过程等诸多工程环节,还要考虑项目规模达到一定程度后的性能问题,比如项目构建/测试时间过长,需要增量构建/测试,关于-CI等需求执行,在实现综合工程能力的同时,还需要考虑性能问题。因此,从零开始定制一套完整的Monorepo工程工具是非常困难的。不过社区已经提供了一些成熟的方案,我们可以用来定制,或者直接使用一些上层的方案。其中,更底层的方案如lerna[1]将依赖安装、脚本批量执行等基本功能封装在Monorepo中,但没有一套构建、测试、部署的工具链。Monorepo整体功能比较弱,但是你需要在业务项目中使用,往往需要基于它封装顶层能力,提供综合工程能力的支持。当然也有一些集成Monorepo的解决方案,比如nx[2](官网写的真好,还有很多视频教程),rushstack[3],提供从初始化,开发,构建、测试到部署。一套比较完善的Monorepo基础设施,适合直接用于业务项目的开发。但由于这些顶层方案的内部流程和工具链已经非常完备,如果要基于这些方案进行定制,适配和维护成本太高,基本不可行。总结总而言之,Monorepo的开发模式就是将独立的项目变成一个统一的工程整体,解决MultiRepo下的各种痛点,提高研发效率和工程质量。最后,我还有一个问题。使用Monorepo解决了之前的痛点后,又出现了哪些新的问题?这些问题能解决吗?参考[1]lerna:https://lerna.js.org/[2]nx:https://nx.dev/latest/react/getting-started/getting-started[3]rushstack:https://rushstack.io/