背景
依赖管理是很常见的技术的名词,但背后的逻辑和原理你是否清楚,让我们一起review一下。
1. 为什么需要包管理?🤔
因为现在开发一个项目,通常都需要很多的依赖包。随着时间的推移,依赖包会有更新升级。新旧版本不一定能够兼容。所以说我们对包的管理主要也是对版本的管理。
2. 包管理的重点是什么?🤔
个人理解:是包版本的管理
3. 包管理重要原则是什么?🤔🤔
包管理重要原则:项目本身和项目的依赖都会有其他依赖,必须保证各自的依赖都是正确的版本(package.json里定义的)
-
宿主项目的依赖必须安装正确的版本(package.json里定义的)
- 当宿主项目的依赖和另一个依赖的依赖是同一个依赖时,得保证宿主项目用的是版本对的依赖,依赖用的也是版本对的依赖
-
项目的依赖的依赖必须也被安装准确的版本
- 如果和项目本身的依赖版本冲突,那么会共存多个版本
因为依赖的依赖肯定在迭代,上层依赖没及时更新是常态,宿主项目内很有可能会使用到和依赖一样的依赖,这里面肯定有版本冲突的情况,所以得保证各自的依赖版本都是对的
4. 为什么我们node_modules总是特别大?🤔
原因:由上面的原则可知,一些依赖包被重复安装了多次,因为有多个版本,所以导致node_modules特别大
如何证明呢?
- 举个简单的例子,比如:项目内输入:npm list axios
会发现其实很多依赖都依赖了axios,并且版本不一样,为了保证整个程序不出错,会下载多个axios的版本
- 可以自行打开node_modules,找到对应依赖,点开会看到依赖自己的node_modules,里面有自己版本的axios
当然各个包管理工具对相同版本的依赖还是有优化的,不会无脑install多份
5. *依赖冲突如何解决?🤔🤔🤔
相信这是大家伙最关心的一个问题,上文讲半天,其实也算为这个问题作铺垫
那我们直接进入正题,为了方便理解,我先构造一个依赖场景:
- 项目和各模块之间依赖关系如下(A、B、C指的是npm包,是项目的依赖。A、B、C各自都依赖axios)
上结论:
场景:依赖关系如上 | 是否会install多份axios@1.0.0 | 是否会install多份axios@2.0.0 | 各自会用axios的什么版本? |
---|---|---|---|
npm | 不会,只会有一份👍: node_module/axios | 会有多份😭: node_module/A/node_module/axios node_module/B/node_module/axios | 都是用自己package.json里写的准确版本: – 项目本身 和 C:用axios@1.0.0 – A 和 B:用axios@2.0.0 |
yarn | 同上 | 同上 | 同上 |
pnpm | 同上 | 不会,只会有一份👍 – node_module/.pnpm/… – 相同版本的依赖只会有一份,会以路径的方式link到这个依赖 |
同上 |
测试版本:
➜ npm -v
6.14.16
➜ yarn -v
1.22.19
➜ pnpm -v
7.11.0
6. 幽灵依赖是什么?怎么解决?🤔
简单的说,幽灵依赖就是:
-
项目内的package.json内,没有安装对应依赖,比如没有install axios,但你在项目内可以使用axios。因为你依赖的A包里的package.json里的 dependencies 对象里 有axios。
关系如下:
axios会被打包到node_modules下去,例如:node_modules/axios(因为要减少重复打包,所以axios被默认隐式放到node_modules下了)
为什么需要解决?
- 因为有安全风险,比如依赖A某天不用axios了,改用fetch,然后你升级了依赖A,这时候 node_modules里就没有axios了,那代码不就直接GG了吗..
如何解决?
- 用pnpm(yarn和npm都有幽灵依赖的问题,目前我用的版本是这样的,不保证未来不会解决)
pnpm解决的原理?
- 同样以上面的axios为例,用pnpm install后,axios会被放到 node_modules/.pnpm/ 目录下,这样就被避免了隐式调用
7. 能不能强制升级某个依赖的版本?🤔
先说下背景:
-
在公司内可能很容易收到安全部门发出的消息,内容可能就是:通过扫描你xx项目,发现有重大安全风险。让你升级某个依赖,假设这个依赖叫:fool,安全部门要求升级到fool@3.11.0以上
-
然后你去xx项目内的package.json里找fool,结果发现你根本没有安装过fool。如果你对前端依赖管理不太熟悉的话,此时的你肯定很懵逼😳!甚至以为是灵异事件😂
-
其实是因为安装了其他的依赖,然后依赖的依赖是这个fool
-
如何找到是哪个依赖用了fool? 输入:npm list fool
-
然后你找到了谁用了fool,夸张一点,假设是jQuery。
-
这个时候,你要解决这个问题的话,你需要升级jQuery,如果高版本的jQuery正好解决了这个,那确实这个安全风险也被解决了。但如果最高版本的jQuery都没有升级这个fool包,那这时你能怎么办呢?😏
-
回到主题,我们如何强制升级某个依赖的版本?(这个依赖是在依赖的依赖里面的,甚至层级更深)
- 方法一:npm需升级到8以上或使用pnpm,然后在package.json里的 overrides 对象里面声明
"overrides": {
"fool":"^3.11.0"
}
- 方法二:使用yarn,然后在package.json里的 resolutions 对象里面声明
"resolutions": {
"fool":"^3.11.0"
}
如果你的项目使用了npm,但npm版本在8以下,怎么办?😅
- 可以用 npm-force-resolutions 这个库,这样就可以不切换到yarn,也可以使用yarn的resolutions对象了
以上办法都可以让所有的fool都升级到 ^3.11.0
- 但是多少还是有点稳定性风险(比如某依赖用的fool@0.0.2,强制升级到3.11.0后,如果有break change,那可能会导致代码出问题),还是需要自测回归哈,别瞎用
8. 看起来依赖管理工具处理的很完善,那为什么我们还是经常碰到依赖版本冲突的问题呢?🤔🤔
最常见的例子就是:和webpack相关的冲突
-
比如当前项目的webpack@5.x,然后安装一个webpack的插件,结果报错。这是因为插件依赖webpack4或4以下,webapck5有break change,导致以前的某些api不再可用,所以就报错了
-
为啥版本冲突呢?不是会安装多份webpack依赖吗?
- 确实会安装多个版本的webpack,但是在项目内执行npm run build的时候,当前的运行时是项目的webpack 是5以上,这就导致当前的node进程内,webpack版本就是5以上,那自然就报错了😒
总结:
-
和命令行相关的依赖版本冲突的话,很容易报错,包管理工具无法兼容此类error。
- 常见的有 各类打包工具(webpack、rollup),编译器(ts、eslint、sass等),e2e工具,单测工具,脚手架。他们都是命令行run的,与之相关的插件版本不对的话,很容易报错
9. 依赖是否会被重复打包?🤔
*有可能会重复打包
场景:比如项目本身依赖了swiper@2.0.0 + 组件库A。组件库A本身又依赖swiper@3.0.0,那么这个时候,你的项目就会把2份swiper打包进去(不同版本)。如果有全局变量,会相互覆盖… 这样是比较糟糕的,既有安全风险,对性能也不好
解决:(继续以上面的case举例)
- 组件库A的构建产物除了提供完整版外,需要多提供一个min版,在构建时 需要把swiper给external掉(webpack里可以配置)
码字不易,点赞鼓励!!
本文正在参加「金石计划」
原文链接:https://juejin.cn/post/7217482458773209143 作者:bigtree