Monorepo——探秘源码管理新姿势!
导语 | Monorepo是一个“单仓多包”的代码管理策略,由于众多大型厂商和开源项目在其上的实践,Monorepo受到了越来越多的关注,和其他已有的代码库管理方案相比,有着自身独特的优势。本文仅讨论Monorepo在前端开发场景中的应用及实践,里面提到的概念和示例都会有所局限,可依据实际情况自行扩展阅读其他资料。
“代码(code)” 是程序员用开发工具所支持的语言写出来的源文件,用于实现或支持所有依托于计算机的程序及应用,因此,如何管理代码是开发人员在项目进程中非常重要的一环。
而“仓库(repository)”则是存储项目中所有代码文件和更改信息的重要载体和方式,它会帮助开发人员以“版本控制”的形式管理整个项目的生命周期。这里的仓库通常指代Git仓库,当然对其他比如Mercurial/Subversion的代码仓库也同样适用。而如何去设计仓库的代码管理策略将会直接影响项目的开发流程和使用体验。
而在近段时间,团队依据项目需求规划开发多个基于Vue3的组件集合,其中包括UI组件、Map组件、以及Chart组件,并采用Vitepress作为文档服务。但是在项目构建的过程中发现,如果采用通常的形式将其拆成三个仓库进行开发会使得管理变得复杂,极度相似的环境需要搭建三套。同时,文档的管理也变成了一个问题,每个仓库内建立一套文档服务和独立文档为一个仓库都将增加管理负担。在寻找技术解决方案的过程中,发现了Monorepo这样的代码管理策略,并进行了实际开发实践。
本文将会通过在项目中的实践经验进行总结和分享Monorepo在仓库代码管理上的策略理念,以及其在前端上面的技术实现。
一、Monorepo策略探索
(一)概念探索
什么是Monorepo?
Monorepo可以理解为一种基于仓库的代码管理策略,它提出将多个代码工程“独立”的放在一个仓库里的管理模式,其中“独立”这个词非常重要,每个代码工程在逻辑上是可以独立运行开发以及维护管理的。Monorepo在实际场景中的运用可以非常宽泛,甚至有企业将它所有业务和不同方向语言的代码放在同一个仓库中管理,当然,这样的运用方式对企业的仓库底层能力要求相当高。因此,更多的Monorepo实践会根据业务和职能范围来进行组织。
谁在用Monorepo?
Monorepo这个概念的提出已经有很长的历史,但直到最近几年,随着技术的更新迭代,以及各项工具链的完善,逐渐开始成为一个热门的话题,很多大型的互联网公司都在采取这样的代码管理策略,比如Google、Facebook、Uber、MicroSoft等。也有很多著名的前端开源库选择用这种方式来构建和管理自己的代码,比如Vue、React、Vite、Babel、Element-plus等。
为何用Monorepo?
那么Monorepo解决了哪些项目代码管理上的问题,让这么多大型厂商和开源项目都纷纷尝试并投身其中?回答这个问题前,我们需要先引出另外两种较为普遍的管理策略:Single-repo Monolith和Multirepo,目前大部分项目所使用的都是这两种管理策略。接下来我们通过对比,来逐步探索对Monorepo的理解以及使用它的原因。
注意:后面所有的探索和实践都将基于前端开发的场景进行。
(二)对比探索
架构模式发现
首先,我们先从最上层的架构入手,来看三种策略的分别是如何去划分和管理一个相对复杂,或者说拥有多个功能和业务模块的项目的。这里引用来自Guide to Monorepos for Front-end Code文章里的一张图,通过这张图可以很清晰的展示出三种策略各自的管理理念:
文章链接:https://www.toptal.com/front-end/guide-to-monorepos
从图中我们来分析三种策略在架构模式上核心的不同点:
Monorepo:只有一个仓库,并且把项目拆分多个独立的代码工程进行管理,而代码工程之间可以通过相应的工具简单的进行代码共享。
Single-repo Monolith:同样也只有一个仓库,而它并不会独立的分割每个代码工程,而是让他们成为一体来进行开发管理,模块的拆分取决于代码工程的设计。
Multi-repo:则是通过建立多个仓库,每个仓库包含拆分好的代码工程,而仓库间的调用共享则是通过NPM或者其他代码引用的方式进行。
虽然这样可以简单的区别三种策略,而实际情况下,这三种策略其实是可以相互转换,相互包含的。一个Monorepo里可以包含多个以Single-repo Monolith形式组织的代码。Multi-repo中的每个repo都可以是一个Monorepo等等。所以,灵活的使用每种策略和组合策略才能更加高效的管理更为复杂多变的情形,这个在后文也会集中讨论Monorepo适合的场景。
仓库组织对比
除了从架构概念上的区分外,这里再构造一个简单前端场景:假设现在有两个可以逻辑上被分割的项目Project1和Project2以及他们共用的一个公共库lib。在这个假设的场景中,我们通过实际的代码目录组织和相互引用方式来更加直观的展示三种策略的不同:
在Single-repo Monolith这个策略下,Project和lib都会被组织在一个仓库当中,并会将两个Projects中代码进行杂糅,放在同一个代码工程当中(当然这个组织形式可以有很多种,具体根据实际场景以及架构师对模块的设计理念)。而lib代码会放在该工程目录下,两个Projects可以简单的通过路径去引用,也可以通过工具设置绝对地址alias来方便引入。最终整个项目会共同构建并部署。
// Repository - monolith
.
├── package.json
├── src/
│ ├── views/
| | ├── project1/
| | ├── project2/
│ ├── router/
| | ├── project1/
| | ├── project2/
| ├── ...
│ └── lib/
└── README.md
// 代码共享 package1/example.js
import {method} from '../../lib';
// script引入共享