摘要:引言本周精读的文章是。精读总的来说,虽然拆分子仓库拆分子包是进行项目隔离的天然方案,但当仓库内容出现关联时,没有任何一种调试方式比源码放在一起更高效。前端精读帮你筛选靠谱的内容。
1. 引言
本周精读的文章是 The many Benefits of Using a Monorepo。
现在介绍 Monorepo 的文章很多,可以分为如下几类:直接介绍 Lerna API 的;介绍如何从独立仓库迁移到 Lerna 的;通过举例子说明 Monorepo 重要性的。
本文属于第三种,从 Android 与 IOS 的开发故事说明了 Monorepo 的重要性。
笔者之所以选择这篇文章,不是因为其故事写的好,而是认可这种具有普适性的解决思路。毕竟 Lerna 作为 Monorepo 的实现之一也并不尽善尽美,而不同场景对 Monorepo 依赖的原因、功能也有所不同,所以希望借这篇文章,从理论上解释清楚为什么会产生 Monorepo,以及 Monorepo 可以解决哪些问题,这样在工作遇到问题时,才能想清楚自己要的是什么。
2. 概述作者的一个项目是 PDF 服务,简称 PSPDFKit,需要同时兼顾 Android 与 IOS 平台,项目的发展经历了如下几个阶段。
初始阶段在 2011 到 2013 年间,PSPDFKit 仅支持 IOS 平台,但最终项目需要支持 Android,因此开了一个新仓库放置 Android 代码。Android 仓库的代码不仅在 UI 上不同,同时解析 PDF 文档的核心代码也不同,这是因为 IOS 平台上使用内置 PDF 渲染引擎同时做了一些业务拓展,但使用的 OC 代码无法在 Android 使用。
最终新建了两个仓库 PSPDFKit-Android 与 Core 。
仓库 Core 中代码依赖 Android 平台 JNI 的支持,所以并不能实现 Core 一处修改,两处都生效的愿望,而我们又希望两边功能始终兼容,且减少分支过多带来的潜在的冲突,因此花了很久才意识到应该将这两个仓库合并起来。
考虑使用 Monorepo由于 Android 的整套流程自己控制的,因此总是可以快速修复用户提出的 BUG,然而 IOS 提供的 CGPDF 总会遇上各种问题。所以在 2014 年,我们开启了一个庞大的项目,重写 IOS 的 Core 库。有三中方式可供选择:
在 IOS 代码中引用 PSPDFKit-Android。
将 PSPDFKit-Android 提取到 Core 仓库中并分别维护。
将 IOS 与 Android 代码合并到一个仓库中。
经过讨论,最终作者的团队选择了第三种方案,因此目录结构类似如下:
- ios-platform - android-platform - core特例
Web 与后台服务代码一直是一个特例,我们认为这些内容相对独立,所以没有将其代码放置到 Monorepo 中。
直到一年后,开始探索 WebAssembly 时,PSPDFKit-web 模块就出现了,因为可以利用 WebAssembly 将 Core 的代码编译并在 Web 平台使用,因此 Core 仓库与 Web 仓库的关系变得非常紧密,最终,我们将 Web、Server 也都迁移到 Monorepo 中了。
问题Monorepo 瑕不掩瑜,但作者还是列举了一些缺陷。
由于源码在一起,仓库变更非常常见,存储空间也变得很大,甚至几 GB,CI 测试运行时间也会变长。即便如此,团队中任何人都不想回到 git submodules 多仓库的方式。
3. 精读总的来说,虽然拆分子仓库、拆分子 NPM 包(For web)是进行项目隔离的天然方案,但当仓库内容出现关联时,没有任何一种调试方式比源码放在一起更高效。
工程化的最终目的是让业务开发可以 100% 聚焦在业务逻辑上,那么这不仅仅是脚手架、框架需要从自动化、设计上解决的问题,这涉及到仓库管理的设计。
一个理想的开发环境可以抽象成这样:
“只关心业务代码,可以直接跨业务复用而不关心复用方式,调试时所有代码都在源码中。”
在前端开发环境中,多 Git Repo,多 Npm 则是这个理想的阻力,它们导致复用要关心版本号,调试需要 Npm Link。
另外对于多仓库的缺点,文中还有一些没有提到的因素,这里一并列举出来:
管理、调试困难
多个 git 仓库管理起来天然是麻烦的。对于功能类似的模块,如果拆成了多个仓库,无论对于多人协作还是独立开发,都需要打开多个仓库页面。
虽然 vscode 通过 Workspaces 解决多仓库管理的问题,但在多人协作的场景下,无法保证每个人的环境配置一致。
对于共用的包通过 Npm 安装,如果不能接受调试编译后的代码,或每次 npm link 一下,就没有办法调试依赖的子包。
分支管理混乱
假如一个仓库提供给 A、B 两个项目用,而 B 项目优先开发了功能 b,无法与 A 项目兼容,此时就要在这个仓库开一个 feature/b 的分支支持这个功能,并且在未来合并到主干同步到项目 A。
一旦需要开分支的组件变多了,且之间出来依赖关联,分支管理复杂度就会呈指数上升。
依赖关系复杂
独立仓库间组件版本号的维护需要手动操作,因为源代码不在一起,所以没有办法整体分析依赖,自动化管理版本号的依赖。
三方依赖版本可能不一致
一个独立的包拥有一套独立的开发环境,难以保证子模块的版本和主项目完全一直,就存在运行结果不一致的风险。
占用总空间大
正常情况下,一个公司的业务项目只有一个主干,多 git repo 的方式浪费了大量存储空间重复安装比如 React 等大型模块,时间久了可能会占用几十 GB 的额外空间,对于没有外接硬盘的同学来说,定期清理不用的项目下 node_modules 也是一件麻烦事。
不利于团队协作
一个大项目可能会用到数百个二方包,不同二方包的维护频率不同,权限不同,仓库位置也不同,主仓库对它们的依赖方式也不同。
一旦其中一个包进行了非正常改动,就会影响到整个项目,而我们精力有限,只盯着主仓库,往往会栽在不起眼的二方包发布上。
所以对于一个非常复杂,又具有技术挑战的大型系统在协作人员多的情况下出现问题的概率非常大,需要通过 Review 制度避免错误的发生,那么将所有相关的源码聚合在一个仓库下,是更好管理的。
理想 monorepo 的设计参考 Lerna 的规范,以 packages 作为子模块根文件夹,笔者设计一个理想的 monorepo 结构:
. ├── packages │ ├─ module-a │ │ ├─ src # 模块 a 的源码 │ │ └─ package.json # 自动生成的,仅模块 a 的依赖 │ └─ module-b │ ├─ src # 模块 b 的源码 │ └─ package.json # 自动生成的,仅模块 b 的依赖 ├── tsconfig.json # 配置文件,对整个项目生效 ├── .eslintrc # 配置文件,对整个项目生效 ├── node_modules # 整个项目只有一个外层 node_modules └── package.json # 包含整个项目所有依赖
所有全局配置文件只有一个,这样不会导致 IDE 遇到子文件夹中的配置文件,导致全局配置失效或异常。node_modules 也只有一个,既保证了项目依赖的一致性,又避免了依赖被重复安装,节省空间的同时还提高了安装速度。
兄弟模块之间通过模块 package.json 定义的 name 相互引用,保证模块之间的独立性,但又不需要真正发布或安装这个模块,通过 tsconfig.json 的 paths 与 webpack 的 alias 共同实现虚拟模块路径的效果。
再结合 Lerna 根据联动发布功能,使每个子模块都可以独立发布。
4. 总结Lerna 是业界知名度最高的 Monorepo 管理工具,功能完整。但由于通用性要求非常高,需要支持任意项目间 Monorepo 的组合,因此在 packages 文件夹下的配置文件还是与独立仓库保持一致,这样在 TS 环境下会造成配置截断的问题。同时包之间的引用也通过更通用的 symlink 完成,这导致了还是要在子模块目录存在 node_modules 文件夹,而且效果依赖项目初始化命令。
如果加一些限定条件,比如基于 Webpack + Typescript 环境的 Monorepo,可以换一套思路,利用这些工具自身运行时功能,减少更多模版代码或配置文件,进一步提升 Monorepo 的效果。
对于别名映射,对 symlink 与 alias 进行对比:
symlink: 更通用,适合任何构建器。但需要初始化,且在每个关联模块下新增 node_modules 文件夹。
alias: 限定构建器。但不需要初始化,不新增文件夹,甚至可以运行时动态修改别名配置。
可见如果限定了构建器,别名映射可以做得更轻量,且无需初始化。
今天的问题是,你的项目需要使用 Monorepo 吗?你对 Monorepo 有其他要求吗?
讨论地址是:精读《Monorepo 的优势》 · Issue #151 · dt-fe/weekly
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/109444.html
摘要:目前最常见的解决方案是和的特性。具体的使用方法移步官网而使用作为包管理器的同学,可以在中以字段声明,就会以的方式管理。这样的话,无论你的包管理器是还是,都能发挥的优势要是包管理是,就会把依赖安装交给处理。 最近我接手了一个项目,代码量比较大、有点复杂。仓库 clone 下来代码有 50+ MB,npm install 安装完体积飚到了近 2GB …… 熟悉了一下,这个项目比较复杂,采用...
我们先说下 Yarn workspace 首先Yarn workspace 是 Yarn 提供的 monorepo 下,管理依赖的机制。这就说主要对代码仓库下,多个 package 的依赖,进行管理:将共同的依赖,做 hosting(提升)。前述这样就可以有效的防止 package 中的包重复安装。 workspace 机制,会在根目录下,统一安装依赖到 node_module,并生成...
摘要:需要说明是的,这里说的专家不再关心细节,不代表成为专家后学不会细节,也不代表专家不了解细节。本文将从三个点去解释,为什么专家看上去越来越原理技术细节。试想一个不能理解业务要做什么的人,即便懂得再多技术细节,对业务也是没有价值的。1. 引言 本周的精读是有感而发。 笔者接触前端已有八年,观察了不少前端大牛的发展路径,发现成功的人都具有相似的经历: 初期技术热情极大 -> 大量标志性技术项目 -...
摘要:最近发现公司一个项目的目录组织挺奇怪的,所有的子项目都放在了目录里,还有这种骚操作特意查了下资料,发现是一种比较流行的项目管理模式。 最近发现公司一个项目的目录组织挺奇怪的,所有的子项目都放在了packages目录里,还有这种骚操作?特意查了下资料,发现是一种比较流行的monorepo项目管理模式。近几年比较火的React,Vue,Babel都是用的这种模式: showImg(http...
摘要:指向一个文件,这个文件描述了所有的字段以及约束。其中一个项目为一个子项,如为一个项目,在创建时自动生成。整个也有此字段,默认生效于所有。默认项目,当使用一些命令没有指定项目名称时,默认指向的项目。 ... 在Angular CLI 6+的版本后,原先的angular-cli.json就被换成了angular.json,而其中里面的字段变化挺大了,如果不了解基本的组成,或者直接把老版本的...
阅读 701·2021-11-11 16:54
阅读 3029·2021-09-26 09:55
阅读 1946·2021-09-07 10:20
阅读 1168·2019-08-30 10:58
阅读 1008·2019-08-28 18:04
阅读 678·2019-08-26 13:57
阅读 3540·2019-08-26 13:45
阅读 1121·2019-08-26 11:42