Fissare la versione di Node.js con pnpm, senza nvm - init.d
EN

# Fissare la versione di Node.js con pnpm, senza nvm

Alessandro Corbelli~5 min
Table of Contents

“Sulla mia macchina funziona.” Una buona parte delle volte, dietro questa frase c’è una versione diversa di Node.js: il progetto è nato sulla 20, il collega ha la 24, la CI usa un’altra ancora. Lo strumento classico per conviverci è nvm, che permette di installare più versioni e passare dall’una all’altra. Funziona, ma va installato e configurato da ogni persona del team, e ricordarsi di digitare nvm use al momento giusto resta un onere. Chi usa pnpm come gestore di pacchetti ha un’alternativa spesso trascurata: pnpm sa gestire anche la versione di Node.js, e sa farlo per progetto, senza strumenti aggiuntivi.

Il problema, in due parole

Node.js è l’ambiente che esegue il JavaScript fuori dal browser; ogni progetto è di fatto testato e garantito solo su certe versioni. nvm (Node Version Manager) affronta il problema installando più versioni fianco a fianco: nvm install 20, nvm use 20 e la shell corrente passa alla 20.

I limiti sono noti a chi lo usa ogni giorno. nvm è uno script di shell che va installato per utente e agganciato al profilo della shell; il cambio di versione vale per la sessione corrente, quindi va ripetuto (o automatizzato con hook) a ogni cartella; nelle pipeline di integrazione continua e nei container si usa tipicamente tutt’altro meccanismo, con il rischio di divergenze. Non è un difetto di nvm: è il prezzo di uno strumento separato dal progetto.

L’alternativa: il runtime dichiarato nel progetto

pnpm ribalta l’approccio: la versione di Node.js diventa una dichiarazione del progetto, scritta nel package.json e versionata insieme al codice. Il campo si chiama devEngines:

{
"devEngines": {
"runtime": {
"name": "node",
"version": "20.19.0",
"onFail": "download"
}
}
}

La chiave è onFail: "download": se il Node in uso non corrisponde a quello dichiarato, pnpm scarica da solo la versione richiesta (una volta sola, poi resta in cache) e la usa per il progetto, senza toccare il Node di sistema. Il runtime compare nell’output di pnpm install come una dipendenza qualsiasi:

Terminal window
$ pnpm install
# ...
+ node 20.19.0

E da quel momento tutto ciò che passa da pnpm usa quella versione. Un esempio provato mentre scrivevamo l’articolo, su una macchina con Node 22 di sistema:

Terminal window
$ node -v # il Node di sistema
v22.22.2
$ pnpm run check-node # uno script che esegue "node -v"
> node -v
v20.19.0 # la versione dichiarata nel progetto
$ pnpm node -v
v20.19.0

Nessun nvm use, nessun hook di shell: chiunque cloni il repository - collega nuovo, CI, container - ottiene la stessa versione di Node semplicemente usando pnpm.

Anche a intervalli, e bloccato nel lockfile

version accetta sia una versione esatta sia un intervallo semver. Con "version": "^20.11.0", nel nostro test pnpm ha risolto e scaricato l’ultima 20.x disponibile:

Terminal window
$ pnpm install
# ...
+ node 20.20.2

Il dettaglio più interessante è che la versione risolta finisce nel lockfile (pnpm-lock.yaml), come una normale dipendenza:

node:
specifier: runtime:^20.11.0
version: runtime:20.20.2

Questo significa che tutto il team e la CI usano esattamente la stessa versione di Node, non solo “una versione compatibile”: la riproducibilità è la stessa che il lockfile garantisce alle librerie. Il classico campo engines, per confronto, si limita ad avvisare quando la versione non corrisponde; devEngines con onFail: "download" risolve il problema da solo.

I limiti da conoscere

Il meccanismo vale per ciò che passa da pnpm: pnpm run, pnpm exec, pnpm node, gli script del package.json. Se si digita node server.js direttamente nel terminale, si usa ancora il Node di sistema. Nella pratica è un limite più piccolo di quanto sembri - nei progetti JavaScript quasi tutto passa dagli script - ma è bene saperlo: devEngines non “cambia il Node del computer”, garantisce che il progetto giri sulla versione giusta.

Una nota per chi arriva da versioni precedenti: fino a pnpm 10 esisteva l’impostazione use-node-version (o useNodeVersion nel pnpm-workspace.yaml), che faceva una cosa simile. pnpm 11 l’ha rimossa insieme a executionEnv.nodeVersion: la strada supportata oggi è devEngines. Se un progetto usa ancora quelle chiavi, con pnpm 11 vengono semplicemente ignorate - un aggiornamento facile da dimenticare.

E al posto di nvm per tutto il sistema?

pnpm può sostituire nvm anche come gestore globale delle versioni, con il comando pnpm env:

Terminal window
pnpm env list 20 # quali versioni 20.x esistono
pnpm env use --global 20 # installa e attiva l'ultima 20.x
pnpm env use --global lts # oppure l'ultima LTS

Qui serve una precisazione onesta: pnpm env gestisce il collegamento globale a Node, quindi richiede che pnpm sia installato in modo autonomo (con lo script di installazione ufficiale o con Corepack), non tramite npm install -g pnpm - in quel caso pnpm girerebbe dentro il Node che sta cercando di sostituire. Per l’uso per-progetto visto sopra, invece, questa distinzione non conta.

In sintesi

Se un progetto usa già pnpm, fissare la versione di Node.js non richiede nvm: un blocco devEngines nel package.json, con onFail: "download", fa scaricare e usare a pnpm il runtime dichiarato - versione esatta o intervallo - e blocca nel lockfile quella effettivamente risolta, uguale per tutto il team e per la CI. Il limite è chiaro (vale per i comandi che passano da pnpm), e chi arriva da pnpm 10 deve migrare da use-node-version a devEngines. nvm resta utile come gestore generale sulla propria macchina; per la riproducibilità di un progetto, la versione dichiarata nel repository è la strada più solida.

Fonti

Tux versione Gandalf, mascotte del blog init.d

init.d è il team di Alessandro Corbelli, sistemista Linux e sviluppatore backend con oltre vent’anni di esperienza. Progetta e gestisce infrastrutture cloud (Google Cloud, AWS, Azure), server farm e architetture ad alta disponibilità, e sviluppa software su misura in Laravel/PHP e Vue - dalla piattaforma di food delivery Take2Me ai gestionali dei nostri clienti. Su questo blog condividiamo note tecniche su Linux, sistemistica, sviluppo, DevOps ed e-commerce.


Altri articoli