有时候monorepo并不比mutirepo好用
前言
我们的团队在4年前开始引入 monorepo
单仓,当时主要是为了解决需求迭代过快,项目数量非常多,为了在依赖之下便于调试、快速搭建新的小项目,用了几年之后,
有一些心得体会,今天想粗浅的探讨一下这个话题。
什么是monorepo?什么是mutirepo?
monorepo这个概念已经出来很久了,但是对于一下小团队其实是不怎么用的,包括有些类型的团队也不适用,比如外包团队,外包团队一般只负责一个项目,
没有必要用monorepo,所以还是先说说什么是 monorepo
什么是 mutirepo
。
什么是mutirepo?
意为 多仓库
,就是过去的项目形式,每一个代码仓库都只有一个项目,就拿前端项目来说,比如你的 “oa系统” 代码,
git clone https://github.com/xxxx/oa.git
clone之后,里面只有一个项目,他的目录结构是这样的
oa/
|-- src/
|-- ...(其他目录)
|-- package.json
执行npm i之后,就可以npm run start等命令来启动项目。
而你还有一个系统 “crm系统” 又需要git clone
git clone https://github.com/xxxx/crm.git
得到一个目录结构
crm/
|-- src/
|-- ...(其他目录)
|-- package.json
再次执行安装依赖和启动项目,这就是多仓,你的所有项目都是独立的仓库。
什么是monorepo?
相对于 mutirepo
而言,与之对应的就是 monorepo
,意为 单仓库
,就是把多个项目放在一个仓库里,上面的例子中,他们2个项目都在一个仓库中,
git clone https://github.com/xxxx/system.git
而在这个项目中,有一个目录,目录中存在两个子项目,oa和crm
system/
|-- packages/
| |-- oa/
| |-- src/
| |-- ...(其他目录)
| |-- package.json
| |-- crm/
| |-- src/
| |-- ...(其他目录)
| |-- package.json
|-- ...(其他目录)
|-- package.json
只需要在system目录中执行一次npm i之后,他们可以各自执行单独启动,但是代码是在一起的。
我用过的几个monorepo
这几年我们的项目很多,用过几个monorepo,分别是 lerna
pnpm
turbo
,各有差异,但总体来说,殊途同归,有差异,但是都能满足我们的使用需求,
相对来说,lerna配置复杂点,但是对于发包的sdk类型项目来说,lerna自带版本抬升的功能,用起来比pnpm和turbo省事儿一点,而在turbo、pnpm中需要借助
changeset,还需要编写一些sh、js脚本来实现一些自动化才能达到lerna的自动管理包版本功能。
lerna
这个是出得比较早的一个单仓工具,他的cli提供了一套完整的命令来帮助mono的使用,相对来说比较复杂和有一些学习成本,其在git上已经获得了35k的start, github , lerna本身并不提供包管理功能,所以需要配合npm/yarn/pnpm使用。如果是sdk类型的单仓,推荐使用lerna, 自带的发包版本管理工具要省事儿不少,用changeset的话,还需要自己写一些脚本分析各个项目的依赖关系,然后做最小化包发布。
pnpm
用过pnpm的人知道,pnpm本身就是一个包管理工具,它的monorepo功能使用也很简单,配置比较少,借助它的包管理硬链接,软链接,可以做到依赖共享, 安装依赖速度也加快,个人比较倾向于实用它,和turbo一样,对于版本发布、版本自动抬升要依赖changeset等工具,pnpm在github 30k star, github
硬链接: monorepo中,同一个依赖多个版本依赖,都会出现在node_modules/.pnpm中,但是它们都是硬链接,真实的文件在系统缓存中(pnpm版本不一样位置不一样)
软链接: 所有子项目的node_modules中的依赖会软链接到node_modules/.pnpm中
turbo
这个单仓我们最终没有使用起来,项目被毙掉了,导致这个技术底座也废弃了,他和lerna一样没不自带包管理,依然需要使用npm/yarn/pnpm等,同时它也不支持 版本管理功能,需要整合changeset、搭配编写一些脚本。turbo支持任务流程定义,在github上27k star github , 其已经被vercel收购,个人觉得它也是可以选择的,会有好的一个发展和支持。
monorepo帮我们解决了什么
monorepo
相较于传统的 mutirepo
,主要解决了以下问题:
- 方便共享代码:在单仓中,不同项目或模块之间共享代码非常方便。开发人员可以直接引用仓库内其他模块的代码,无需通过复杂的依赖管理或发布流程,提高了代码复用的效率,减少了重复开发。
- 统一版本管理:所有代码处于同一个仓库,便于进行统一的版本控制。开发人员可以更轻松地跟踪代码的变更历史,了解整个项目的演进过程。在需要回滚或查看特定版本的代码时,操作更加便捷,能够快速定位到问题所在的版本。
- 简化依赖关系:对于多个相互关联的项目,单仓可以将它们的依赖关系集中管理。避免了不同项目之间因依赖版本不一致而导致的冲突问题,使得依赖管理更加清晰和可控。
- 高效构建与部署:单仓可以实现整体的构建和部署流程优化。可以一次性对所有相关项目进行构建、测试和部署,减少了多个仓库之间的协调成本,提高了持续集成和持续交付(CI/CD)的效率,有助于实现更快速、更稳定的软件发布。
- 增强代码可见性:所有团队成员都可以方便地访问和查看整个项目的代码库,了解其他模块的功能和实现细节。这有助于提高团队成员之间的代码共享和知识传递,促进协作开发,减少因信息不透明而导致的开发冲突和重复工作。
- 便于跨团队协作:对于大型项目,通常会有多个团队负责不同的模块或功能。单仓为跨团队协作提供了一个统一的平台,各个团队可以在同一个代码库中进行开发、调试和集成,便于协调工作、解决问题,提高整体的协作效率。
不好意思,上面都是我抄的,现在我来说人话 :
- 集中管理,所有代码都在一个仓库,不用繁琐的clone多个项目,方便查看、修改、调试
- 快速开发,依赖都在一起,你直接改,改完马上就编译生效,不像mutirepo一样,发版(发包)、安装依赖这样一套流程
- 依赖管理更好,依赖提升,依赖共享,安装依赖速度更快
monorepo给我们带来了什么问题
但是单仓它也不是100%的优势,它也有一些问题,所以用不用单仓还是要根据自身的团队状况来选择。
幻影依赖
如果你的项目一开始希望大力使用单仓的依赖提升特性,来增加安装速度、统一版本,就会经常出现在子项目不安装依赖,放到根目录package.json中安装,时间一长 某个子项目需要升级某个依赖,就容易出现幻影依赖,影响其他项目了。
解决思路:在所有子项目中严格指定依赖的版本,这可能会牺牲安装依赖的速度(同依赖不同版本都被安装)。
仓库体积
单仓随着使用时间,内部的项目会越来越多,安装依赖也越来越大,仓库越来越大,各个项目之间对顶层的配置做了修改,会影响其他项目,很容易就碰到某两个项目水火不容 的情况。我们就遇到过拆分项目到另一个单仓中的情况。
解决思路:这里其实需要我们多思考,什么时候用单仓,单仓的临界值是什么?我的答案是一个类型,或者相互依赖的项目放到一个单仓中,2个项目是不是一个类型这个概念比较泛化, 要根据实际的项目情况、功能情况来划分,不能一概而论。
构建时间变长
构建时间也可能变长,比如说要构建A项目,但是根目录的package.json中有很多A项目不用的依赖,构建(安装依赖)时间就会变长。
解决思路:缩减仓库体积
权限问题
使用单仓后,无疑你的代码就没有什么界限了,但凡是有仓库权限的人就能拿到所有代码,无法针对某一个项目做权限控制,不管是组内协作还是对外协作都是问题,因此单仓在闭源代码、 权限控制上存在的问题是比较棘手的。
解决思路:结合上面仓库体积,好好想想怎么拆分仓库。另外还有一条值得探索的思路,使用分支(gitlab)来授权,完整项目所在的分支不开放,开放一些只有部分代码的分支。
协作问题
单仓中的项目很多,不同的人在里面改各自的项目,分支合并代码冲突、依赖版本冲突等问题就比较常见了。
解决思路:没啥好办法解决,主要还是多控制单仓的体积,不是什么项目都放进来好。
总结
最后,一句话总结:什么项目都放到一个仓库中的monorepo并不会给你带来好的体验,适合自己的才是最好的。