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 :
- MAJEURE quand un changement casse la rétrocompatibilité,
- MINEURE quand on ajoute des fonctionnalités rétrocompatibles,
- 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.3fige le 1,^0.2.3fige le 2,^0.0.3fige 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
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
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
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
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
Le dist-tag : latest
Point important :
latestn’est pas une règle SemVer. C’est un dist-tag npm.
Exemple :
"shiki": "latest"
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-*
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
- Tableau officiel npm sur le SemVer et les ranges : https://docs.npmjs.com/about-semantic-versioning
- Calculateur de ranges npm : https://semver.npmjs.com/
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)