[#102] Post: careful about NPM versions

parent 5f9b1222
---
date: 2020-05-04T22:25:41+02:00
title: Attention aux subtilités de versions dans votre package.json
subtitle: Ce ne sont pas les modules que vous recherchez.
slug: subtilites-npm-package.json-version-modules
description: NPM et Node ont permis une nouvelle gestion des librairies et scripts, mais ses tildes et chevrons peuvent suffire à casser votre projet si vous n'y faites pas attention.
author: chop
categories:
- software-creation
tags:
- web-dev
- node-npm
---
Parfois, je ne comprends pas quelque chose, donc je cherche la réponse et il est naturel de la partager.
D'autres fois, je pense être suffisamment en retard pour que ce partage soit inutile, jusqu'à ce que des membres de mon équipe aient le même souci.
Ce billet figure dans la seconde catégorie.
Si vous utilisez régulièrement NPM, vous avez dû remarquer les tildes (`~`) et les chevrons (`^`) devant les numéros de version dans le `package.json`.
Vous vous êtes peut-être interrogé·e sur le fichier `package-lock.json`.
Ce billet va s'attarder un peu sur tout ça.
<!--more-->
## Une parabole {#parabole}
Début 2016, j'ai rejoint une équipe de développement web et j'ai découvert les joies[^fn-joys] de Node, NPM et Webpack.
La pile m'a passionné et j'ai fait un petit [projet perso][beverages] pour me mettre à niveau.
[^fn-joys]: Aucune ironie ici, j'y ai vraiment pris plaisir.
Et est arrivée une bizarrerie : j'ai assez rapidement détecté un bogue que personne d'autre dans l'équipe n'avait.
Puisque j'étais le seul à le voir, nous l'avons écarté comme étant un problème avec mon navigateur.
Avance rapide de quelques mois.
L'application est maintenant en production et l'équipe de support nous transmet un ticket avec exactement le même bogue que celui que nous avions vu sur mon ordinateur.
Ce n'était donc pas un problème de configuration, mais il _était_ spécifique à mon ordinateur, car j'avais rejoint l'équipe un mois après qu'ils aient commencé à travailler.
Quel est le rapport avec tout cela ?
Eh bien, avec le NPM, le moment auquel vous installez vos dépendances n'est pas si anodin.
Vous voyez, NPM enregistre vos dépendances dans le fichier `package.json`, mais d'une manière qui lui permet de télécharger des versions en aval de celle qui est déclarée.
C'est ce qui m'est arrivé : pour l'une des dépendances, j'avais téléchargé une version plus récente que celle que mes collègues utilisaient.
Cette version avait introduit un bogue --- pas évident à détecter, car c'était un effet secondaire pour des changements qui n'avaient a priori rien à voir.
Ça n'était pas un souci tant qu'on se limitait à mon poste, mais notre intégration continue, lorsqu'elle a construit la version de production, a téléchargé exactement la même version.
Imaginez-vous trouver un bogue entre deux bases de code identiques.
Je me souviens avoir compris le problème, mais je suis incapable de dire comment j'ai pensé à comparer les versions des dépendances avec mon coéquipier.
Voici un exemple de pourquoi il vaut mieux [comprendre le fonctionnement de NPM][know-how] si vous devez vous en servir.
Je vous propose quelques conseils de base pour commencer.
## Comment vous pouvez déclarer vos dépendances avec NPM
Jetons un œil aux déclarations de versions valides dans un fichier `package.json`.
D'après [la documentation de NPM][dependencies], tous les exemples suivants sont valides :
```json
{ "dependencies" :
{ "foo" : "1.0.0 - 2.9999.9999"
, "bar" : ">=1.0.2 <2.1.2"
, "baz" : ">1.0.2 <=2.3.4"
, "boo" : "2.0.1"
, "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
, "asd" : "http://asdf.com/asdf.tar.gz"
, "til" : "~1.2"
, "elf" : "~1.2.3"
, "two" : "2.x"
, "thr" : "3.3.x"
, "lat" : "latest"
, "dyl" : "file:../dyl"
}
}
```
La plupart de ces déclarations est assez explicite, mais j'aimerais m'attarder une minute sur les deux suivantes :
- `~version` « Approximativement équivalent à la version »
- `^version` « Compatible avec la version »
Des exemples seront plus clairs que du texte.
S'il vous faut davantage d'informations, je vous invite à consulter la [documentation de semver][semver].
### Les tildes
Ils permettent les changements du numéro de patch si une mineure est spécifiée et de la mineure sinon.
- `~1.2.3` := `>=1.2.3 < 1.3.0`
- `~1.2` := `>=1.2.0 < 1.3.0` := `1.2.x`
- `~1` := `>=1.0.0 < 2.0.0` := `1.x`
### Les chevrons
Ils permettent les changements du numéro non nul le plus à gauche.
- `^1.2.3` := `>=1.2.3 < 2.0.0`
- `^0.2.3` := `>=0.2.3 < 0.3.0`
- `^0.0.3` := `>=0.0.3 < 0.0.4`
## Comment vous devriez déclarer vos dépendances avec NPM
Je ne voudrais pas passer pour un gourou, mais je peux vous donner mon opinion bâtie sur [mon expérience][#parabole].
Dans mon anecdote, la différence portait sur le patch, me semble-t-il, et le bogue n'était lié à aucun des changements de la release note --- en tout cas, pas directement.
Ma conclusion[^fn-stackoverflow-agrees] est que vous devriez être extrêmement prudent quand vous choisissez vos dépendances.
Pour tout projet sensible ou destiné à la production, je recommanderais de fixer la version en retirant les préfixes.
Ne faites pas confiance aux versions descendantes tant que vous ne les avez pas testées, et ne limitez pas ces tests à ce qu'indiquent les release notes.
Dans le monde Java, [Maven permet de déclarer des plages de versions][maven-ranges] pour les dépendances, mais la plupart des développeurs que je connais (moi compris) considèrent qu'il s'agit généralement d'une mauvaise pratique.
**Vos builds _doivent_ être répétables**.
Laisser votre outil de build déterminer les versions au moment de la compilation est juste incompatible avec cet objectif.
Si vous ne voulez pas qu'NPM préfixe les versions, vous pouvez utiliser la commande suivante : `npm config set save-prefix=''`.
[^fn-stackoverflow-agrees]: Les commentaires et conseils que j'ai pu voir sur StackOverflow en cherchant pour ce billet me confortent dans mon opinion.
## Une note sur le package-lock.json
En travaillant sur mon projet perso à l'époque de la [parabole][#parabole], j'ai découvert [Yarn].
C'est une alternative à NPM _made in Facebook_ pour contrer des problèmes qu'ils ont rencontrés.
Beaucoup ont été séduit·es par sa vitesse incomparable.
Mais ce qui a retenu mon attention en priorité a été le fichier `yarn.lock` : lors la première installation des dépendances, Yarn crée un fichier de verrouillage qui indique les versions effectivement installées.
Ainsi, lorsqu'un autre membre de l'équipe récupère le projet et installe les modules, Yarn va regarder le fichier de verrouillage plutôt que le `package.json`.
Les années ont passé et NPM a intégré des fonctionnalités similaires à celle de Yarn, dont la ligne suivante lorsque vous installez vos dépendances pour la première fois :
```
$ npm i
npm notice created a lockfile as package-lock.json. You should commit this file.
```
Mon voisin de bureau m'a récemment dit qu'il ajoutait ce fichier au `.gitignore`.
C'est une bonne idée pour la plupart des fichiers générés, mais pas pour celui-ci : vous devriez le versionner et le partager avec votre équipe pour garantir des installations identiques sur tous les postes.
Non, n'essayez pas de le modifier à la main.
Il est écrit pour NPM, pas pour des humains.
Modifiez `package.json` et exécutez `npm i` pour mettre à jour le fichier de verrouillage.
## Un chouette outil pour expérimenter
Il est temps de conclure.
Je vais donc vous quitter sur un lien sympathique : semver vous aide à comprendre et tester votre spécification de version grâce à une interface dynamique et claire.
Je ne vais pas faire plus de fioritures que le site en question, cliquez juste ici pour voir : https://semver.npmjs.com/
[beverages]: https://github.com/cychop/beverages-js
[know-how]: {{< relref path="/blog/2019/12/27-know-technology-not-framework" >}}
[semver]: https://docs.npmjs.com/misc/semver
[dependencies]: https://docs.npmjs.com/files/package.json#dependencies
[maven-ranges]: https://www.baeldung.com/maven-dependency-latest-version#maven-version-range-syntax
[yarn]: https://yarnpkg.com/
---
date: 2020-05-04T22:25:41+02:00
title: Make Sure These Node Modules Are Those You Wanted
subtitle: These are not the modules you are looking for.
slug: subtleties-npm-package.json-module-versions
description: NPM and Node have brought a new way to manage libs and scripts, but NPM's tildes and carets may be enough to break your build if you're not careful.
author: chop
categories:
- software-creation
tags:
- web-dev
- node-npm
---
Sometimes, I don't understand something, so I search the answer and sharing it is natural.
Sometimes, I don't think sharing it will be any use, until I realize that some people, even in my team, struggle with the same thing.
This post is in the second category.
If you use NPM regularly, you must have noticed it adds tildes (`~`) or carets (`^`) in front of your dependencies' version number.
You may also have noticed it creates a `package-lock.json` file.
If you don't know what any of these are, this post will shed some light.
<!--more-->
## A Cautionary Tale {#tale}
At the beginning of 2016, I joined a front-end development team and I discovered the joys[^fn-joys] of Node, NPM and Webpack.
I was actually quite fond of the stack and made a little [side project][beverages] to get up to date.
[^fn-joys]: No irony here, I really enjoyed it.
Something funny occurred, though.
I quite quickly detected a bug that no one else on the team had.
Since only I saw that, we discarded it as a problem with my browser.
Fast forward to a few months later.
The app is now released and the support team forwards a ticket with the exact same bug we had seen on my computer.
So, it wasn't a problem with my configuration, but it _was_ specific to my computer, because I had joined the team a month after they had started working.
What's that to do with anything?
Well, with NPM, the moment you install the dependencies is not that trivial.
See, NPM saves your dependencies in the `package.json` file, but in a manner that allows it to download downstream versions than the one declared.
This is what happened to me: for one of the dependencies, I had downloaded a later version than the one my colleagues were using.
That version had introduced a bug---not easy to detect as it was a side effect for changes that shouldn't have affected it.
My computer wouldn't have been a problem, but our CI downloaded just the same version to build the production release.
Now, figuring out a bug that appeared only on specific computers while your codebase is identical is not that obvious.
I remember understanding the problem, but I can't tell you how I thought of comparing the versions of the dependencies with my teammate.
That's an example why you'd better [take some time understanding how NPM works][know-how] if you're going to use it.
Here are a few essential tips to begin with.
## How You Can Declare Dependencies with NPM
Let's have a look at the valid version declarations for dependencies in a `package.json` file.
From [NPM's documentation][dependencies], all the following are valid:
```json
{ "dependencies" :
{ "foo" : "1.0.0 - 2.9999.9999"
, "bar" : ">=1.0.2 <2.1.2"
, "baz" : ">1.0.2 <=2.3.4"
, "boo" : "2.0.1"
, "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
, "asd" : "http://asdf.com/asdf.tar.gz"
, "til" : "~1.2"
, "elf" : "~1.2.3"
, "two" : "2.x"
, "thr" : "3.3.x"
, "lat" : "latest"
, "dyl" : "file:../dyl"
}
}
```
Most of those declarations are quite clear, but I'd like to focus a minute on the two following:
- `~version` "Approximately equivalent to version"
- `^version` "Compatible with version"
Examples should be clearer than any amount of text.
If you need more information than the examples below, please check [semver's documentation][semver].
### Tilde Range
Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not.
- `~1.2.3` := `>=1.2.3 < 1.3.0`
- `~1.2` := `>=1.2.0 < 1.3.0` := `1.2.x`
- `~1` := `>=1.0.0 < 2.0.0` := `1.x`
### Caret Range
Allows changes that do not modify the leftmost non-zero digit of the version.
- `^1.2.3` := `>=1.2.3 < 2.0.0`
- `^0.2.3` := `>=0.2.3 < 0.3.0`
- `^0.0.3` := `>=0.0.3 < 0.0.4`
## How You Should Declare Dependencies with NPM
Well, I'm not in the place to tell you that, but I can give you the opinion I built based on [my experience][#tale].
In my tale, the difference was a patch, I think, and the bug was a side effect on something that was not on the release note.
My conclusion[^fn-stackoverflow-agrees] is that you should be extra-careful when handling your dependencies.
For anything sensitive or production-ready, I'd recommend fixing the versions by avoiding the prefix in the dependencies' version.
Don't trust downstream versions until you've got a chance to test them, and don't test only what the release note indicates.
In the Java world and, though Maven authorizes to [declare dependencies versions as ranges][maven-ranges], most developers I know (myself included) consider this a bad practice.
**You _need_ to your builds to be repeatable**.
Letting the build tool choosing on build time which version it'll use is just incompatible with that.
If you actually don't want NPM to add the tilde or caret prefix to the versions, you can use the following command: `npm config set save-prefix=''`.
[^fn-stackoverflow-agrees]: I'm comforted in this view from the many comments I saw on StackOverflow while researching for this post.
## A Note About the package-lock.json
When working on my side project at the time of the [tale][#tale], I discovered [Yarn].
It's an alternative to NPM that Facebook built due to limitations they faced.
Many people back then were seduced because it was a hell of a lot faster to download dependencies.
But the thing that got my eye first was the `yarn.lock` file: when installing dependencies, Yarn creates a lockfile that tells which dependencies were effectively installed.
That way, when another developer checkouts the projects and installs the modules, Yarn will look at the lockfile rather than the `package.json` and do the install.
Years have passed and NPM has integrated features similar to Yarn's, including the following line when you install dependencies:
```
$ npm i
npm notice created a lockfile as package-lock.json. You should commit this file.
```
Inconsistencies between machines should no longer be a problem, now.
A teammate recently told me he added this file to the `.gitignore`.
It makes sense for most generated files, but not this one: you _should_ version it and share it with your team, to ensure consistent installs across machines.
No, don't try to read or edit it.
It's for NPM, not humans.
Just edit `package.json` and run `npm i` to update the lockfile, or do everything with an `npm` command if you know them well enough.
## A Nice Tool for Experimenting
Time to conclude this post, so I'll just leave a funny link here: semver can help you understanding and testing your version specification through a nice dynamic interface.
Just go there to play a bit: https://semver.npmjs.com/
[beverages]: https://github.com/cychop/beverages-js
[know-how]: {{< relref path="/blog/2019/12/27-know-technology-not-framework" >}}
[semver]: https://docs.npmjs.com/misc/semver
[dependencies]: https://docs.npmjs.com/files/package.json#dependencies
[maven-ranges]: https://www.baeldung.com/maven-dependency-latest-version#maven-version-range-syntax
[yarn]: https://yarnpkg.com/
---
title: Node & NPM
description:
D'abord pensés pour construire des solutions serveur basées sur JavaScript, Node et NPM ont apporté de nouveaux outils pour le développement web.
weight: 11
---
---
title: Node & NPM
description:
First intended as tools for building JavaScript-based server-side solutions, Node and NPM have brought a set of new tools to front-end development.
weight: 11
---
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment