Skip to main content

平铺的 node_modules 并不是唯一的方式

· 6 min read
Zoltan Kochan
Lead maintainer of pnpm

pnpm 的新用户经常问我关于 pnpm 创建的 node_modules 奇怪结构的问题。为什么它不是平铺的?所有的子依赖都在哪里?

🌐 New users of pnpm frequently ask me about the weird structure of node_modules that pnpm creates. Why is it not flat? Where are all the sub-dependencies?

我将假设文章的读者已经熟悉由 npm 和 Yarn 创建的扁平 node_modules。如果你不理解为什么 npm 3 必须在 v3 中开始使用扁平 node_modules,你可以在 Why should we use pnpm? 中找到一些前史。

那么,为什么 pnpm 的 node_modules 不寻常呢?让我们创建两个目录,并在其中一个目录中运行 npm add express,在另一个目录中运行 pnpm add express。以下是你在第一个目录的 node_modules 中得到的顶部内容:

🌐 So why is pnpm's node_modules unusual? Let's create two directories and run npm add express in one of them and pnpm add express in the other one. Here's the top of what you get in the first directory's node_modules:

.bin
accepts
array-flatten
body-parser
bytes
content-disposition
cookie-signature
cookie
debug
depd
destroy
ee-first
encodeurl
escape-html
etag
express

你可以在这里查看整个目录。

🌐 You can see the whole directory here.

这就是你在 pnpm 创建的 node_modules 中得到的内容:

🌐 And this is what you get in the node_modules created by pnpm:

.pnpm
.modules.yaml
express

你可以在这里查看 here

🌐 You can check it here.

那么所有的依赖在哪里?在 node_modules 中只有一个名为 .pnpm 的文件夹和一个名为 express 的符号链接。嗯,我们只安装了 express,所以那是你的应用必须访问的唯一包

🌐 So where are all the dependencies? There is only one folder in the node_modules called .pnpm and a symlink called express. Well, we installed only express, so that is the only package that your application has to have access to

阅读更多关于为什么 pnpm 的严格性是好事的信息 这里

让我们看看 express 里面有什么:

🌐 Let's see what is inside express:

▾ node_modules
▸ .pnpm
▾ express
▸ lib
History.md
index.js
LICENSE
package.json
Readme.md
.modules.yaml

express没有node_modulesexpress的所有依赖都在哪里?

诀窍在于 express 只是一个符号链接。当 Node.js 解析依赖时,它会使用它们的实际位置,所以不会保留符号链接。但你可能会问,express 的实际位置在哪里?

🌐 The trick is that express is just a symlink. When Node.js resolves dependencies, it uses their real locations, so it does not preserve symlinks. But where is the real location of express, you might ask?

在这里: node_modules/.pnpm/express@4.17.1/node_modules/express.

🌐 Here: node_modules/.pnpm/express@4.17.1/node_modules/express.

好的,现在我们知道了 .pnpm/ 文件夹的用途。.pnpm/ 将所有包存储在一个平坦的文件夹结构中,因此每个包都可以在一个按以下模式命名的文件夹中找到:

🌐 OK, so now we know the purpose of the .pnpm/ folder. .pnpm/ stores all the packages in a flat folder structure, so every package can be found in a folder named by this pattern:

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

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

🌐 We call it the virtual store directory.

这种扁平结构避免了由 npm v2 创建的嵌套 node_modules 所导致的路径过长问题,但与 npm v3、4、5、6 或 Yarn v1 创建的扁平 node_modules 不同,它仍然保持了包的隔离。

🌐 This flat structure avoids the long path issues that were caused by the nested node_modules created by npm v2 but keeps packages isolated unlike the flat node_modules created by npm v3,4,5,6 or Yarn v1.

现在让我们看看 express 的实际位置:

🌐 Now let's look into the real location of 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/

🌐 Is it a scam? It still lacks node_modules! The second trick of pnpm's node_modules structure is that the dependencies of packages are on the same directory level as the real location of the dependent package. So dependencies of express are not in .pnpm/express@4.17.1/node_modules/express/node_modules/ but in .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 的依赖放到上一层可以避免循环符号链接。

🌐 All the dependencies of express are symlinks to appropriate directories in node_modules/.pnpm/. Placing dependencies of express one level up allows avoiding circular symlinks.

所以正如你所看到的,尽管 pnpm 的 node_modules 结构乍一看似乎不寻常:

🌐 So as you can see, even though pnpm's node_modules structure seems unusual at first:

  1. 它完全兼容 Node.js
  2. 包和它们的依赖被很好地分组

对于具有对等依赖的包来说,结构会稍微复杂一些,但思路是相同的:使用符号链接在平铺的目录结构中创建嵌套。

🌐 The structure is a little bit more complex for packages with peer dependencies but the idea is the same: using symlinks to create a nesting with a flat directory structure.