Post

Understanding Phantom Dependencies

What phantom dependencies are, why they cause hidden bugs, and how pnpm eliminates them by design.

Understanding Phantom Dependencies

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

1. 什么是幻影依赖?

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

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

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

2. 版本问题

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

3. 依赖丢失

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

3.1. npm 对依赖树的处理

这里面涉及到了「依赖图」和「文件树」的概念。依赖图是图结构,一个包依赖另外的两个包,另外两个包又依赖其他的包。文件树是树结构,每个包都是一个文件夹,里面又有其他的包,这会造成包重复。

image.png

如 图2 是 npm 早期的做法,是文件树,每个包都是一个文件夹,里面又有其他的包。图3 是借用了 yarn 的做法,不管依赖的层级有多深,全部平铺,为「一级子目录」,这样可以避免重复。

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

3.2. pnpm 的处理方案

首先在 node_modules 里,会有一个仓库 .pnpm,里面有所有依赖,包括直接的和间接的。因为这些依赖存在于一个仓库,不是 node_modules 里的子文件夹,因此不能直接使用。

image.png

pnpm 把仓库建立好之后,重新生成一个树形目录(图2)。这样,只能引用子文件夹里的包,而不能引用其他的。而 图2 只是一个树关系,并没有具体的版本号,可以理解为一个快捷方式,这样不会占用额外的磁盘空间。

This post is licensed under CC BY 4.0 by the author.