pnpm: 扁平化的 node_modules 不是唯一的方式
原文链接:Flat node_modules is not the only way,2020.05.27,by Zoltan Kochan
新用户经常问我关于 pnpm 创建的 node_modules
结构为什么如此奇怪。为什么它不是扁平化的?所有子依赖(sub-dependencies)在哪里?
我假设读者已经熟悉 npm 和 Yarn 创建的扁平化
node_modules
。如果你不明白为什么 npm 3 必须从 v3 开始使用扁平化 node_modules,可以在《为什么我们应该使用pnpm?》中找到一些原因。
那么,为什么 pnpm 的 node_modules
与众不同呢?让我们创建两个目录,在其中一个目录中运行 npm add express
,在另一个目录中运行 pnpm add express
。下面是第一个目录的 node_modules
顶层内容:
.bin
accepts
array-flatten
body-parser
bytes
content-disposition
cookie-signature
cookie
debug
depd
destroy
ee-first
encodeurl
escape-html
etag
express
你可以在这里看到整个目录。
而下面就是通过 pnpm 创建的 node_modules
中所得到的内容:
.pnpm
.modules.yaml
express
你可以在这里查看。
那么所有的依赖项都在哪里呢?node_modules
文件夹中只有一个名为 .pnpm
的文件夹和一个名为 express
的符号链接。我们只安装了 express
,所以这也我们在应用程序唯一能够访问的包。
在这里阅读更多关于 pnpm 严格性好处的信息。
让我们来看看 express
里面有什么:
▾ node_modules
▸ .pnpm
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
.modules.yaml
express
没有 node_modules
?express
的所有依赖在哪里?
诀窍在于 express
只是一个符号链接。当 Node.js 解析依赖项时,它使用它们的真实位置,会忽略符号链接。但你可能会问,express
的真实位置在哪里呢?
这里: node_modules/.pnpm/express@4.17.1/node_modules/express。
好的,现在我们知道了 .pnpm/
文件夹的目的。.pnpm/
将所有包存储在一个扁平化的文件夹结构中,因此每个包都可以在以这种模式命名的文件夹中找到:
.pnpm/<name>@<version>/node_modules/<name>
我们称之为虚拟存储目录(virtual store directory)。
译注:pnpm 的虚拟存储目录最初是 .registry.npmjs.org///node_modules/ 方式,后来才改成上述结构。因为 .pnpm 具有标识意义,类似于项目中的 .vscode、.github 的作用
这种扁平结构避免了由 npm v2 创建的嵌套 node_modules
引起的长路径问题,但与 npm v3、4、5、6 或 Yarn v1 创建的扁平 node_modules
相比,它又保持了包的隔离性。
现在让我们来看一下 express
的真实位置:
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
这是一个骗局吗?它还缺少 node_modules
!pnpm 的 node_modules
结构的第二个技巧是,包的依赖关系与其真实位置所在的目录层级相同。因此,express
的依赖关系不在 .pnpm/express@4.17.1/node_modules/express/node_modules/
中,而是在 .pnpm/express@4.17.1/node_modules/ 中:
▾ node_modules
▾ .pnpm
▸ accepts@1.3.5
▸ array-flatten@1.1.1
...
▾ express@4.16.3
▾ node_modules
▸ accepts
▸ array-flatten
▸ body-parser
▸ content-disposition
...
▸ etag
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
所有 express
的依赖都是指向 node_modules/.pnpm/
中适当目录的符号链接。将 express
的依赖放在上一级可以避免循环符号链接。
所以你可以看到,尽管 pnpm
的 node_modules
结构一开始看起来不寻常:
- 它完全兼容 Node.js
- 包和它们的依赖关系又能被很好地分组
译注:这就是 pnpm 非常巧思的地方,利用了 Node.js 查找依赖时的寻址策略(查找当前目录或上层目录中的 node_modules 目录,以此类推),配合符号链接:既解决了 npm 扁平化带来的间接依赖暴露问题又做到了依赖分组,同时借助全局存储(global store)大大节省了硬盘空间。
对于具有 peer dependencies
的包,结构稍微复杂些,但思路是相同的:使用符号链接创建一个嵌套并具有扁平化的目录结构。
转载自:https://juejin.cn/post/7255309681547952185