前端依赖管理问题

背景

依赖管理是很常见的技术的名词,但背后的逻辑和原理你是否清楚,让我们一起review一下。

1. 为什么需要包管理?🤔

因为现在开发一个项目,通常都需要很多的依赖包。随着时间的推移,依赖包会有更新升级。新旧版本不一定能够兼容。所以说我们对包的管理主要也是对版本的管理

2. 包管理的重点是什么?🤔

个人理解:是包版本的管理

3. 包管理重要原则是什么?🤔🤔

包管理重要原则:项目本身和项目的依赖都会有其他依赖,必须保证各自的依赖都是正确的版本(package.json里定义的)

  1. 宿主项目的依赖必须安装正确的版本(package.json里定义的)

    • 当宿主项目的依赖和另一个依赖的依赖是同一个依赖时,得保证宿主项目用的是版本对的依赖,依赖用的也是版本对的依赖
  2. 项目的依赖的依赖必须也被安装准确的版本

    • 如果和项目本身的依赖版本冲突,那么会共存多个版本

因为依赖的依赖肯定在迭代,上层依赖没及时更新是常态,宿主项目内很有可能会使用到和依赖一样的依赖,这里面肯定有版本冲突的情况,所以得保证各自的依赖版本都是对的

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包,那这时你能怎么办呢?😏

回到主题,我们如何强制升级某个依赖的版本?(这个依赖是在依赖的依赖里面的,甚至层级更深

  1. 方法一:npm需升级到8以上或使用pnpm,然后在package.json里的 overrides 对象里面声明
"overrides": {
    "fool":"^3.11.0"
}
  1. 方法二:使用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

(1)
上一篇 2023年4月3日 上午10:10
下一篇 2023年4月3日 上午10:20

相关推荐

发表评论

登录后才能评论