符号链接的 `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.0
的 foo@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:
-
允许包自行导入。
foo
应该能够require('foo/package.json')
或import * as package from "foo/package.json"
。¥allow packages to import themselves.
foo
should be able torequire('foo/package.json')
orimport * as package from "foo/package.json"
. -
避免循环符号链接。包的依赖放置在依赖包所 在的同一文件夹中。对于 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 othernode_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
作为 bar
和 foo
的依赖。新结构如下所示:
¥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"