Passa al contenuto principale
Versione: Prossimo

Struttura `node_modules` con collegamenti simbolici

info

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:

  1. permettere ai pacchetti di importarsi. foo dovrebbe essere in grado di eseguire require('foo/package.json') o import * as package from "foo/package.json".
  2. 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 altro node_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"