跳到主要内容
版本:10.x

符号链接的 `node_modules` 结构

信息

本文仅描述当没有带对等依赖的包时,pnpm 的 node_modules 是如何构建的。对于带有对等依赖的更复杂场景,请参见对等依赖如何解析

pnpm 的 node_modules 布局使用符号链接来创建依赖项的嵌套结构。

node_modules 中每个包的每个文件都是到基于内容寻址存储的硬链接。假设你安装了依赖 bar@1.0.0foo@1.0.0。pnpm 会将这两个包硬链接到 node_modules 中,如下所示:

node_modules
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar
│ ├── index.js -> <store>/001
│ └── package.json -> <store>/002
└── foo@1.0.0
└── node_modules
└── foo
├── index.js -> <store>/003
└── package.json -> <store>/004

这些是 node_modules 中唯一的"真实"文件。一旦所有包都硬链接到 node_modules 中,就会创建符号链接来构建嵌套的依赖图结构。

你可能已经注意到,两个包都被硬链接到 node_modules 文件夹中的一个子文件夹中(foo@1.0.0/node_modules/foo)。这是必需的,原因如下:

  1. 允许包导入自身。 foo 应该能够使用 require('foo/package.json')import * as package from "foo/package.json"
  2. 避免循环符号链接。 包的依赖项被放置在依赖包所在的相同文件夹中。对于 Node.js 来说,依赖项是在包的 node_modules 中还是在父目录的任何 node_modules 中并没有区别。

安装的下一个阶段是链接依赖项。bar 将被符号链接到 foo@1.0.0/node_modules 文件夹中:

node_modules
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>
└── foo@1.0.0
└── node_modules
├── foo -> <store>
└── bar -> ../../bar@1.0.0/node_modules/bar

接下来,处理直接依赖项。foo 将被符号链接到根 node_modules 文件夹中,因为 foo 是项目的依赖项:

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>
└── foo@1.0.0
└── node_modules
├── foo -> <store>
└── bar -> ../../bar@1.0.0/node_modules/bar

这是一个非常简单的例子。然而,无论依赖项的数量和依赖图的深度如何,布局都将保持这种结构。

让我们将 qar@2.0.0 添加为 barfoo 的依赖项。新结构将如下所示:

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ ├── bar -> <store>
│ └── qar -> ../../qar@2.0.0/node_modules/qar
├── foo@1.0.0
│ └── node_modules
│ ├── foo -> <store>
│ ├── bar -> ../../bar@1.0.0/node_modules/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
└── qar@2.0.0
└── node_modules
└── qar -> <store>

如你所见,即使图现在更深了(foo > bar > qar),文件系统中的目录深度仍然保持不变。

这种布局乍看可能很奇怪,但它完全兼容 Node 的模块解析算法!在解析模块时,Node 会忽略符号链接,所以当从 foo@1.0.0/node_modules/foo/index.js 引用 bar 时,Node 不会使用 foo@1.0.0/node_modules/bar,而是将 bar 解析到其真实位置(bar@1.0.0/node_modules/bar)。因此,bar 也可以解析其在 bar@1.0.0/node_modules 中的依赖项。

这种布局的一个重大优势是只有真正在依赖项中的包才能被访问。而在扁平化的 node_modules 结构中,所有提升的包都是可访问的。要了解为什么这是一个优势,请参见"pnpm 的严格性有助于避免愚蠢的错误"

不幸的是,生态系统中的许多包都是有问题的 — 它们使用了未在其 package.json 中列出的依赖项。为了最小化新用户遇到的问题,pnpm 默认将所有依赖项提升到 node_modules/.pnpm/node_modules 中。要禁用这种提升,请将 hoist 设置为 false