npm, yarn o pnpm: quale gestore scegliere
npm, yarn o pnpm a confronto: come organizzano node_modules, velocità, file di lock, monorepo e sicurezza. Quale gestore di pacchetti scegliere e perché.
Apri un progetto trovato online e nelle istruzioni c'è scritto pnpm install. Tu hai sempre usato npm. Cambia qualcosa? E perché nella cartella ci sono tre file di lock diversi? Sono domande che sembrano da dettaglio finché non ti bloccano per un pomeriggio. In questo articolo trovi cosa fa davvero un gestore di pacchetti, perché il file di lock è la cosa più importante di tutte, le differenze concrete tra i tre e una risposta onesta su quale scegliere.
Cos'è un gestore di pacchetti
Un gestore di pacchetti è lo strumento che scarica le librerie di cui il tuo progetto ha bisogno, insieme a tutte quelle di cui hanno bisogno loro, e le tiene alla versione giusta.
Sembra poco, ma il lavoro nascosto è parecchio. Quando installi un pacchetto, quello ne richiede altri cinque, che a loro volta ne richiedono venti: sono le dipendenze transitive. Il gestore risolve l'intero albero, decide quale versione soddisfa tutti i vincoli e sistema il risultato dentro node_modules.
Il tutto ruota attorno a due file:
package.json— quello che scrivi tu: le dipendenze che vuoi, con intervalli di versione ("react": "^19.0.0"significa "la 19.0.0 o qualunque aggiornamento compatibile").package-lock.json(oyarn.lock, opnpm-lock.yaml) — quello che genera il gestore: le versioni esatte che sono state effettivamente installate, per ogni singolo pacchetto dell'albero.
npm install react # aggiunge a package.json e al lock
npm install -D typescript # dipendenza di sviluppo
npm ci # installa ESATTAMENTE dal lock
Il file di lock, e l'errore che costa di più
Ecco la parte che vale il prezzo dell'articolo intero.
Il file di lock va sempre committato. Sempre, senza eccezioni, su ogni progetto, anche quello da solo.
Il motivo sta in quel ^ davanti alla versione. "^19.0.0" autorizza il gestore a installare anche la 19.3.1 se nel frattempo è uscita. Se il lock non è versionato, tu installi a gennaio e il tuo collega a marzo: ottenete due alberi di dipendenze diversi dallo stesso package.json. Il progetto funziona da te e si rompe da lui.
È l'origine più frequente del classico "sulla mia macchina funziona". Il codice è lo stesso, le librerie no.
Da qui due regole pratiche:
- In locale usa
install, in CI usanpm ci(oyarn install --frozen-lockfile, opnpm install --frozen-lockfile). Il comandociignora gli intervalli di versione, installa esattamente il lock e fallisce se il lock non è coerente con ilpackage.json: è il comportamento che vuoi in una pipeline. - Un solo gestore per progetto. Se nel repository trovi due lock diversi, qualcuno ha installato con lo strumento sbagliato e le due verità hanno iniziato a divergere. Cancella quello di troppo e mettilo nel
.gitignore.
Lo stesso vale per Docker: copiare il lock nell'immagine prima del resto del codice ti dà build riproducibili e la cache dei livelli che funziona davvero.
Le differenze vere tra i tre
Tolti i dettagli, la differenza sostanziale è come organizzano node_modules. Il resto discende da lì.
npm
È quello che arriva installato con Node, quindi non devi fare niente per averlo. Da anni ormai non è più lento e scomodo come lo ricordano in molti: ha una cache locale, il comando ci e il supporto ai workspace.
Organizza node_modules in modo piatto: tutti i pacchetti, anche quelli che nessuno ha chiesto direttamente, finiscono al primo livello. Nasce da un problema reale (evitare cartelle annidate all'infinito) ma ha un effetto collaterale importante, che vedi tra poco.
yarn
Nato nel 2016 per risolvere lentezza e mancanza di lock di npm, problemi che npm ha poi risolto. Yarn Classic (la versione 1) è ancora molto diffuso ma ormai in manutenzione; le versioni moderne introducono Plug'n'Play, un approccio che elimina del tutto node_modules — elegante, ma non tutti gli strumenti lo digeriscono senza configurazione.
Oggi ha senso se lavori su un progetto che già lo usa. Per un progetto nuovo, è quello con la motivazione più debole.
pnpm
È quello con l'idea tecnica più interessante. Invece di copiare i pacchetti dentro ogni progetto, li salva una volta sola in un archivio condiviso sul disco e nei node_modules mette dei collegamenti.
Due conseguenze concrete:
- Molto meno spazio occupato. Se hai dieci progetti che usano React 19, sul disco c'è una copia sola. La differenza si misura in gigabyte.
- Installazioni più rapide quando un pacchetto è già nell'archivio: non va scaricato né copiato, basta collegarlo.
La seconda caratteristica è il rigore. pnpm costruisce node_modules in modo che tu veda solo i pacchetti dichiarati nel tuo package.json.
// package.json dichiara solo "express"
// express al suo interno usa "debug"
import debug from "debug"; // npm: funziona. pnpm: non trovato.
Con npm quell'import funziona per puro caso, perché debug è finito nella cartella piatta. È una dipendenza fantasma: il giorno in cui express smette di usarlo, o cambia versione, il tuo codice si rompe senza che tu abbia toccato niente.
All'inizio sembra un fastidio, ed è invece un pregio: pnpm ti obbliga a dichiarare quello che usi, e ti fa scoprire subito un problema che con npm salta fuori tra sei mesi, nel momento peggiore.
Il confronto
| npm | yarn | pnpm | |
|---|---|---|---|
| Installazione | Incluso in Node | Da installare | Da installare |
| File di lock | package-lock.json | yarn.lock | pnpm-lock.yaml |
| node_modules | Piatto, con copie | Piatto, con copie | Collegamenti a un archivio condiviso |
| Spazio su disco | Alto | Alto | Nettamente il più basso |
| Velocità | Buona | Buona | La migliore sui progetti già visti |
| Dipendenze fantasma | Permesse | Permesse | Bloccate |
| Monorepo | Workspace di base | Buono | Il più solido |
| Compatibilità | Totale, è lo standard | Molto buona | Molto buona, rari casi da sistemare |
I monorepo
Se tieni più pacchetti in un solo repository — per esempio un frontend, un backend e una libreria di componenti condivisa — entrano in gioco i workspace: un unico install alla radice, dipendenze condivise, e i pacchetti interni che si vedono tra loro senza doverli pubblicare.
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
pnpm --filter web add react # installa solo in apps/web
pnpm -r build # esegue build in tutti i pacchetti
Tutti e tre supportano i workspace, ma pnpm è quello che regge meglio quando i pacchetti diventano molti, anche grazie al rigore di cui sopra: in un monorepo le dipendenze fantasma si moltiplicano, e lì fanno danni seri.
Sicurezza: la parte che si trascura
Installare un pacchetto significa eseguire codice scritto da sconosciuti, insieme a quello di tutte le sue dipendenze transitive. Un progetto medio ne porta dentro centinaia.
npm audit è il primo controllo, e funziona in modo equivalente con tutti e tre:
npm audit # elenca le vulnerabilità note
npm audit --production # solo ciò che finisce in produzione
npm audit fix # aggiorna dove si può senza rompere
npm outdated # cosa è rimasto indietro
Tre avvertenze che evitano perdite di tempo e falsi allarmi:
- Non farti prendere dal panico dai numeri. Molte segnalazioni riguardano strumenti di sviluppo che non finiscono nel prodotto finale, o percorsi di codice che non usi. Guarda la gravità e se la dipendenza è di produzione, non il totale.
- Evita
npm audit fix --force. Aggiorna a versioni con cambiamenti incompatibili e rompe il progetto. Se lo usi, fallo su un branch e con i test che girano. - I pacchetti abbandonati sono il rischio più sottovalutato. Una libreria senza commit da tre anni non riceverà una correzione quando emergerà un problema, e un pacchetto popolare ma trascurato è un bersaglio: sono già successi casi di manutentori che hanno ceduto il controllo a chi ha poi pubblicato codice malevolo. Prima di aggiungere una dipendenza, guarda data dell'ultimo rilascio, problemi aperti e quante persone la mantengono — e chiediti se quelle trenta righe non convenga scriverle.
Una difesa semplice e sottovalutata: meno dipendenze installi, meno superficie esponi. Il tema più ampio è in cos'è la cybersecurity.
Una nota su npx, che esegue un pacchetto senza installarlo: comodo, ma scarica ed esegue codice al volo. Controlla il nome prima di premere invio: un carattere sbagliato può portarti su un pacchetto scritto apposta per somigliare a quello vero.
Cosa fa perdere ore
Le situazioni che vedo più spesso, e la via d'uscita:
- Cancellare
node_modulesal primo problema. A volte serve, ma prima provanpm cache clean --force. E cancella il lock solo se hai deciso di rigenerarlo: sono due gesti con conseguenze diverse. - Mettere il lock nel
.gitignore. Capita per abitudine, copiando un.gitignoreda un altro linguaggio. È il modo più veloce per avere build non riproducibili. - Versioni di Node diverse tra macchine. Alcuni pacchetti si compilano durante l'installazione e falliscono su una versione diversa. Fissa la versione nel campo
enginese usa nvm o fnm. - Confondere
dependenciesedevDependencies. Ciò che serve solo per costruire il progetto (TypeScript, test, linter) va indevDependencies. Sbagliarlo gonfia l'immagine di produzione.
Quale scegliere: la risposta onesta
Per un progetto piccolo o medio, con una manciata di dipendenze, la differenza non si sente. Parliamo di qualche secondo in installazione e di qualche centinaio di megabyte. Se usi npm e funziona, non hai nessun motivo per cambiare: cambiare strumento non risolve nessun problema che avevi.
Il mio consiglio pratico:
- npm se stai iniziando, se il progetto è piccolo, o se vuoi zero attriti con tutorial e guide — che danno tutte per scontato npm.
- pnpm se hai molti progetti sulla stessa macchina (il risparmio di spazio è reale), se lavori su un monorepo, o se vuoi il rigore sulle dipendenze. È la scelta che consiglio per un progetto nuovo che dovrà crescere.
- yarn se il progetto lo usa già. Non è una cattiva scelta, ma oggi non c'è un motivo forte per adottarlo da zero.
Il passaggio da uno all'altro è meno drammatico di quanto sembri: cancelli il vecchio lock e node_modules, lanci l'install con il nuovo gestore, verifichi che la build passi. Falla su un branch: è proprio nel cambio che emergono le dipendenze fantasma nascoste.
In sintesi
Un gestore di pacchetti scarica le librerie del progetto, risolve l'intero albero delle dipendenze e ne fissa le versioni.
Le cose che contano davvero: il file di lock va sempre committato — è la differenza tra un progetto riproducibile e il "sulla mia macchina funziona"; in CI usa npm ci e non install; tieni un solo gestore per progetto; e controlla le dipendenze prima di aggiungerle, perché ognuna è codice altrui che eseguirai.
La differenza tecnica vera è che pnpm collega i pacchetti a un archivio condiviso invece di copiarli: meno spazio, installazioni più rapide e niente dipendenze fantasma.
E la parte onesta: su un progetto piccolo npm basta e avanza. La scelta del gestore è tra le decisioni meno importanti che prenderai, e ha molto più impatto quello che ci metti dentro.
Per il quadro generale di cosa installi e perché, vedi cos'è una libreria e cos'è un framework. Per il linguaggio che quasi certamente userai con questi strumenti, vedi cos'è TypeScript e perché usarlo.