如何解决项目依赖重复打包问题

本文探讨了前端项目中重复依赖导致的构建体积增大问题,分析了lockfile副作用、不兼容版本和peerDependencies等因素如何产生重复依赖,并介绍了如何发现和修复这些问题。通过直接查看依赖分布图、使用分析工具、配置打包工具插件等方法检测重复依赖,以及对package打包时避免打包dependencies、声明兼容性版本号、使用peerDependencies等方法来修复。此外,文章还分享了作者在实战中使用的工具和策略。
摘要由CSDN通过智能技术生成

title: 如何解决项目依赖重复打包问题 tags: - webpack - vite categories: - 前端 author: 余腾靖

pubDatetime: 2024-03-22

由于最近面试经常被问到这个问题(简历上写了),感觉答的时候不是很系统清晰,于是便有了这篇文章。

为啥对这个问题这么上心?

在上家公司最后一段时间是做前端工程基建相关的,不说是最有成长的一段时间,但绝对是最开心的一段时间。上来第一个任务是优化项目构建体积,项目之前是 webpack 写的,做技术升级之后迁移到了 vite,包管理器也从 yarn1 迁移到 pnpm,但是迁移后发现主入口 bundle 的体积增加了很多,后面也是采用了多个方法对体积进行优化:

  • 去除重复依赖,有很多依赖由于引用到了不同版本被打包了多次,具体哪些包忘了,但是印象中 pnpm.overrides 很长
  • 按需导入,组件库是 ant design vue based,我知道最新版已经支持 tree-shaking,不需要配置按需导入,但当时项目用的版本很低了,毕竟还是 vue2。
  • 选择精简的版本,例如我们项目目只用到了 paper.js 的核心功能,但是默认 papermain 是指向 dist/paper-full.js(451KB) 你可以配置它指向 dist/paper-core.js(394KB)
  • 使用 importmap 将包从构建中分离,改为使用浏览器原生的 ESM 从 CDN 加载
  • 对于一些还在使用 rollup + babel 打包的 package 使用 babel-runtime 避免重复打包 helper 代码
  • 精准配置 browserslist,所有构建工具统一使用 package.jsonbrowserslist 字段读取浏览器兼容目标版本
  • 项目中同时使用了多个功能类型的 pkg,例如时间相关的 moment, dayjs, date-fns, 还有我记得生成二维码和处理 xlsx 的库也有多个,为了这个问题我还写了个 cli: find-similar-packages
  • 通知其它部门同事不要把 node_modules 打包到 dist 里面,把 ant design vuebabel-runtime 升级到 @babel/runtime,反正就是说有些包打包很不规范
  • ...

等等,离职混日子快半年了,暂时只能想起这些。

回到主题,在我所有的优化策略中,去除重复依赖减小打包体积的效果是占第二位的。第一位是 importmap,最简单的减小体积策略就是不打包。关于 importmap,有机会单独写一篇文章。

重复依赖是如何产生的?

在讨论之前,先介绍一些后面会提到的术语,确保我们在同一个频道上。

下面是一个典型的 monorepo 前端项目:

plaintext ./mono ├── apps // 应用级别的包 │ ├── mobile │ └── web ├── packages // 共享模块 │ ├── pkg1 │ ├── pkg2 │ ├── ui │ └── utils └── tools // 工具包 ├── eslint-config └── vite-config

apps, packages, tools 都叫 workspace, 里面 package 称之为 workspace packagemono 文件夹叫 root workspace

在包管理器的视角依赖可以分为两类:

  • 直接依赖:例如 workspace package uipackage.json 中声明的 axios
  • 间接依赖:例如 axios 依赖的 follow-redirects

按照用途分为两类:

  • 源码依赖:例如 apps/web/src 导入了 pkg1vuepkg1vue 以及它俩依赖树上的依赖称为源码依赖
  • 开发依赖:例如 vite, esbuild 不会被打包到 apps/web/dist 中的只在开发时使用的依赖,也包括它俩依赖树上的依赖

lockfile 的副作用

lockfile 可以帮我们确保安装的依赖完全一致,但是它却也是导致我们项目依赖安装多个版本的主要原因之一。

场景还原:

  1. 我们在 pkg1 中的 dependencies 声明 "foo": "^1.0.1"”,此时 foo 最新版本是 1.0.1,运行 pnpm install, 这时 lockfile 中写入了 pkg1foo 解析到的版本是 1.0.1,实际也是安装 1.0.1
  2. 某一天我们需要在 pkg2 中开发需求,pkg2 也要用到 foo,于是运行 pnpm --filter pkg2 add foo,在这段时间 foo 发布了 1.0.2,此时 pkg2 安装的就会是 1.0.2。我们的 app package 依赖 pkg1pkg2,于是打包 app 的时候就会打包 foo@1.0.1foo@1.0.2

你可能会说 pnpm 咋这么蠢,不会直接把 pkg1foo 也安装为 1.0.2 吗,1.0.2 是符合 ^1.0.1 的兼容性要求的呀。实际上按照我的理解,pnpm 之所以没这么做,是因为

  1. lockfile 里面已经声明了 pkg1foo 解析到的版本,所以会直接按照 lockfile 声明的版本来
  2. 因为 pkg 中 foo@1.0.1 之前已经验证测试时可用的,要是它帮你更新到 1.0.2 可能会出现 bug,为了确保项目的稳定默认不会帮你升级。semver 只是一个规范, 1.0.11.0.2 到底有没有引没有引入 breaking change 那你得去看 changelog 和源代码才能确定。

不兼容版本

一个很典型的例子就是去年 axios 发布了 1.x,项目中同时存在 0.x1.x。草,axios 这么古老的项目直到去年才发 1.x 你敢信?esbuildreact native 加把油,希望我退休之前能发 1.0

即便依赖树上的包在 dependencies 中声明 axios 的时候都使用了兼容性前缀 ^,但是对于这种不符合兼容性前缀的多个版本,pnpm 就没法通过

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值