Pular para o conteúdo principal
Versão: 8.x

Symlinked `node_modules` structure

informação

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:

  1. permitir que os pacotes se importem a si mesmos. O pacote foo deve ser capaz de fazer require('foo/package.json') ou importar * as package de "foo/package.json".
  2. 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 outro node_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).