慢慢地,放弃了使用 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 对依赖数的处理
这里面涉及到了「依赖图」和「文件树」的概念。依赖图是图结构,一个包依赖另外的两个包,另外两个包又依赖其他的包。文件树是树结构,每个包都是一个文件夹,里面又有其他的包,这会造成包重复。
如 图2 是 npm 早期的做法,是文件树,每个包都是一个文件夹,里面又有其他的包。图3 是借用了 yarn 的做法,不管依赖的层级有多深,全部平铺,为「一级子目录」,这样可以避免重复。
但是,这样处理后,都是子文件夹,都可以使用,这就形成了「幻影依赖」。
pnpm 的处理方案
首先在 node_modules 里,会有一个仓库 .pnpm
,里面有所有依赖,包括直接的和间接的。因为这些依赖存在于一个仓库,不是 node_modules 里的子文件夹,因此不能直接使用。
pnpm 把仓库建立好之后,重新生成一个树形目录(图2)。这样,只能引用子文件夹里的包,而不能引用其他的。而 图2 只是一个树关系,并没有具体的版本号,可以理解为一个快捷方式,这样不会占用额外的磁盘空间。