项目中现有问题定义
多个子项目间改动同步
我正在开发的项目 A,依赖了已经线上发布的项目 B,但是随着项目 A 的不断开发,又需要不时修改项目 B 的代码(这些修改暂时不必发布线上),如何能够在修改项目 B 代码后及时将改动后在项目 A 中同步? 在项目 A 发布上线后,如何以一种优雅的方式解决项目 A,B 版本升级后的版本同步问题?
依赖版本不一致
最好的锁定版本策略是
- 对于第三方依赖,推荐去锁版本
- 对于我们自己的二方库,不推荐锁版本,理应使用同一个版本依赖。如果去锁版本,会不同子应用内有不同版本的依赖
目前使用 lock 文件的方式,会导致固定在一个版本。导致不同项目自己使用不同版本的业务组件库,这也就导致不同子应用中使用的业务组件版本不一致。
环境配置复杂,想要发一个包需要复杂的前置步骤
类似这种情况,需要找对应的人加权限和发包。以及新上手一个 repo 需要大量的时间去做前置配置,甚至可能跑不起来。
monorepo 方案的优势劣势
有哪些优势
代码重用将变得非常容易
:由于所有的项目代码都集中于一个代码仓库,我们将很容易抽离出各个项目共用的业务组件或工具,并通过 TypeScript,Lerna 或其他工具进行代码内引用;依赖管理将变得非常简单
:同理,由于项目之间的引用路径内化在同一个仓库之中,我们很容易追踪当某个项目的代码修改后,会影响到其他哪些项目。通过使用一些工具,我们将很容易地做到版本依赖管理和版本号自动升级;代码重构将变得非常便捷
:想想究竟是什么在阻止您进行代码重构,很多时候,原因来自于「不确定性」,您不确定对某个项目的修改是否对于其他项目而言是「致命的」,出于对未知的恐惧,您会倾向于不重构代码,这将导致整个项目代码的腐烂度会以惊人的速度增长。而在 monorepo 策略的指导下,您能够明确知道您的代码的影响范围,并且能够对被影响的项目可以进行统一的测试,这会鼓励您不断优化代码;它倡导了一种开放,透明,共享的组织文化
,这有利于开发者成长,代码质量的提升:在 monorepo 策略下,每个开发者都被鼓励去查看,修改他人的代码(只要有必要),同时,也会激起开发者维护代码,和编写单元测试的责任心(毕竟朋友来访之前,我们从不介意自己的房子究竟有多乱),这将会形成一种良性的技术氛围,从而保障整个组织的代码质量。
monorepo 方案的劣势
- 这意味着 A 部门的 a 项目若是不想被 B 部门的开发者看到就很难了。后续如果需要单独开一个项目的权限,将不太现实
- 建议把 monorepo 实践在项目的级别,如果是公司级别,会有很多意外的场景发生。
相关实践
复用 packages
:workspace,业务代码级别复用
经过一番思考,我们意识到一件事:能够放在同一个 Monorepo 的,大概率都是一方业务,技术栈是自己可控的(Google 这种超巨型 Monorepo 场景除外,这个不好说)。加上前面在规划 package 的时候,我们以是否对外部署为判断,将 pacakge 分成了「应用」和「库」两个大类。于是我们做了一个约定:所有的「库」统一都以源码形式提供(TypeScript、SCSS),本身不安排构建。而「应用」则需要安排构建流,自行处理诸如在 Node 应用中使用 ESM 这样的工程问题。这样一来,对「库」源码的改动,就会直接体现到「应用」中,立即看到结果,并且由于约定了统一的技术栈,构建流程可能涉及到的问题也都是可控的,即便是面对跨端的场景,也可以使用统一的构建流程。
Bundless 模式下的产物可以被项目选择性引入,同时也具备更好的可调试性。对于大部分项目而言,Bundless 应该都是最好的选择,这也是社区大部分项目的选择。
- pnpm 会自动去做 Link,这样每次发布用的都是最新版本的依赖。
统一配置:合并同类项 - Eslint,Typescript 与 Babel
javascript
{
"extends": "../../.eslintrc", // 注意这里的不同
"parserOptions": {
"project": "tsconfig.json"
}
}
统一 commitlint 配置,只有一处配置
- 大禹的任务和缺陷写入commit 信息内需要去做相关修改
统一命令脚本:turbo
- 使用 turbo 统一各个子引用的脚本
- Build 的时候标榜完全 cache 和异步的方式
统一依赖版本
javascript
{
"pnpm": {
"overrides": {
"foo": "1.0.0"
}
}
}
- 使用 pnpm 的依赖去统一版本,如果相统一 react 和 antd 版本,在根目录下的
package.json
内配置即可
FAQ
会不会影响构建和 hot reload?
如果只启动对应的项目,跟 mutli-repo 目前的仓库组织形式没有差别。也不是很建议一次性启动所有应用。
代码冲突问题
目前使用下来的体验是 pnpm-lock 文件冲突会比较多。在解决冲突的时候,选择 incoming 或者 current 重新生成即可。
开发和代码混乱问题
目前 compass 和工作台、天眼同时在使用 gtsp-fe-workspace
的 MonoRepo,感觉影响不大。洛萨和北树也可以说一下感受。
对于规范的确定要求比较高,5月份会去优化 monorepo 内的 tsconfig 和 eslint 配置
👻幽灵依赖问题
有两种做法,推荐第二种做法,即如果出现了幽灵依赖,加上对应的依赖即可。
- 修改 pnpm 本身配置:pnpm.io/faq#pnpm-do...
npmrc 文件内配置选项
javascript
node-linker=hoisted
- 什么叫做幽灵依赖,也就是我的 package.json 没有指明这个包,但实际项目使用了这个包,且这个包因为扁平化嵌套导致了可以直接使用,也就是非法访问,最经常碰到的就是 dayjs 这个包。
-
- 比如我的项目使用了 arco,但是 arco 的子依赖有 dayjs,那么根据扁平化,dayjs 就会被放在 node_modules 的首层。
- 但是存在很大的问题,一旦 arco 去掉了这个子依赖,那么我们的代码就直接报错了。
相关链接
- 为什么 Babel 使用Monorepo: github.com/babel/babel...
- MonoRepo 的优缺点: dl.acm.org/doi/pdf/10....
- Monorepo 宣传文章:monorepo.tools/
- 幽灵依赖问题:zhuanlan.zhihu.com/p/611712498