前言
在一个项目中最头疼的问题就是依赖装不上,项目一直启动不起来,有些不太耐心的新同学就很容易放弃。
幽灵依赖一直是前端工程化包管理器的一个重点问题,pnpm
包管理器火起来的一大原因就是,他解决了幽灵依赖这个问题。
今天这篇文章就将带你彻底认识node_modules以及它产生的问题:幽灵依赖
发展历程
嵌套排序
相传在很久很久以前,node_modules还是非常质朴纯粹的,我们叫它小N,小N有两个包一个叫package-A
一个叫package-B
,这两个包呢有一个共同点,都有lodash
这个包。
此时小N是这么处理的,目录结构如下:
这就是最初的node_modules处理的方式:嵌套结构。一个包嵌套着N个包。
相信大家都会发现一个问题,如果lodash
包还引入着其他包是不是一直需要递归下去,还有就是lodash
这个包下载太多次浪费空间。
总结一下这种方式:
- 嵌套过深
- 占用空间过大
平铺结构
小N为了解决这个问题,把lodash
包提升同一级目录
小N处理同一版本的包就像上面那样,如果用的同一个包的不同版本,取高版本
幽灵依赖
当一个包被提升到一个上层目录,而主项目仍然在其依赖中引用该包时,会导致一种看似正常但潜在危险的情况。让我通过一个例子来说明这个问题:
假设有一个项目结构如下:
project/
├── node_modules/
│ ├── package-A/
│ │ ├── lodash@1.0.0/
│ │ └── ...
│ └── ...
├── package.json
└── ...
在这个项目中,package-A
使用了 lodash@1.0.0
版本作为其依赖,并且已经被提升到了项目的顶层 node_modules
目录中。
主项目的 package.json
文件中可能会包含以下内容:
{
"name": "main-project",
"dependencies": {
"package-A": "^1.0.0"
}
}
这时,主项目依赖于 package-A
,而 package-A
实际上已经被提升到了顶层的 node_modules
目录。
一段时间后,开发者决定废弃使用 package-A
,并将其从项目中移除。但是,他们忘记了移除主项目中对于 package-A
的依赖引用。
随后,由于某些原因,项目的顶层 node_modules
目录被清理,其中的所有依赖项都被删除,包括了 package-A
。
此时,当主项目尝试运行时,会发现虽然 package.json
中声明了对于 package-A
的依赖,但是在实际的 node_modules
目录中找不到这个包,因为它已经被废弃并且删除了。
这将导致主项目无法找到所需的包而报错,尽管一开始没有报错,因为当时 package-A
被提升到顶层 node_modules
目录中。
pnpm
它解决了 npm/yarn 平铺 node_modules 带来的依赖项重复的问题
假设存在依赖:
├── package-a
│ └── lodash@4.0.0
├── package-b
│ └── lodash@4.0.0
├── package-c
│ └── lodash@3.0.0
└── package-d
└── lodash@3.0.0
那么不可避免地在 npm 或者 yarn 中,lodash@3.0.0
会被多次安装,无疑造成了空间的浪费与诸多问题。
而pnpm使用软硬链接去下载依赖,下载到全局中,并且同一个依赖只会在全局下载一次。
./node_modules/package-a -> .pnpm/package-a@1.0.0/node_modules/package-a
./node_modules/package-b -> .pnpm/package-b@1.0.0/node_modules/package-b
./node_modules/package-c -> .pnpm/package-c@1.0.0/node_modules/package-c
./node_modules/package-d -> .pnpm/package-d@1.0.0/node_modules/package-d
./node_modules/.pnpm/lodash@3.0.0
./node_modules/.pnpm/lodash@4.0.0
./node_modules/.pnpm/package-a@1.0.0
./node_modules/.pnpm/package-a@1.0.0/node_modules/package-a
./node_modules/.pnpm/package-a@1.0.0/node_modules/lodash -> .pnpm/package-a@1.0.0/node_modules/lodash@4.0.0
./node_modules/.pnpm/package-b@1.0.0
./node_modules/.pnpm/package-b@1.0.0/node_modules/package-b
./node_modules/.pnpm/package-b@1.0.0/node_modules/lodash -> .pnpm/package-b@1.0.0/node_modules/lodash@4.0.0
./node_modules/.pnpm/package-c@1.0.0
./node_modules/.pnpm/package-c@1.0.0/node_modules/package-c
./node_modules/.pnpm/package-c@1.0.0/node_modules/lodash -> .pnpm/package-c@1.0.0/node_modules/lodash@3.0.0
./node_modules/.pnpm/package-d@1.0.0
./node_modules/.pnpm/package-d@1.0.0/node_modules/package-d
./node_modules/.pnpm/package-d@1.0.0/node_modules/lodash -> .pnpm/package-d@1.0.0/node_modules/lodash@3.0.0
lodash@3.0.0
与 lodash@4.0.0
会生成一个指向全局目录的硬链接,如果新项目依赖二者,则可复用存储空间。
小结
总结来说,通过上述的特殊方式,pnpm 能够高效地解决幽灵依赖问题,实现更节省空间、更稳定的包管理。这也是为什么 pnpm 在前端社区引起关注的一个原因。