シンボリックリンクを使用した `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.jsのモジュール解決アルゴリズムと完全に互換性のある構造なのです。 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"