info: 本文仅介绍不存在有peer dependency的包时,pnpm是如何组织它的
node_modules
目录的。对于有peer dependency的更复杂的情况,请参考peer是如何被解析的。
pnpm的node_modules
使用了符号链接来创建依赖关系的嵌套结构。
node_modules
中每个package的每个文件都是指向内容可寻址存储(content-addressable store)的硬链接。假设您安装了依赖于bar@1.0.0
的foo@1.0.0
。 pnpm会将两个package都硬链接到node_modules
,如下所示:
node_modules
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>/bar
│ ├── index.js
│ └── package.json
└── foo@1.0.0
└── node_modules
└── foo -> <store>/foo
├── index.js
└── package.json
这些是node_modules
中唯一的“真实”文件。在所有package都被硬链接到node_modules
之后,pnpm会创建符号链接来构建嵌套的依赖图结构。
您可能已经注意到,这两个软件包都被硬链接到node_modules
文件夹(foo@1.0.0/node_modules/foo
)内的子文件夹中。这是为了:
- . 允许package导入自己。
foo
应该能够require ('foo/package.json')
,或者import * as package from "foo/package.json"
。 - 避免循环符号链接。package的直接依赖与间接依赖都位于同一文件夹下(原文为Dependencies of packages are placed in the same folder in which the dependent packages are.)。对于Node.js,依赖关系是在包的node_modules内部还是在父目录中的任何其他
node_modules
中,都没有关系。
安装的下一阶段是创建依赖的符号链接。bar
将被链接到foo@1.0.0/node_modules
文件夹:
node_modules
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>/bar
└── foo@1.0.0
└── node_modules
├── foo -> <store>/foo
└── 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>/bar
└── foo@1.0.0
└── node_modules
├── foo -> <store>/foo
└── bar -> ../../bar@1.0.0/node_modules/bar
这是一个非常简单的例子。但是,无论依赖项的数量和依赖关系图的深度如何,node_modules
目录都将保持此结构。
让我们添加qar@2.0.0
作为bar
和foo
的依赖项。这是新的目录结构:
node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ ├── bar -> <store>/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
├── foo@1.0.0
│ └── node_modules
│ ├── foo -> <store>/foo
│ ├── bar -> ../../bar@1.0.0/node_modules/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
└── qar@2.0.0
└── node_modules
└── qar -> <store>/qar
如您所见,即使现在依赖关系图变得更深(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的严格性有助于避免愚蠢的错误」