Symlinked `node_modules` structure
Este artigo descreve apenas como é estruturado a node_modules
do pnpm quando não há pacotes com peer dependencies. Para cenários mais complexos com peer dependencies, veja como as peer dependencies são resolvidas..
o layout da node_modules
do pnpm utiliza links simbólicos para criar uma estrutura aninhada de dependências.
Cada arquivo de cada pacote dentro da node_modules
é um link físico para o conteúdo da store. Vamos supor que você instale o pacote foo@1.0.0
, que depende do pacote bar@1.0.0
. O pnpm criará links físicos para ambos os pacotes em node_modules
da seguinte forma:
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
Esses são os únicos arquivos "reais" na node_modules
. Depois que todos os pacotes são linkados fisicamente na node_modules
, links simbólicos são criados para construir a estrutura de grafo de dependências aninhadas.
Como você deve ter percebido, ambos os pacotes são linkados fisicamente em uma subpasta dentro de node_modules
(foo@1.0.0/node_modules/foo
). Isso é necessário para:
- permitir que os pacotes se importem a si mesmos. O pacote
foo
deve ser capaz de fazerrequire('foo/package.json')
ouimportar * as package de "foo/package.json"
. - evitar links simbólicos circulares. As dependências dos pacotes são colocadas na mesma pasta em que os pacotes dependentes estão. Para o Node.js, não faz diferença se as dependências estão dentro do
node_modules
do pacote ou em qualquer outronode_modules
nos diretórios pai.
A próxima etapa da instalação é criar links simbólicos para as dependências. O pacote bar
será linkado para a pasta foo@1.0.0/node_modules
da seguinte forma:
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
Em seguida, as dependências diretas são tratadas. O pacote foo
será linkado para a pasta node_modules
na raiz do projeto, pois o foo
é uma dependência do projeto:
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
Este é um exemplo muito simples. No entanto, o layout manterá essa estrutura independentemente do número de dependências e da profundidade do grafo de dependências.
Vamos adicionar qar@2.0.0
como uma dependência de bar
e foo
. A nova estrutura ficará assim:
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
Como você pode ver, mesmo com um grafo mais profundo agora (foo > bar > qar
), a profundidade dos diretórios no sistema de arquivos continua a mesma.
Este layout pode parecer estranho à primeira vista, mas é totalmente compatível com o algoritmo de resolução de módulos do Node! Ao resolver módulos, o Node ignora links simbólicos, portanto, quando o bar
é exigido a partir de foo@1.0.0/node_modules/foo/index.js
, o Node não usa o bar
em foo@1.0.0/node_modules/bar
, mas sim, o bar
em sua localização real (bar@1.0.0/node_modules/bar
). Como consequência, o bar
também pode resolver suas dependências que estão em bar@1.0.0/node_modules
.
Uma grande vantagem desse layout é que apenas os pacotes que realmente estão nas dependências são acessíveis. Com uma estrutura achatada de node_modules
, todos os pacotes elevados são acessíveis. Para saber mais sobre por que isso é uma vantagem, veja "A rigidez do pnpm ajuda a evitar bugs bobos" (tradução livre).