Skip to main content
Version: 9.x

符号链接的 `node_modules` 结构

信息

本文仅描述当不存在具有对等依赖的包时,pnpm 的 node_modules 是如何构造的。对于与对等依赖的依赖的更复杂场景,请参阅 同行如何解决

¥This article only describes how pnpm's node_modules are structured when there are no packages with peer dependencies. For the more complex scenario of dependencies with peers, see how peers are resolved.

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

¥pnpm's node_modules layout uses symbolic links to create a nested structure of dependencies.

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

¥Every file of every package inside node_modules is a hard link to the content-addressable store. Let's say you install foo@1.0.0 that depends on bar@1.0.0. pnpm will hard link both packages to node_modules like this:

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 中唯一的 "real" 文件。一旦所有包都硬链接到 node_modules,就会创建符号链接来构建嵌套依赖图结构。

¥These are the only "real" files in node_modules. Once all the packages are hard linked to node_modules, symbolic links are created to build the nested dependency graph structure.

你可能已经注意到,这两个包都硬链接到 node_modules 文件夹 (foo@1.0.0/node_modules/foo) 内的子文件夹中。需要这样做:

¥As you might have noticed, both packages are hard linked into a subfolder inside a node_modules folder (foo@1.0.0/node_modules/foo). This is needed to:

  1. 允许包自行导入。foo 应该能够 require('foo/package.json')import * as package from "foo/package.json"

    ¥allow packages to import themselves. foo should be able to require('foo/package.json') or import * as package from "foo/package.json".

  2. 避免循环符号链接。包的依赖放置在依赖包所在的同一文件夹中。对于 Node.js,依赖是否位于包的 node_modules 内部或父目录中的任何其他 node_modules 中都没有区别。

    ¥avoid circular symlinks. Dependencies of packages are placed in the same folder in which the dependent packages are. For Node.js it doesn't make a difference whether dependencies are inside the package's node_modules or in any other node_modules in the parent directories.

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

¥The next stage of installation is symlinking dependencies. bar is going to be symlinked to the foo@1.0.0/node_modules folder:

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 是项目的依赖:

¥Next, direct dependencies are handled. foo is going to be symlinked into the root node_modules folder because foo is a dependency of the project:

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

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

¥This is a very simple example. However, the layout will maintain this structure regardless of the number of dependencies and the depth of the dependency graph.

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

¥Let's add qar@2.0.0 as a dependency of bar and foo. This is how the new structure will look:

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),但文件系统中的目录深度仍然相同。

¥As you may see, even though the graph is deeper now (foo > bar > qar), the directory depth in the file system is still the same.

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

¥This layout might look weird at first glance, but it is completely compatible with Node's module resolution algorithm! When resolving modules, Node ignores symlinks, so when bar is required from foo@1.0.0/node_modules/foo/index.js, Node does not use bar at foo@1.0.0/node_modules/bar, but instead, bar is resolved to its real location (bar@1.0.0/node_modules/bar). As a consequence, bar can also resolve its dependencies which are in bar@1.0.0/node_modules.

这种布局的一个很大的好处是,只有真正位于依赖中的包才能访问。采用扁平的 node_modules 结构,所有提升的包均可触及。要详细了解为什么这是一个优势,请参阅“pnpm 的严格性有助于避免愚蠢的错误

¥A great bonus of this layout is that only packages that are really in the dependencies are accessible. With a flattened node_modules structure, all hoisted packages are accessible. To read more about why this is an advantage, see "pnpm's strictness helps to avoid silly bugs"