符号链接的 `node_modules` 结构
本文仅描述了在没有带有对等依赖的包的情况下,pnpm 的 `node_modules` 结构是如何组织的。有关带有对等依赖的更复杂场景,请参阅 如何解析对等依赖。
pnpm 的 `node_modules` 布局使用符号链接来创建依赖项的嵌套结构。
node_modules
中每个包的每个文件都是指向内容寻址存储的硬链接。假设你安装了依赖于 `[email protected]` 的 `[email protected]`。pnpm 将像这样将这两个包硬链接到 `node_modules`
node_modules
└── .pnpm
├── [email protected]
│ └── node_modules
│ └── bar -> <store>/bar
│ ├── index.js
│ └── package.json
└── [email protected]
└── node_modules
└── foo -> <store>/foo
├── index.js
└── package.json
这些是 `node_modules` 中唯一的“真实”文件。一旦所有包都被硬链接到 `node_modules`,就会创建符号链接来构建嵌套的依赖关系图结构。
你可能已经注意到,这两个包都被硬链接到 `node_modules` 文件夹(`[email protected]/node_modules/foo`)中的一个子文件夹中。这是为了
- 允许包导入自身。 `foo` 应该能够 `require('foo/package.json')` 或 `import * as package from "foo/package.json"`。
- 避免循环符号链接。 包的依赖项被放置在与依赖包相同的文件夹中。对于 Node.js 来说,依赖项是在包的 `node_modules` 中还是在父目录中的任何其他 `node_modules` 中,都没有区别。
安装的下一阶段是符号链接依赖项。`bar` 将被符号链接到 `[email protected]/node_modules` 文件夹
node_modules
└── .pnpm
├── [email protected]
│ └── node_modules
│ └── bar -> <store>/bar
└── [email protected]
└── node_modules
├── foo -> <store>/foo
└── bar -> ../../[email protected]/node_modules/bar
接下来,处理直接依赖项。`foo` 将被符号链接到根 `node_modules` 文件夹,因为 `foo` 是项目的依赖项
node_modules
├── foo -> ./.pnpm/[email protected]/node_modules/foo
└── .pnpm
├── [email protected]
│ └── node_modules
│ └── bar -> <store>/bar
└── [email protected]
└── node_modules
├── foo -> <store>/foo
└── bar -> ../../[email protected]/node_modules/bar
这是一个非常简单的例子。但是,无论依赖项的数量和依赖关系图的深度如何,布局都将保持这种结构。
让我们将 `[email protected]` 添加为 `bar` 和 `foo` 的依赖项。新的结构将如下所示
node_modules
├── foo -> ./.pnpm/[email protected]/node_modules/foo
└── .pnpm
├── [email protected]
│ └── node_modules
│ ├── bar -> <store>/bar
│ └── qar -> ../../[email protected]/node_modules/qar
├── [email protected]
│ └── node_modules
│ ├── foo -> <store>/foo
│ ├── bar -> ../../[email protected]/node_modules/bar
│ └── qar -> ../../[email protected]/node_modules/qar
└── [email protected]
└── node_modules
└── qar -> <store>/qar
如你所见,即使图现在更深了(`foo > bar > qar`),文件系统中的目录深度仍然相同。
这种布局乍一看可能很奇怪,但它完全兼容 Node 的模块解析算法!在解析模块时,Node 会忽略符号链接,因此当从 `[email protected]/node_modules/foo/index.js` 中需要 `bar` 时,Node 不会使用 `[email protected]/node_modules/bar` 中的 `bar`,而是将 `bar` 解析到其真实位置(`[email protected]/node_modules/bar`)。因此,`bar` 也可以解析其位于 `[email protected]/node_modules` 中的依赖项。
这种布局的一个很大的好处是,只有真正位于依赖项中的包才是可访问的。使用扁平化的 `node_modules` 结构,所有提升的包都是可访问的。要了解更多关于为什么这是一个优势的信息,请参阅 "pnpm 的严格性有助于避免愚蠢的错误"