跳到主内容

扁平的 node_modules 不是唯一的方法

Pnpm 的新用户们经常会问我关于 pnpm 创建的奇怪的 node_modules 结构。 为什么不是平铺的? 次级依赖去哪了?

我将默认这篇文章的读者已经熟悉了 npm 与 yarn 创建的平铺的 node_modules。 如果你不明白为什么 npm3 需要开始在 v3 中使用平铺的 node_modules,你可以在这里找到一些前情提要 Why should we use 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/[email protected]/node_modules/express

OK,所以我们现在知道了 .pnpm/ 文件夹的用途。 .pnpm/ 以平铺的形式储存着所有的包,所以每个包都可以在这种命名模式的文件夹中被找到:

.pnpm/<name>@<version>/node_modules/<name>

我们称之为虚拟存储目录。

这个平铺的结构避免了 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! Pnmp 的 node_modules 结构的第二个诀窍是包的依赖项与依赖包的实际位置位于同一目录级别。 所以 express 的依赖不在 .pnpm/[email protected]/node_modules/express/node_modules/ 而是在 .pnpm/[email protected]/node_modules/

▾ node_modules
▾ .pnpm
...
▾ 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 结构一开始看起来很奇怪:

  1. 它完全适配了 Node.js。
  2. 包与包的依赖很好的被分组在一起。

有 peer 依赖的包的结构更加复杂一点点但思路是一样的:使用软链与平铺目录来构建一个嵌套结构。