本文会以尽量简洁的语言来描述当下主流包管理工具 npm、yarn、pnpm 的管理策略以及进化史,不涉及任何晦涩的代码。

2010 年 1 月,一款名为 npm 的包管理器诞生。
很多人认为 npm 是 node package manager 的缩写,其实不是,而且 npm 根本也不是任何短语的缩写。
npm 官方辟谣:

它的前身其实是名为 pm(pkgmakeinst) 的 bash 工具,它可以在各种平台上安装各种东西。
硬要说缩写的话,也应该是 node pm 或者 new pm。
npm 在早期采用的是嵌套的 node_modules 结构,直接依赖会平铺在 node_modules 下,子依赖嵌套在直接依赖的 node_modules 中。
比如项目依赖了A 和 C,而 A 和 C 依赖了不同版本的 B@1.0 和 B@2.0,node_modules 结构如下:
node_modules
├── A@1.0.0
│ └── node_modules
│ └── B@1.0.0
└── C@1.0.0
└── node_modules
└── B@2.0.0
复制
如果 D 也依赖 B@1.0,会生成如下的嵌套结构:
node_modules
├── A@1.0.0
│ └── node_modules
│ └── B@1.0.0
├── C@1.0.0
│ └── node_modules
│ └── B@2.0.0
└── D@1.0.0
└── node_modules
└── B@1.0.0
复制
可以看到同版本的 B 分别被 A 和 D 安装了两次。
在真实场景下,依赖增多,冗余的包也变多,node_modules 最终会堪比黑洞,很快就能把磁盘占满。而且依赖嵌套的深度也会十分可怕,这个就是依赖地狱。