Menu
Français

package.json : scripts, dépendances et versions

Tout ce qu'il faut savoir sur package.json : les champs vraiment utiles, le fonctionnement des scripts et comment les plages semver décident des versions installées par npm.

Le fichier manifeste d'un projet Node

Tout projet Node.js possède un package.json à sa racine. C'est un simple fichier JSON qui décrit le projet — son nom, sa version, ses dépendances, les commandes qu'il expose — et c'est ce que lit npm à chaque fois qu'il fait quoi que ce soit. Si tu le supprimes, npm n'a plus aucune idée de ce qu'est ton projet.

Le moyen le plus rapide d'en créer un, c'est npm init :

npm init -y

L'option -y passe outre les questions interactives et applique les valeurs par défaut. On obtient alors quelque chose de ce genre :

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

Voilà le squelette minimal. La plupart de ces champs ne servent pas à grand-chose tels quels — ils prennent tout leur sens au fur et à mesure que vous ajoutez des dépendances et des scripts.

dependencies vs devDependencies

Deux champs font l'essentiel du boulot : dependencies et devDependencies. Les deux associent un nom de paquet à une plage de versions.

index.js
Output
Click Run to see the output here.

La distinction a son importance pour une raison précise : les dependencies sont les paquets dont ton code a besoin pour tourner. Les devDependencies, elles, ne servent que pendant le développement — frameworks de tests, linters, outils de build, vérificateurs de types. Quand quelqu'un installe ton paquet comme dépendance du sien, npm récupère tes dependencies et ignore tes devDependencies.

npm met ces champs à jour tout seul. npm install express ajoute une ligne dans dependencies. npm install --save-dev vitest en ajoute une dans devDependencies. Tu auras rarement à les modifier à la main.

Plages de versions : ^, ~ et version exacte

Les chaînes de version comme ^4.19.0 ne désignent pas une version précise — ce sont des plages. npm s'appuie sur le semver, qui découpe chaque version en MAJOR.MINOR.PATCH :

  • MAJOR : incrémenté lors d'un changement cassant la compatibilité.
  • MINOR : incrémenté quand on ajoute des fonctionnalités, sans rien casser.
  • PATCH : incrémenté pour les corrections de bugs.

Les deux opérateurs que tu croiseras partout :

"express": "^4.19.0"   // >= 4.19.0 et < 5.0.0  (tout 4.x.x supérieur ou égal à 4.19.0)
"express": "~4.19.0"   // >= 4.19.0 et < 4.20.0 (tout 4.19.x supérieur ou égal à 4.19.0)
"express": "4.19.0"    // exactement 4.19.0

^ est la valeur par défaut que npm utilise quand tu installes un paquet. Il fait confiance aux montées de version mineures et correctives pour rester compatibles. ~ est plus prudent : uniquement les correctifs. Une version écrite telle quelle, sans préfixe, épingle la version exacte.

Le piège : « ce que je viens d'installer » et « ce que la plage autorise », ce n'est pas la même chose. Si tu installes express@4.19.0 aujourd'hui et qu'un collègue installe ton projet dans un mois, ^4.19.0 pourrait se résoudre en 4.19.5. C'est précisément là qu'intervient package-lock.json : il enregistre les versions exactes qui ont été résolues, pour que tout le monde obtienne le même arbre de dépendances. Commite-le.

Les scripts npm : la surface de commandes de ton projet

Le champ scripts te permet de définir des raccourcis pour les commandes courantes. Tout ce que tu y mets peut être lancé avec npm run <nom> :

index.js
Output
Click Run to see the output here.

Quelques points à retenir sur les scripts :

  • npm start, npm test et quelques autres noms fonctionnent sans le mot-clé run. Pour tout le reste, il faut passer par npm run <nom>.
  • Les scripts s'exécutent dans un shell qui a node_modules/.bin dans le PATH, ce qui permet d'appeler directement les binaires des paquets installés. "test": "vitest" fonctionne même si vitest n'est pas installé globalement.
  • On peut enchaîner les scripts : "build": "npm run lint && npm run compile". Le && signifie « exécute en séquence, arrête-toi à la première erreur ».
  • Les scripts pre<nom> et post<nom> se déclenchent automatiquement. Si tu as un prebuild, il sera lancé avant build sans rien avoir à configurer.

Les scripts, c'est la porte d'entrée du projet en ligne de commande. Un bon package.json permet à un nouveau contributeur de cloner le dépôt, lancer npm install, puis npm run dev ou npm test sans avoir à consulter le moindre wiki.

Points d'entrée : main, exports et type

Ces champs indiquent à Node (et aux bundlers) comment charger ton paquet.

index.js
Output
Click Run to see the output here.
  • type détermine comment les fichiers .js sont interprétés. "module" active l'ESM (import / export). Si tu l'omets ou que tu mets "commonjs", c'est CommonJS (require) qui prend le relais. Pour tous les détails, va voir la doc CommonJS vs ESM.
  • main est le point d'entrée historique — c'est ce que résout require("my-lib"). Les anciens outils s'appuient encore dessus.
  • exports est son remplaçant moderne, beaucoup plus strict. Il définit précisément quels fichiers les consommateurs peuvent importer, et sous quels sous-chemins. Si un fichier n'est pas listé, l'import échoue — et c'est voulu, pas un bug. Tu gardes la main sur ton API publique.

Si tu développes simplement une application (sans publier de package), le seul champ qui compte vraiment pour toi ici, c'est type.

Un package.json réaliste

En mettant tout bout à bout, voici à quoi ressemble concrètement le package.json d'une petite appli Node :

index.js
Output
Click Run to see the output here.

Remarquez le champ engines.node. Il est purement indicatif : npm affiche un avertissement (ou carrément une erreur avec engine-strict) si la version de Node de l'utilisateur ne correspond pas. Une bonne habitude à prendre dès que vous publiez quelque chose.

Les champs à connaître

Quelques autres champs que vous croiserez tôt ou tard :

  • private: true — empêche la publication accidentelle du paquet sur npm. À activer sur tout projet qui n'a pas vocation à être publié.
  • license — un identifiant SPDX du genre "MIT" ou "ISC". Important pour tout projet public.
  • repository, bugs, homepage — ces champs s'affichent sur la page du registre npm.
  • bin — si votre paquet fournit une CLI, c'est ici que vous associez les noms de commande aux fichiers de script. Après installation, ces commandes deviennent directement exécutables.
  • workspaces — pour les monorepos ; indique à npm de considérer certains sous-dossiers comme des paquets liés entre eux.

Vous n'avez pas besoin de tout ça. Vous avez besoin de ce qui correspond à votre projet, point.

Les pièges classiques

Voici quelques trucs sur lesquels on se casse régulièrement les dents :

  • Versionner node_modules. Non, vraiment pas. Ajoutez-le à votre .gitignore. Un package.json accompagné du package-lock.json suffit largement pour que n'importe qui reconstruise l'arborescence avec npm install.
  • Oublier de versionner package-lock.json. Celui-là, par contre, il doit être dans Git. Sans le lockfile, le fameux « ça marche chez moi » devient une vraie menace, parce que les plages semver peuvent résoudre vers des versions différentes au fil du temps.
  • Mettre des dépendances runtime dans devDependencies. Votre appli peut très bien tourner en local — normal, les dev deps y sont installées — puis casser en production, où elles sont ignorées. Si le code que vous livrez en a besoin, sa place est dans dependencies.
  • Modifier les versions à la main sans réinstaller. Si vous changez une version dans package.json, lancez npm install dans la foulée — sinon node_modules et le lockfile finissent désynchronisés.

La suite : le runtime Node

Le package.json dit à Node ce qu'est votre projet. Le runtime Node, lui, décide comment il s'exécute : résolution des modules, modules natifs, variables globales, boucle d'événements en coulisses. C'est le sujet de la page suivante.

Questions fréquentes

À quoi sert le fichier package.json ?

C'est le manifeste d'un projet Node.js. Il contient le nom et la version du projet, la liste des paquets dont il dépend, les scripts que tu peux lancer avec npm run, ainsi que des métadonnées comme le point d'entrée et le type de module. Quand tu fais npm install, npm lit ce fichier pour savoir quoi télécharger.

Quelle est la différence entre dependencies et devDependencies ?

Les dependencies sont les paquets nécessaires à l'exécution de ton code — par exemple express ou react. Les devDependencies ne servent qu'en développement ou au build : runners de tests, bundlers, linters… Quand quelqu'un installe ton paquet comme dépendance de son propre projet, npm ignore tes devDependencies.

Que signifient ^ et ~ dans les versions de package.json ?

Ce sont des opérateurs de plage semver. ^1.2.3 autorise toute version 1.x.x supérieure ou égale à 1.2.3 (même version majeure). ~1.2.3 est plus strict : il accepte 1.2.x à partir de 1.2.3 (même mineure). Un 1.2.3 sans préfixe fige la version exacte. Le fichier package-lock.json, lui, enregistre les versions précisément résolues pour garantir des installations reproductibles.

Comment créer un fichier package.json ?

Place-toi dans un dossier vide et lance npm init pour répondre aux questions, ou npm init -y pour accepter les valeurs par défaut et obtenir le fichier immédiatement. Tu peux aussi l'écrire à la main — c'est du JSON classique. Les seuls champs réellement obligatoires sont name et version.

Apprendre à coder avec Coddy

COMMENCER