基于符号链接的 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's node_modules
layout uses symbolic links to create a nested structure of
dependencies.
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
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.
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
). 这必要的:
- allow packages to import themselves.
foo
should be able torequire('foo/package.json')
orimport * as package from "foo/package.json"
. - 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
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
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
这是一个非常简单的例子。 但是,无论依赖项的数量和依赖关系图的深度如何,布局都会保持这种结构。
Let's add qar@2.0.0
as a dependency of bar
and 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
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 的模块解析算法完全兼容! 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
.
这种布局的一大好处是只有真正在依赖项中的包才能访问。 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"