DEV Community

Cover image for Guide du SemVer avec npm
Benjamin Auzanneau
Benjamin Auzanneau

Posted on • Originally published at 616.earth

Guide du SemVer avec npm

Combien de fois avons-nous vu un ^ ou un ~ devant une version d'une lib dans un package.json sans être totalement sûrs de ce que cela fait réellement ?

On va essayer d'y voir plus clair dans cet article.

Initialement, j'avais rédigé cet article en juillet 2021.

Il comportait une petite erreur sur l'utilisation du caret. J'en ai donc profité pour le corriger et l'enrichir, notamment avec une section sur le package-lock.json.

Le SemVer, c’est quoi ?

Le SemVer (pour Semantic Versioning) est une convention de versionnement qui donne du sens aux numéros de version.

Si l'on s'appuie sur le site officiel, on retrouve la forme :

MAJEURE.MINEURE.CORRECTIF

Selon la spécification, on incrémente :

  1. MAJEURE quand un changement casse la rétrocompatibilité,
  2. MINEURE quand on ajoute des fonctionnalités rétrocompatibles,
  3. CORRECTIF quand on corrige des bugs rétrocompatibles.

npm s’appuie sur ces principes (et sur une grammaire de plages de versions) pour décider quelles versions sont “acceptables” lors d’un npm install.

Pour aller plus loin, n’hésitez pas à consulter semver.org.

Les symboles dans le package.json

npm va venir enrichir ces notions avec des symboles.
Ce sont des plages de versions (ranges). Elles indiquent ce que le projet accepte comme mises à jour, notamment lors des npm install.

Le symbole ^ : “jusqu’à la prochaine release majeure”

Règle npm : le caret autorise les mises à jour tant que le premier chiffre non nul reste identique (ex. ^1.2.3 fige le 1, ^0.2.3 fige le 2, ^0.0.3 fige le 3).

Exemples :

"react": "^1.2.3", // >=1.2.3 <2.0.0
"shiki": "^0.2.3", // >=0.2.3 <0.3.0
"tslib": "^0.0.3", // >=0.0.3 <0.0.4
Enter fullscreen mode Exit fullscreen mode

Sur les versions 0.x, le caret est plus restrictif.

Dans notre exemple, ^0.2.3 bloque le passage à tout ce qui est supérieur ou égal à 0.3.0.

Le symbole ~ : “patchs uniquement”

Le tilde autorise des changements au niveau correctif, tant que la mineure ne bouge pas :

"shiki": "~1.2.3", // >=1.2.3 <1.3.0
"tslib": "~0.2.3", // >=0.2.3 <0.3.0
Enter fullscreen mode Exit fullscreen mode

Les symboles >, >=, <, <=, = : comparateurs

Ces opérateurs fonctionnent comme des comparateurs classiques :

"react": ">1.2.3",  // strictement supérieur
"shiki": ">=1.2.3", // supérieur ou égal
"tslib": "<2.0.0",  // strictement inférieur
"axios": "<=2.0.0", // inférieur ou égal
"redux": "1.2.3",   // (ou `=1.2.3`) version exacte
Enter fullscreen mode Exit fullscreen mode

Le symbole - : inclusif

Un intervalle avec tiret est inclusif sur les bornes :

"tslib": "1.2.3 - 2.3.4", // >=1.2.3 <=2.3.4
"shiki": "0.2.0 - 0.4.2", // accepte 0.4.2 mais pas 0.4.3
Enter fullscreen mode Exit fullscreen mode

Le symbole || : combiner des ensembles

Un intervalle avec le double pipe combine les deux ensembles :

"tslib": "^0.2.0 || >=0.5.0 <1.2.0" // Compatible avec l'ensemble ^0.2.0 ou >=0.5.0 <1.2.0
Enter fullscreen mode Exit fullscreen mode

Le dist-tag : latest

Point important : latest n’est pas une règle SemVer. C’est un dist-tag npm.

Exemple :

"shiki": "latest"
Enter fullscreen mode Exit fullscreen mode

Par défaut, npm install <pkg> installe la version pointée par le tag latest (si on ne met ni <version> ni @<tag>).

Un mainteneur peut faire pointer latest vers une version qui n’est pas “la plus récente” au sens strict, et utiliser d’autres tags (beta, next, etc.).

Bonus : x, * et les pré-releases -alpha, -beta

Exemples :

"tslib": "1.2.x", // >=1.2.0 <1.3.0
"shiki": "1.2.*", // >=1.2.0 <1.3.0
"redux": ">=1.2.3-0 <1.3.0" // inclut les pré-releases >=1.2.3-*
Enter fullscreen mode Exit fullscreen mode

Les pré-releases (-alpha, -beta) ont des règles spécifiques : par défaut, elles sont exclues des ranges. Pour les inclure, on peut utiliser une borne avec -0 (ou une version pré-release) dans la range.

Quid du package-lock.json

package.json vs package-lock.json

Le package-lock.json est conçu pour être versionné dans le repo afin que les devs et la CI installent exactement les mêmes dépendances.

  • package.json décrit l’intention : dépendances + plages (^, ~, intervalles, etc.).
  • package-lock.json décrit l’état exact résolu : l’arbre complet des dépendances (y compris transitives), avec versions précises, sources de téléchargement (resolved) et empreintes (integrity).

Ce qu’on trouve dedans

Quelques champs utiles à connaître :

  • lockfileVersion : version du format.
  • packages : description des packages par chemin, avec resolved et integrity.

npm install vs npm ci

  • npm install : installe en s’appuyant sur package.json et le lockfile ; il peut mettre à jour le lockfile si nécessaire.
  • npm ci : installation suivant le package-lock.json, elle échoue si le package.json et le package-lock.json ne correspondent pas, supprime les node_modules avant de lancer l'installation et ne touche pas au package-lock.json.

Liens à garder sous la main

Conclusion

La première fois que j'ai rédigé cet article, j'avais essayé d'être le plus minimal possible.

Depuis, je me rends compte que c'est quand même plus sympa d'aller un peu plus loin dans les détails, pour bien comprendre le fonctionnement du versionnement avec npm et l'impact du package-lock.json.

J'ai rédigé cet article pour m'en faire un mémo, l'avoir toujours sous la main et le mettre dans mes favoris.

Vous pouvez en faire autant !


Merci d'avoir lu cet article !
Il a été posté initialement sur mon blog : https://616.earth/petit-rappel-du-semver-avec-npm

Top comments (0)