前端工程Monorepo项目管理方式

1,571 阅读6分钟

Monorepo 与 Multirepo 的概念

L1VzZXJzL3h1amlheWluZy9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvRGluZ1RhbGtNYWMvMjczMDQ1Njg0OF92Mi9JbWFnZUZpbGVzLzE2NjA4OTAzODAzMzhfMUQ4RjIwQUItMDFCMi00MkY5LThDQUItMjZBQThEQjQxRTU1LnBuZw==.png “单仓”: 将多个功能独立的项目放到一个Git仓库中进行管理;实现更大程度的代码共享、更便捷的项目开发、管理、测试、发布等流程。
“多仓”:传统的方式Multirepo当中,每个项目都对应单独的一个代码仓库。

Multirepo的弊端

1.代码复用成本高 在维护多个项目的时候,有一些逻辑很有可能会被多次用到,比如一些基础的组件、工具函数,或者一些配置;通过copy❌,抽取公用逻辑作为npm包,重复步骤多。

2.版本管理 多个项目依赖于同一个工具包,会存在依赖更新不及时的情况,版本管理复杂造成莫名报错。

3.项目基建 由于在 MultiRepo 当中,各个项目的工作流是割裂的,因此每个项目需要单独配置开发环境、配置 CI 流程、配置部署发布流程等等,甚至每个项目都有自己单独的一套脚手架工具。这些项目里的很多基建的逻辑可能都是重复的,如果是 5 个项目,就需要维护 5 份基建的流程,而且各个项目间存在构建、部署和发布的规范不能统一的情况,这样维护起来就更加麻烦了。

Monorepo的优势与局限

优势

  1. 更简单的依赖关系管理:共享依赖关系很简单,因为所有模块都托管在同一个存储库中,因此都不需要包管理器。

  2. 唯一依赖源:每个依赖只有一个版本,意味着没有版本冲突,没有依赖地狱。

  3. 一致性:当你把所有代码库放在一个地方时,执行代码质量标准和统一的风格会更容易。

  4. 基建成本的降低:有项目复用一套标准的工具和规范,无需切换开发环境,如果有新的项目接入,也可以直接复用已有的基建流程,比如 CI 流程、构建和发布流程。这样只需要很少的人来维护所有项目的基建,维护成本也大大减低。

  5. 团队协作也更加容易:能够方便地共享和复用代码,方便检索项目源码,简化了 commit 记录,方便协作。

局限

  1. 缺乏每个项目的权限控制:开发者每次拉取代码都会拉取所有的代码,无法进行精准的权限控制。

  2. 版本信息杂糅不清晰:同样由于所有功能都在同一个项目中,就会导致所有功能的修改都在同一个Git下,不便于回溯和查找对应的历史记录。

  3. 大项目在Git上表现很差:代码增多之后,整个Git项目会变得越来越大,开发者拉取代码的时间也会变得更长。

  4. 构建时间更长:由于有很多个项目和功能模块,Monorepo 在构建时势必会花费更多的时间。而在这方面做的比较好的应该是 Bazel[1],它特别针对超大型的 Monorepo 项目进行了构建优化。

[1]Bazel:开源构建和测试工具。

Monorepo 的使用场景

Vue3、Yarn、Npm7、Babel、Vite、ElementPlus 等等知名开源项目的源码也是采用 Monorepo 的方式来进行管理的。

你应该使用单仓吗???

看情况而定,没有适合所有用例的答案;

总的来说,如果我们在开发过程中,需要跨组合作,或是希望有几个有互相引用关系的项目,能协同开发,但又希望保证每个项目的独立性,那就值得尝试 Monorepo。

但是如果没有良好的复用机制、完善的流程、规范和完备的单元测试则不建议使用,意义不大。

基于Lerna实现的 Monorepo 多包管理方式

Lerna 是一个管理工具,用于管理包含多个软件包(package)的 JavaScript 项目。 基于 Lerna 实现的 Monorepo 多包管理方式,能解决的问题(优点)如下:
扁平: 同一仓库(项目)下,统一管理维护多个 package
集中:在根目录的 node_modules/ 文件夹下维护所有 package 的三方依赖
简化:根据文件变动统一执行命令,按需发包,自动升级版本并回写仓库、打 tag
高效:有互相依赖的项目直接可直接关联,避免开发人员在多仓库之间切换 当然,Lerna 经过长时间的使用,一些问题也在生产环境中暴露出来,典型的如:
无效构建:每次发包前会对所有的 package 进行构建
无效依赖:每次发包都会安装所有 package 的依赖项
幽灵依赖:在依赖提升后更加明显(由于无法保证幽灵依赖(隐式依赖)的版本正确性,给程序运行带来了不可控的风险)
这里将问题罗列出来,不是说 Lerna 就应该被放弃,而是我们应当清楚技术方案的利与弊,并结合项目的实际情况做一些取舍,上述的缺点只是在构建中不那么优雅,但并不影响 Lerna 作为一种可落地 Monorepo 的方案。

pnpm与幽灵依赖

幽灵依赖”指: 可以在项目中使用本没有安装的依赖。
(app 依赖了 lib-a,lib-a 依赖了 lib-x,由于依赖提升,我们可以在 app 中直接引用 lib-x,这并不可靠,我们能否引用到 lib-x,以及引用到什么版本的 lib-x 完全取决于 lib-a 的开发者。)
出现这个现象的原因是yarn/npm在处理安装的npm包时,针对node_modules采用了拍平的方案。pnpm可以解决这个问题。推荐基于Pnpm  + rush实现Monorepo多包管理方式

pnpm(performance npm)。核心思想:将所有项目需要安装的npm包统一安装到磁盘的一个位置,之后通过硬连接的方式将其链接到项目的node_modules中;下图为pnpm的特点概括。

L1VzZXJzL3h1amlheWluZy9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvRGluZ1RhbGtNYWMvMjczMDQ1Njg0OF92Mi9JbWFnZUZpbGVzLzE2NjA4OTA0MDcyMjRfMzZGOTUxMkMtNjI5MC00MjJDLTlEN0UtNDhDMDdCRjAyMUVFLnBuZw==.png

L1VzZXJzL3h1amlheWluZy9MaWJyYXJ5L0FwcGxpY2F0aW9uIFN1cHBvcnQvRGluZ1RhbGtNYWMvMjczMDQ1Njg0OF92Mi9JbWFnZUZpbGVzLzE2NjA4OTExMDIxMzJfNTJEMkZEQjctRkU3OS00RTY0LTg1MzUtQjgxQzVGODIxODdBLnBuZw==.png

  1. pnpm 使用软链(符号链接)的方式将项目的直接依赖添加进模块文件夹的根目录
  2. 虚拟存储目录 .pnpm/ 以平铺的形式储存着所有的包,所以每个包都可以在这种命名模式的文件夹中被找到:.pnpm/@/node_modules/这个平铺的结构避免了 npm v2 创建的嵌套 node_modules 引起的长路径问题,但与 npm v3,4,5,6 或 yarn v1 创建的平铺的 node_modules 不同的是,它保留了包之间的相互隔离。

总结

你应该使用单仓吗???

看情况而定,没有适合所有用例的答案;

总的来说,如果我们在开发过程中,需要跨组合作,或是希望有几个有互相引用关系的项目,能协同开发,但又希望保证每个项目的独立性,那就值得尝试 Monorepo。

但是如果没有良好的复用机制、完善的流程、规范和完备的单元测试则不建议使用,意义不大。