Struttura `node_modules` con collegamenti simbolici
Questo articolo descrive solo come è strutturata la cartella node_modules
di pnpm quando non ci sono pacchetti con dipendenze peer. Per lo scenario più complesso di dipendenze con i peer, vedere come vengono risolti i peer.
il layout node_modules
pnpm utilizza collegamenti simbolici per creare una struttura nidificata di dipendenze.
Ogni file di ogni pacchetto all'interno di node_modules
è un collegamento fisico all'archivio indirizzabile al contenuto. Diciamo che installi foo@1.0.0
che dipende da bar@1.0.0
. pnpm collegherà entrambi i pacchetti a node_modules
in questo modo:
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
Questi sono gli unici file "reali" in node_modules
. Una volta che tutti i pacchetti sono collegati fisicamente a node_modules
, vengono creati collegamenti simbolici per creare la struttura del grafico delle dipendenze annidata.
Come potresti aver notato, entrambi i pacchetti sono collegati fisicamente in una sottocartella all'interno di una cartella node_modules
(foo@1.0.0/node_modules/foo
). Ciò è necessario per:
- permettere ai pacchetti di importarsi.
foo
dovrebbe essere in grado di eseguirerequire('foo/package.json')
oimport * as package from "foo/package.json"
. - evitare i collegamenti simbolici circolari. Le dipendenze dei pacchetti sono posizionate nella stessa cartella in cui si trovano i pacchetti dipendenti. Per Node.js non fa differenza se le dipendenze si trovano all'interno di
node_modules
del pacchetto o in qualsiasi altronode_modules
delle cartelle superiori.
La fase successiva dell'installazione è il collegamento simbolico delle dipendenze. bar
sarà collegato simbolicamente alla cartella foo@1.0.0/node_modules
:
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
Successivamente, vengono gestite le dipendenze dirette. pippo
verrà collegato simbolicamente nella cartella root node_modules
perché foo
è una dipendenza del progetto:
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
Questo è un esempio molto semplice. Tuttavia, il layout manterrà questa struttura indipendentemente dal numero di dipendenze e dalla profondità del grafico delle dipendenze.
Aggiungiamo qar@2.0.0
come dipendenza di bar
e foo
. Ecco come apparirà la nuova struttura:
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
Come puoi vedere, anche se il grafico ora è più profondo (pippo > bar > qar
), la cartella nel file system è sempre la stessa.
Questo layout potrebbe sembrare strano a prima vista, ma è completamente compatibile con l'algoritmo di risoluzione del modulo di Node! Quando risolve i moduli, Node ignora i collegamenti simbolici, quindi quando è richiesto bar
da foo@1.0.0/node_modules/foo/index.js
, Node non usa bar
da foo@1.0.0/node_modules/bar
, ma invece bar
è risolto nella sua posizione reale (bar@1.0.0/node_modules/bar
). Di conseguenza, bar
può anche risolvere le sue dipendenze che sono in bar@1.0.0/node_modules
.
Un grande bonus di questo layout è che solo i pacchetti che sono davvero nelle dipendenze sono accessibili. Con una struttura node_modules
appiattita, tutti i pacchetti installati sono accessibili. Per saperne di più sul perché questo è un vantaggio, vedi "la severità di pnpm aiuta ad evitare bug stupidi"