symlink된 `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의 모듈 resolution 알고리즘과 완벽하게 호환됩니다. 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"