作为一个前端对于包管理器应该都不陌生,像 npm,pnpm,yarn 等,或许有的你没用过,但是你一定听说过。本篇文章带大家看一看包管理器中的一种现象幽灵依赖 ,以及如何解决幽灵依赖问题。
什么是幽灵依赖?
首先我们要了解什么是幽灵依赖?直接举个例子大家就能明白了。
新建一个文件夹,在终端进行 npm 初始化npm init -y
,然后随便找一个包进行安装,这里选择安装一个 express,npm i express -S
。这时候我们打开 node_modules 文件夹你会发现什么?
欸?我明明只安装了 express,怎么 node_modules 下会出现那么多包?其实很简单,那是因为 express 依赖了一些包,而依赖的这些包又会依赖其它包...npm 则是把这些包拍平了放到了 node_modules 下,这也就导致 node_modules 里出现了这么多包。
那么问题又来了,那 node_modules 下的这些包我没有去主动安装我能不导入呢?答案是肯定的。比如新建一个 index.js,然后导入 node_modules 下的body-parser
js
import bd from "body-parser";
console.log(bd);
执行一下这个文件(注意,这里需要在 package.json 中加个字段"type": "module",这样才能用 es6 语法)
发现是可以使用的,但是我们并没有去安装 body-parser。而这种依赖包就被称为为幽灵依赖
幽灵依赖带来哪些问题?
首先幽灵依赖会带来版本或者依赖丢失问题。 举个例子:
假如小坤安装一个依赖包 A1,而 A1 依赖 B1,然后小黑在接手项目时需要用到 B1,发现直接可以用就没有去主动安装 B1,某一天小坤将依赖包 A1 升级为 A2,而 A2 需要依赖升级版的 B2 或者不再需要依赖 B1,这时候启动项目问题就出现了,小黑的代码可能就出 B1 版本不兼容或者 B1 依赖丢失问题!
其次幽灵依赖可能还会导致生产环境出现问题的情况。还是上面例子
假如小坤安装一个依赖包 A1 是开发环境中的依赖,即 但是经过测试,我发现无论是 webpack 还是 vite,只要使用了某个包,无论它devDependencies
下的,而小黑需要使用的 B1 需要是生产环境的依赖,即dependencies
。这时候就会出现代码在本地没有问题,而打包过后就会出现找不到依赖 B1 的问题devDependencies
下还是dependencies
都会被打包进去。
如何解决幽灵依赖?
早先 npm 是没有幽灵依赖的,它安装完某个包时,它会将这些依赖包按照树形文件进行存储,比如安装了 A 包依赖了 B 包和 C 包,node_modules 下文件会如下图存储
但是假如项目中又需要安装个 D 包,而 D 也需要依赖 B 包
此时 node_modules 就会有两个一样的 B 包分别在 A 文件和 D 文件下,这样就会造成磁盘空间的浪费。这也就是后面 npm 升级成扁平化包管理的方式的原因(导致幽灵依赖出现)。
那么有没有方案既能解决幽灵依赖,有能节省磁盘空间呢? 答案是肯定的,这时候就要介绍一下大名鼎鼎的包管理器 pnpm 是如何解决这些问题的
pnpm
首先全局安装一下 pnpm npm i pnpm -g
,然后新建一个文件夹进行 pnpm 初始化pnpm init
,同样的我们安装一个 express 试试
pnpm install express -S
然后我们看一下它的node_modules
文件夹
我们会发现只有一个我们安装的 express 包文件和一个.pnpm 文件,而.pnpm 文件下则是平铺了所有的依赖包,同时我们发现 express 文件还是一个软连接(一个快捷方式)指向的是.pnpm 文件下的express@4.18.3/node_modules/express
文件
所以说 pnpm 解决幽灵依赖和磁盘占用的方式是把所有的包平铺存到一个仓库文件夹里面(.pnpm),同时每个包也采用了树状结构,只不过每个包的依赖包都是一个个指向平铺在.pnpm 下的软连接。然后在最外层 node_modules 里面用的则是正常安装的包文件,同样的是以软连接的形式指向.pnpm 中的包。