前言
在前几个月的时候,我还不懂monorepo甚至没有听过monorepo是什么,我在实习的过程中,接触到了公司一个体量很大的项目,我发现项目中存在着许多package.json
,我感觉十分的不可思议。我一直认为一个项目只会有一个package.json文件,它通常位于项目的根目录之中,之后才知道这是monorepo
的用法。
在每个前端项目中,都有 package.json 文件,它是项目的配置文件,常见的配置有配置项目启动、打包命令,声明依赖包等。
monorepo是什么
Monorepo (Monolithic Repository的简称)是一种代码管理策略,指的是将多个项目的代码存储在同一个代码仓库中,而不是为每个项目创建单独的代码仓库。它常用于管理大型、多模块或多团队协作的项目。
特点
- 单一仓库:所有项目和共享代码都位于同一个版本控制仓库中。
- 代码共享:代码和依赖可以方便地在项目之间共享。
- 版本一致性:通过统一的代码管理,确保所有模块的版本同步更新。
- 工具支持:通常配合工具(如 Lerna、Nx)管理依赖和构建任务。
仓库的进化
单仓库(Single Repository)
特点:
- 单个项目:早期的开发模式通常是每个项目使用单独的仓库。
- 简单易用:对于小型项目或团队来说,单仓库管理简单,变更历史清晰。
- 迅速上手:新成员加入时只需克隆一个仓库即可开始工作。
限制:
- 项目规模限制:当项目变得复杂和庞大时,单仓库管理变得困难。
- 依赖管理:难以管理与其他项目之间的依赖关系。
- 代码重复:不同项目之间可能存在大量重复代码,但又很难共享代码。
多仓库(Multirepo)
特点:
- 模块化管理:将大项目拆分为多个小的、更专注的仓库,每个仓库对应一个子模块或服务。
- 独立开发:每个子模块或服务的开发可以在不同的仓库中独立进行。
- 依赖分离:通过明确的接口和API进行集成,模块之间可以独立发布和更新。
优势:
- 清晰边界:每个子模块有自己的生命周期、权限控制和开发流程。
- 独立部署:模块可以单独部署和扩展,提高灵活性。
- 版本控制:可以独立管理每个模块的版本。
限制:
- 依赖管理复杂:跨仓库的依赖管理需要额外工具和流程,如包管理器、构建工具等。
- 同步问题:多仓库之间的变更同步问题,尤其是在API变化和协议变更时,容易引发兼容性问题。
- 开发环境配置繁琐:每个仓库都有独立的配置和依赖,新成员需要设置多个仓库的开发环境。
Monorepo
特点:
- 单一仓库:将多个项目或子模块存放在同一个版本控制仓库中。
- 统一管理:所有代码共享统一的版本控制历史和管理策略。
- 代码共享:不同模块间可以直接引用和共享代码,无需发布和安装。
优势:
- 统一依赖管理:所有项目使用相同的依赖管理和构建工具,更容易保持一致性。
- 原子性提交:可以在一个提交中对多个模块进行原子性变更,避免版本不一致的问题。
- 简化协作:团队成员可以更方便地浏览和修改整个代码库,降低了沟通和协作成本。
- 提高复用:共享代码更为便捷,不再需要通过包管理器发布和安装。
限制:
- 规模和性能挑战:随着代码库增长,版本控制系统(如Git)的性能可能成为瓶颈,需要专门优化和维护。
- 权限控制:对不同模块的访问权限控制变得更加复杂,需要确保适当的安全措施。
- 工具支持:需要使用合适的工具和平台(如Bazel、Lerna、Nx、Rush)来管理构建、依赖和测试过程。
给大家分享一张直观的图,该图片来源于掘金上的一个博主,我觉得十分好:
对比
在Multirepo下
假如你有三个不同代码仓库,一个是首页,一个是播放页,还有一个是共用的工具库(utils)。因为我们想要共用工库utils,那么我们就需要把它当作SDK来使用,我们需要把utils发布到npm上,然后让首页和播放页都安装并且依赖utils。但是如果我们工具库utils有bug的话,由于首页和播放页都应用了这个工具库,所以都会被这个bug影响到。当你修复完这个utils的bug后,你需要这样做:
- 在工具库utils里提交一个修复bug的更改
- 执行发布任务,将utils的新版本发布到npm
- 在首页更新utils的版本
- 在播放页中也更新utils的版本
- 准备部署
如果有更多的仓库,并且彼此间依赖的关系更复杂,那么这个流程就会变得十分难处理。
在Monorepo下
上面我们有介绍过monorepo,一个仓库管理多个项目,那么首页、播放页、工具库我们将它们放在一个项目仓库中。现在的过程就是:
- 提交一个utils的更新
- 首页和播放页准备部署
用monorepo就代表我们不需要进行版本控制,因为首页和播放页不依赖于npm中的utils版本,而是依赖于monorepo中的播放器版本。
从上面的例子可以看出,monorepo的好处在于可以使共享代码变得简单。当依赖的代码变动时不需要繁琐的去更新。