likes
comments
collection
share

基于 Monorepo 解决依赖统一,业务组件代码重复度高的问题

作者站长头像
站长
· 阅读数 8

基于 Monorepo 解决依赖统一,业务组件代码重复度高的问题

项目中现有问题定义

多个子项目间改动同步

我正在开发的项目 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 应该都是最好的选择,这也是社区大部分项目的选择。

基于 Monorepo 解决依赖统一,业务组件代码重复度高的问题

  • pnpm 会自动去做 Link,这样每次发布用的都是最新版本的依赖。

统一配置:合并同类项 - Eslint,Typescript 与 Babel

{
  "extends": "../../.eslintrc", // 注意这里的不同
  "parserOptions": {
    "project": "tsconfig.json"
  }
}

统一 commitlint 配置,只有一处配置

  • 大禹的任务和缺陷写入commit 信息内需要去做相关修改

统一命令脚本:turbo

  • 使用 turbo 统一各个子引用的脚本
  • Build 的时候标榜完全 cache 和异步的方式

统一依赖版本

{
  "pnpm": {
    "overrides": {
      "foo": "1.0.0"
    }
  }
}
  • 使用 pnpm 的依赖去统一版本,如果相统一 react 和 antd 版本,在根目录下的 package.json内配置即可

FAQ

会不会影响构建和 hot reload?

如果只启动对应的项目,跟 mutli-repo 目前的仓库组织形式没有差别。也不是很建议一次性启动所有应用。

代码冲突问题

目前使用下来的体验是 pnpm-lock 文件冲突会比较多。在解决冲突的时候,选择 incoming 或者 current 重新生成即可。

👻幽灵依赖问题

有两种做法,推荐第二种做法,即如果出现了幽灵依赖,加上对应的依赖即可。

npmrc 文件内配置选项

node-linker=hoisted 
  • 什么叫做幽灵依赖,也就是我的 package.json 没有指明这个包,但实际项目使用了这个包,且这个包因为扁平化嵌套导致了可以直接使用,也就是非法访问,最经常碰到的就是 dayjs 这个包。
  • 比如我的项目使用了 arco,但是 arco 的子依赖有 dayjs,那么根据扁平化,dayjs 就会被放在 node_modules 的首层。
    • 但是存在很大的问题,一旦 arco 去掉了这个子依赖,那么我们的代码就直接报错了。

相关链接