Menu

package.json: script, dipendenze e versioni

Cosa c'è dentro package.json: i campi che contano, come funzionano gli script e come gli intervalli semver decidono quali versioni installa npm.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Il file manifesto di un progetto Node

Ogni progetto Node.js ha un package.json nella cartella principale. È un semplice file JSON che descrive il progetto: nome, versione, da cosa dipende, quali comandi espone. Ed è ciò che npm legge ogni volta che fa qualcosa. Se lo cancelli, npm non ha idea di cosa sia il tuo progetto.

Il modo più veloce per crearne uno è npm init:

npm init -y

L'opzione -y salta le domande e accetta i valori predefiniti. Ottieni qualcosa del genere:

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Questo è lo scheletro di base. La maggior parte di questi campi da sola non fa granché: diventa utile man mano che aggiungi dipendenze e script.

Dependencies vs devDependencies

Due campi fanno quasi tutto il lavoro: dependencies e devDependencies. Entrambi associano nomi di pacchetti a intervalli di versioni.

La divisione conta per un motivo: le dependencies sono i pacchetti di cui il tuo codice ha bisogno per funzionare. Le devDependencies sono pacchetti che ti servono solo mentre sviluppi: test runner, linter, strumenti di build, controllori di tipi. Quando qualcuno installa il tuo pacchetto come propria dipendenza, npm scarica le tue dependencies e salta le devDependencies.

npm aggiorna questi campi in automatico. npm install express aggiunge una riga a dependencies. npm install --save-dev vitest ne aggiunge una a devDependencies. Raramente li modifichi a mano.

Intervalli di versione: ^, ~ ed esatta

Stringhe di versione come ^4.19.0 non sono versioni esatte, sono intervalli. npm segue semver, che divide le versioni in MAJOR.MINOR.PATCH:

  • Un aumento di MAJOR rompe la compatibilità con le versioni precedenti.
  • Un aumento di MINOR aggiunge funzionalità senza rompere nulla.
  • Un aumento di PATCH corregge bug.

I due operatori che vedrai ovunque:

"express": "^4.19.0"   // >= 4.19.0 and < 5.0.0  (any 4.x.x at or above 4.19.0)
"express": "~4.19.0"   // >= 4.19.0 and < 4.20.0 (any 4.19.x at or above 4.19.0)
"express": "4.19.0"    // exactly 4.19.0

^ è il default che npm usa quando installi un pacchetto. Si fida del fatto che gli aumenti minor e patch restino compatibili. ~ è più prudente: solo aggiornamenti patch. Una versione senza simboli la fissa in modo esatto.

Il tranello: "quello che ho appena installato" e "quello che l'intervallo consente" non sono la stessa cosa. Se oggi installi express@4.19.0 e tra un mese un collega installa il tuo progetto, ^4.19.0 potrebbe risolversi in 4.19.5. Qui entra in gioco package-lock.json: registra le versioni esatte che sono state risolte, così tutti ottengono lo stesso albero. Fai il commit.

Script: i comandi del tuo progetto

Il campo scripts è dove definisci scorciatoie per i comandi più comuni. Tutto ciò che ci metti si può lanciare con npm run <nome>:

Alcune cose da sapere sugli script:

  • npm start, npm test e pochi altri nomi funzionano senza la parola run. Tutto il resto richiede npm run <nome>.
  • Gli script vengono eseguiti in una shell con node_modules/.bin nel PATH, quindi puoi chiamare direttamente gli eseguibili dei pacchetti installati. "test": "vitest" funziona anche se vitest non è installato globalmente.
  • Puoi concatenare gli script: "build": "npm run lint && npm run compile". Usa && per dire "esegui in sequenza e fermati se qualcosa fallisce".
  • Gli script pre<nome> e post<nome> partono in automatico. Se hai prebuild, viene eseguito prima di build senza altra configurazione.

Gli script sono l'interfaccia a riga di comando del progetto. Un buon package.json permette a chi arriva nel progetto di clonarlo, lanciare npm install e poi npm run dev / npm test senza leggere nessuna wiki.

Punti di ingresso: main, exports, type

Questi campi dicono a Node (e ai bundler) come caricare il tuo pacchetto.

  • type decide come vengono interpretati i file .js. "module" significa ESM (import / export). Omettilo o imposta "commonjs" per CommonJS (require). Trovi tutti i dettagli nella pagina su CommonJS vs ESM.
  • main è il punto di ingresso storico: ciò a cui si risolve require("my-lib"). Gli strumenti più vecchi lo rispettano ancora.
  • exports è il sostituto moderno e più rigido. Definisce esattamente quali file possono importare gli utilizzatori e sotto quale sottopercorso. Se un file non è elencato qui, importarlo fallisce, ed è una funzionalità, non un bug: sei tu a controllare l'API pubblica.

Se stai solo costruendo un'app (e non pubblicando un pacchetto), probabilmente l'unico di questi campi che ti interessa è type.

Un package.json realistico

Mettendo tutto insieme, ecco più o meno come appare nella pratica il package.json di una piccola app Node:

Nota engines.node. È indicativo: npm mostra un avviso (o un errore con engine-strict) se la versione di Node dell'utente non corrisponde. Buona abitudine per tutto ciò che pubblichi.

Altri campi da conoscere

Qualche altro campo in cui ti imbatterai:

  • private: true: ti impedisce di pubblicare per sbaglio il pacchetto su npm. Impostalo in ogni progetto che non deve essere pubblicato.
  • license: un identificatore SPDX come "MIT" o "ISC". Conta per tutto ciò che è pubblico.
  • repository, bugs, homepage: vengono mostrati nella pagina del pacchetto sul registro npm.
  • bin: se il tuo pacchetto include una CLI, qui associ i nomi dei comandi ai file di script. Dopo l'installazione quei comandi diventano eseguibili.
  • workspaces: per i monorepo; dice a npm di trattare alcune sottocartelle come pacchetti collegati.

Non ti servono tutti. Ti servono quelli giusti per ciò che stai facendo.

Errori comuni

Alcune cose su cui si inciampa spesso:

  • Fare il commit di node_modules. Non farlo. Aggiungilo a .gitignore. package.json più package-lock.json bastano a chiunque per ricostruirlo con npm install.
  • Non fare il commit di package-lock.json. Va incluso. Senza il lockfile, il classico "sul mio computer funziona" diventa un rischio concreto, perché gli intervalli semver possono risolversi in versioni diverse nel tempo.
  • Mettere dipendenze di runtime in devDependencies. In locale l'app potrebbe funzionare perché le dipendenze di sviluppo sono installate, per poi rompersi in produzione dove vengono saltate. Se il codice che distribuisci lo usa, va in dependencies.
  • Modificare le versioni a mano senza reinstallare. Cambia una versione in package.json e lancia npm install, altrimenti node_modules e il lockfile vanno fuori sincrono.

Prossimo passo: il runtime di Node

package.json dice a Node cosa è il tuo progetto. Il runtime di Node decide come viene eseguito: risoluzione dei moduli, moduli integrati, variabili globali, l'event loop dietro le quinte. È l'argomento della prossima pagina.

Domande frequenti

A cosa serve package.json?

È il file manifesto di un progetto Node.js. Registra nome e versione del progetto, i pacchetti da cui dipende, gli script che puoi lanciare con npm run e metadati come il punto di ingresso e il tipo di modulo. npm install lo legge per decidere cosa scaricare.

Che differenza c'è tra dependencies e devDependencies?

Le dependencies sono i pacchetti di cui il tuo codice ha bisogno a runtime, come express o react. Le devDependencies servono solo durante lo sviluppo o la build: test runner, bundler, linter. Quando qualcuno installa il tuo pacchetto come dipendenza del proprio, npm salta le tue devDependencies.

Cosa significano ^ e ~ nelle versioni di package.json?

Sono operatori di intervallo semver. ^1.2.3 accetta qualsiasi versione 1.x.x uguale o superiore a 1.2.3 (stessa major). ~1.2.3 è più rigido: accetta 1.2.x uguale o superiore a 1.2.3 (stessa minor). Un semplice 1.2.3 fissa la versione esatta. package-lock.json registra le versioni esatte risolte, così le installazioni restano riproducibili.

Come creo un file package.json?

Lancia npm init in una cartella vuota e rispondi alle domande, oppure usa npm init -y per accettare i valori predefiniti e ottenere subito il file. Puoi anche scriverlo a mano: è solo JSON. Gli unici campi davvero obbligatori sono name e version.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA