幻影依赖

what are phantom dependencies?

Posted by My on June 1, 2023

慢慢地,放弃了使用 npm 作为包管理工具,转而使用 pnpm 作为包管理工具。其中一个原因就是 npm 存在幻影依赖问题。

什么是幻影依赖?

幻影依赖是指,在项目中,某个依赖包的版本号被锁定,但实际上该依赖包的依赖版本号并没有被锁定,导致项目中存在多个版本的依赖包。

通俗的讲,当你只安装了依赖 A ,但实际上会安装很多依赖(查看 node_modules 目录)。因为里面存在间接依赖,比如依赖 A 依赖了依赖 B,而依赖 B 又依赖了依赖 C,最终导致项目中存在多个版本的依赖包。

那就是说我们可以使用那些依赖,比如依赖 A 并不是主动去安装的(packege.json 中并没有),但是我们可以直接使用 A。

版本问题

这就是所谓的版本号没有绑定。比如说工程里的依赖 A ,版本是 1.0.0 ,而我们使用了一个没有声明的依赖 B ,版本是 1.0.0。这时候我们升级了依赖 A 到 2.0.0 ,但依赖 B 还是 1.0.0 。这时候我们运行项目,就会报错,因为依赖 A 版本号是 2.0.0 ,而依赖 B 版本号是 1.0.0 。总之就是依赖之间的版本冲突。

依赖丢失

同样的使用了一个没有声明的依赖 B ,在「开发环境」里没有问题。但是在生产环境里,没有去安装「开发依赖」,但是我们使用了和「开发依赖」相关联的依赖 B 。这时候就会出现依赖丢失的问题

npm 对依赖数的处理

这里面涉及到了「依赖图」和「文件树」的概念。依赖图是图结构,一个包依赖另外的两个包,另外两个包又依赖其他的包。文件树是树结构,每个包都是一个文件夹,里面又有其他的包,这会造成包重复。 image.png 如 图2 是 npm 早期的做法,是文件树,每个包都是一个文件夹,里面又有其他的包。图3 是借用了 yarn 的做法,不管依赖的层级有多深,全部平铺,为「一级子目录」,这样可以避免重复。

但是,这样处理后,都是子文件夹,都可以使用,这就形成了「幻影依赖」。

pnpm 的处理方案

首先在 node_modules 里,会有一个仓库 .pnpm,里面有所有依赖,包括直接的和间接的。因为这些依赖存在于一个仓库,不是 node_modules 里的子文件夹,因此不能直接使用。 image.png pnpm 把仓库建立好之后,重新生成一个树形目录(图2)。这样,只能引用子文件夹里的包,而不能引用其他的。而 图2 只是一个树关系,并没有具体的版本号,可以理解为一个快捷方式,这样不会占用额外的磁盘空间。