💡

Vous êtes en train de lire le Chapitre 5 du livre “Node.js”, écrit par Thomas Parisot et publié aux Éditions Eyrolles.

L’ouvrage vous plaît ? Achetez-le sur Amazon.fr ou en librairie. Donnez quelques euros pour contribuer à sa gratuité en ligne.

Savoir naviguer dans la richesse de l’écosystème npm est une force pour la durabilité de nos projets. Ces modules nous aident à façonner un outillage résilient et adapté à chacun de nos projets.

Sommaire
  • Créer un fichier package.json

  • Installer un module npm

  • Outiller un projet avec les scripts npm

  • Anatomie du fichier package.json

  • Quelques commandes pour aller plus loin

  • Questions et mystères autour de npm

Ce chapitre va nous permettre d’y voir plus clair du côté des modules npm. Nous apprendrons comment identifier des modules de confiance, les installer et les mettre à jour sans casser nos projets.

Nous nous tournerons ensuite du côté des scripts npm pour créer un outillage sur mesure et de qualité. Grâce à eux, nous serons en mesure d’automatiser les tâches répétitives à notre rythme.

Enfin, nous découvrirons des commandes moins connues de npm. Elles pourrons nous faciliter la vie ou nous débloquer quand ça ne va pas.

💬
Remarque Versions de Node et npm

Le contenu de ce chapitre utilise les versions Node v10 et npm v6. Ce sont les versions stables recommandées en 2022.

Le mot npm correspond à trois concepts différents que nous aborderons tout au long de ce chapitre :

  • l'exécutable npm – un programme écrit en ECMAScript ;

  • le registre npm – une plate-forme de distribution de modules ;

  • un module npm – en général installé depuis le registre et utilisable avec les fonctions require() et import.

Je préciserai toujours si l’utilisation de npm fait référence à l'exécutable, au registre ou à un module.

L’exécutable npm est installé par défaut avec Node. Vérifions la version installée en ouvrant un terminal et en écrivant la commande suivante :

npm --version
6.4.0

Si un message s’affiche en indiquant que npm n’est pas un programme reconnu, veuillez vous référer au chapitre 2 et vérifier que Node v10 est bien installé.

💡
Pratique Jouer avec les exemples dans un terminal

Les exemples titrés d’un nom de fichier peuvent être installés sur votre ordinateur. Exécutez-les dans un terminal et amusez-vous à les modifier en parallèle de votre lecture pour voir ce qui change.

Installation des exemples via le module npm nodebook
npm install --global nodebook
nodebook install chapter-05
cd $(nodebook dir chapter-05)

La commande suivante devrait afficher un résultat qui confirme que vous êtes au bon endroit :

node hello.js

Suivez à nouveau les instructions d’installation pour rétablir les exemples dans leur état initial.

6. Créer un fichier package.json

La présence d’un fichier package.json devient nécessaire dès qu’un projet inclut un module npm ou a vocation à être publié pour être repris dans un autre projet – que ce soit dans un cadre professionnel ou personnel.

Le fichier package.json est la clé de voûte servant à reproduire l’installation du projet et créer un outillage autonome. La commande npm init génère un tel fichier. L’utilisation de l’option --yes va plus vite car elle nous évite de répondre aux questions :

npm init --yes

Si aucun fichier package.json n’existe dans le répertoire courant, il sera créé avec des valeurs par défaut – le nom du module correspondra au nom du répertoire courant. Si ce fichier existait déjà, il sera alors préservé et son contenu sera affiché :

package.json
{
  "name": "nodebook.chapter-05",
  "private": true,
  "version": "1.0.0",
  "main": "./examples/index.js",
  "description": "",
  "scripts": {
    "lint": "eslint ./examples",
    "print-args": "node examples/print-args.js",
    "start": "micro examples/app.js",
    "test": "mocha examples/tests.js",
    "pretest": "npm run lint"
  },
  "engines": {
    "node": "^10.0.0"
  },
  "author": "Thomas Parisot (https://thom4.net)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/thom4parisot/nodebook/issues"
  },
  "homepage": "https://github.com/thom4parisot/nodebook",
  "dependencies": {
    "cowsay": "^1.3.1",
    "lodash": "^4.17.11",
    "micro": "^9.3.3"
  },
  "devDependencies": {
    "eslint": "^5.9.0",
    "mocha": "^5.2.0"
  }
}

Nous reviendrons sur la structure du fichier. En attendant, focalisons-nous sur les opérations courantes comme l’installation de modules.

7. Installer des modules npm

Le mécanisme des modules est documenté dans le chapitre 4. Les fonctions require() et import chargent nos propres modules mais aussi les modules de base, installés avec Node. Les modules npm sont complémentaires et téléchargeables à l’aide de l’exécutable npm.

Cette section va nous aider à comprendre ce qui se passe pendant les phases d’installation, de mise à jour et de désinstallation des modules npm.

7.1. Depuis le registre npm

Le registre npm (npmjs.com) est l’hébergement principal des modules ECMAScript, pour Node et le front-end.

La commande npm install s’utilise directement quand nous connaissons déjà le nom d’un module à installer, par exemple cowsay (npmjs.com/cowsay) :

npm install cowsay
+ cowsay@1.3.1
added 10 packages from 3 contributors in 1.667s
found 0 vulnerabilities

Le module est installé et prêt à être inclus dans un script. Nous constatons aussi que le champ dependencies est apparu dans le fichier package.json :

package.json
{
  ...
  "dependencies": {
    "cowsay": "^1.3.1"
  }
}

L’exécutable npm tient les comptes des modules installés à notre demande. Cela nous sera utile pour installer les modules sur un autre ordinateur. Nous reviendrons plus tard sur la notation des versions – on en reparlera sous le nom de versions sémantiques (Semantic Versionning).

cow.js
'use strict';

const {say} = require('cowsay');
const message = say({ text: 'Bonjour !' });

console.log(message);

L’inclusion d’un module npm est identique à celle d’un module de base.

Regardons le résultat sans plus tarder :

node cow.js
___________
< Bonjour ! >
-----------
       \   ^__^
        \  (oo)\_______
           (__)\       )\/\
               ||----w |

Le module npm nous a permis d’utiliser du code sans avoir à le créer, alors qu’il n’était pas fourni par la plate-forme Node.

Maintenant que nous savons installer un module npm, nous pouvons en chercher d’autres et comprendre comment les utiliser.

💬
Question Où sont stockés les modules npm ?

Les modules npm et leurs dépendances sont stockés dans un répertoire node_modules, situé au même niveau que le fichier package.json.

💬
Sous le capot Ce que fait l’exécutable npm pendant l’installation

L’exécutable npm effectue un bon nombre d’actions lorsqu’on valide la commande npm install cowsay :

  1. Il interroge le registre npmjs.com pour obtenir des informations sur le module demandé.

  2. Il détermine que 1.3.1 est la version la plus récente.

  3. Il télécharge une archive compressée (.tar.gz) qui contient tous les fichiers de la version 1.3.1.

  4. L’archive est décompressée dans le répertoire node_modules.

  5. Les dépendances sont elles aussi téléchargées puis décompressées dans le répertoire node_modules.

  6. Le module cowsay est inscrit dans le fichier package.json.

💬
Remarque Dépendances de développement

Il existe une variante de la commande pour distinguer les dépendances spécifiques à l’outillage du projet. Rendez-vous dans la section “Dépendances de développement” pour en savoir plus.

7.2. Trouver son bonheur dans le registre npm

Le registre npm (npmjs.com) fourmille de modules – de simples fonctions, des bibliothèques ou des frameworks complets. Ils couvrent un spectre d’usages très larges : accès aux bases de données, frameworks web, outils front-end, utilitaires de test, compression de données, paiement bancaire, des frameworks mobiles, etc.

Cherchons une bibliothèque pour nous connecter à une base de données MySQL ou MariaDB. Tapez “mysql” dans le champ de recherche du registre npm ou saisissez directement l’URL menant aux résultats de cette recherche (npmjs.com/search?q=mysql) :

npm registry search
Figure 1. Extrait des résultats d’une recherche de modules npm avec le mot-clé “mysql”

Les résultats sont triés par pertinence – un mélange entre popularité, qualité et maintenance des projets.

Je trouve qu’il est difficile de décider uniquement en regardant la liste. J’ai tendance à ouvrir un onglet par module pour en lire la documentation. Prenons le cas du module mysql2 (npmjs.com/mysql2) justement :

npm package mysql2
Figure 2. Extrait de la page consacrée au module npm mysql2

Plusieurs éléments de cette page tendent à me rassurer et m’aident à juger de la robustesse de ce module :

  • les badges colorés qui affichent le statut d’exécution des tests ;

  • une introduction de documentation claire et concise ;

  • un nombre de téléchargements en progrès réguliers ;

  • l’utilisation avec des promesses ;

  • le nombre important de modules dépendants ;

  • je reconnais une personne qui contribue du code de qualité – Rebecca Turner (npmjs.com/~iarna).

J’ai un doute quand je lis 108 issues et 13 pull requests. Dans ce cas-là, je me dis que les personnes qui maintiennent le projet ne sont pas forcément très réactives.

Cependant, il y a suffisamment d’indicateurs au vert pour l’installer avec npm install mysql2 puis l’essayer dans un script.

Le module mysql-libmysqlclient (npmjs.com/mysql-libmysqlclient) ne me fait pas du tout le même effet.

npm package mysql libmysqlclient
Figure 3. Extrait de la page consacrée au module npm mysql-libmysqlclient

La page du module ne met pas d’exemple simple à comprendre et fait référence à des versions de Node antédiluviennes. Rien n’indique qu’il ne peut pas fonctionner avec Node v10, mais la présence du mot binding m’évoque que l’installation du module compile un programme écrit dans un autre langage – en l’occurrence, libmysqlclient.

Point positif : il n’y a que 14 issues GitHub. C’est peu, mais l’une d’entre elles est intitulée Does not work with any modern version of Node.js. Cela confirme mes doutes ; c’est suffisant pour que je passe mon chemin.

En continuant plus loin dans la liste des résultats de recherche, je suis tombé sur le module nommé falchion.

npm package falchion
Figure 4. Extrait de la page consacrée au module npm falchion

Il n’y a qu’une seule version du module, qui date de quatre années avec une documentation qui tient sur une ligne. Il y a très peu de chances que nous puissions en faire quelque chose.

Voici au final ce que j’estime être le plus important pour me faire une idée d’un module et décider de l’installer ou non :

  • Présence d’une documentation – je peux me faire une idée des fonctionnalités et de la complexité d’utilisation du module.

  • Des badges d'intégration continue – je sais ainsi qu’il y a des tests unitaires qui sont exécutés automatiquement avant que le module soit publié.

  • Le nombre de téléchargements – je sais si d’autres personnes s’en servent en espérant qu’ils remontent les problèmes rencontrés.

  • Le nombre de versions – cela me donne une idée de la maturité du projet et de la réactivité aux demandes de la communauté.

Ce sont des critères subjectifs. Un module est parfois populaire par ancienneté alors qu’il existe des alternatives, plus légères ou plus simples d’utilisation. C’est le cas du module moment.js, plus populaire que date-fns – que je préfère.

Il y a aussi des modules dans lesquels j’ai une confiance quasi-aveugle. Ils sont publiés par les personnes présentes dans cette liste non exhaustive :

Tableau 1. Personnes ayant écrit des modules npm à suivre

dougwilson

jdalton

sindresorhus

feross

jshttp

substack

fgribreau

mbostock

zkat

iarna

nodejitsu

isaacs

rwaldron

💡
Pratique Sélection de modules npm

J’ai compilé une liste de modules utiles pour mieux démarrer dans vos projets. Vous la trouverez en annexe A.

7.3. Désinstaller un module

L’utilisation de la commande npm uninstall supprime en toute sécurité un module npm et les fichiers qu’il a installés. La commande le retire ensuite de la liste des dépendances du fichier package.json.

npm uninstall cowsay
removed 10 packages in 1.963s
found 0 vulnerabilities

Le module cowsay n’est plus installé. Que se passe-t-il si nous exécutons à nouveau un l’exemple cow.js ?

node cow.js
internal/modules/cjs/loader.js:596
    throw err;
    ^

Error: Cannot find module 'cowsay'

Le chargement du module a échoué car Node n’arrive pas à le trouver – et c’est normal.

Nous devons relancer la commande npm install cowsay pour que le script fonctionne à nouveau.

7.4. Depuis un fichier package.json

Jusqu’à présent, nous avons installé des modules en les ajoutant un par un. La procédure est légèrement différente quand nous installons le projet de zéro ou quand le fichier package.json a été mis à jour par un·e collègue, par exemple.

L’exemple suivant illustre la remise à zéro des modules utilisés en exemple de ce chapitre :

cd $(nodebook dir {chapter-id} --root)
rm -rf node_modules
npm install
added 164 packages from 583 contributors in 4.781s
found 0 vulnerabilities

Nous nous sommes positionnés dans un répertoire qui contient un fichier package.json puis nous avons supprimé tout ce qui aurait pu être installé.

La commande npm install s’utilise de manière systématique quand nous récupérons du code avec Git pour la première fois (git clone) ou après une mise à jour, par exemple avec git pull.

L’exécutable npm vérifie que la correspondance est bien respectée entre ce qui est installé dans le répertoire node_modules et les modules listés dans le fichier package.json. La commande npm install installe, met à jour et retire les modules nécessaires.

💡
Pratique npm clean-install (npm ci)

La commande npm clean-install réinstalle un projet de zéro de manière prédictible. Nous y reviendrons plus loin.

7.5. Spécifier une version

Par défaut, l’exécutable npm installe la dernière version d’un module. Nous avons la liberté d’en installer d’autres qui sont antérieures. C’est pratique quand des modules arrêtent de prendre en charge des navigateurs web ou des versions de Node alors que nous les utilisons encore.

Nous allons nous servir du module lodash (npmjs.com/lodash) pour illustrer nos allées et venues entre différentes versions. À l’heure où j’écris ces lignes, sa version la plus récente est la 4.17.11. C’est ce que rapporte le résultat de la commande npm install lodash :

npm install lodash
+ lodash@4.17.11

L’utilisation du caractère @ conjointement à un numéro de version précise la version à installer :

npm install lodash@3.0.0
+ lodash@3.0.0

Nous avons installé une version précise, mais il y a sûrement des mises à jour qui ont suivi pour corriger des bogues. Le problème est que, à ce stade, nous ne connaissons pas le numéro de version à spécifier. Idéalement, je préférerais installer la version la plus récente de la série 3. Il se trouve que l’exécutable npm sait le faire pour nous et sans effort :

npm install lodash@3
+ lodash@3.10.1

Nous pouvons faire la même chose avec les versions les plus récentes de la série 3 et de la série 2.2 :

npm install lodash@3
+ lodash@3.10.1
npm install lodash@2.2
+ lodash@2.2.1
💡
Pratique Connaître toutes les versions d’un module

La commande npm view affiche les informations d’un module npm directement depuis notre terminal. Elle affiche toutes les versions publiées avec l’argument versions :

npm view lodash versions
[ '0.1.0',
  '0.2.0',
  ...
  '1.0.0',
  '1.0.1',
  '1.0.2',
  ... ]

Revenons à la version la plus récente en réutilisant la commande d’installation abordée auparavant :

npm install lodash
+ lodash@2.4.2

Quelque chose d’inattendu s’est produit : la version la plus récente de la série 2 a été installée au lieu de la version 4.17.11. Nous trouverons un élément de réponse dans le fichier package.json :

package.json
{
  ...
  "dependencies": {
    "cowsay": "^1.3.1",
    "lodash": "^2.4.2"
  }
}

L’exécutable npm respecte la version indiquée dans le fichier package.json si aucune autre n’est précisée dans la commande. Si la dépendance n’est pas listée, alors l’exécutable npm installe la version la plus récente.

L’étiquette latest explicite notre envie d’installer la version la plus récente et sans tenir compte du fichier package.json :

npm install lodash@latest
+ lodash@4.17.11

Nous sommes désormais en mesure de choisir entre différentes versions d’un module et de manière plus ou moins fine.

Nous prendrons le temps d’explorer le mécanisme de numérotation des versions dans la section suivante afin de mieux comprendre ce qui est renseigné dans le fichier package.json.

💡
Pratique Connaître les étiquettes d’un module

La commande npm view va à nouveau nous aider. Elle affiche toutes les versions publiées avec l’argument dist-tags :

npm view lodash dist-tags
{ latest: '4.17.11' }

Ce mécanisme d’étiquette sert de raccourci pour associer un numéro de version (qui change) à un intitulé (qui reste dans le temps).

7.6. Comprendre les numéros de versions (Semantic Versioning)

Les numéros de versions ont été utilisés de deux manières dans les sections précédentes : avec l’exécutable npm et en observant la liste de dépendances dans le fichier package.json.

L’exécutable npm découpe un numéro de version en trois parties : majeure, mineure et patch. Pour le numéro de version 1.2.3, 1 indique la version majeure, 2 la version mineure et 3 la version patch.

Si nous devions mettre à jour lodash@2.2.0 :

  • Vers lodash@2.2.1 : mise à jour patch – des bogues sont corrigés.

  • Vers lodash@2.4.2 : mise à jour mineure – des fonctionnalités sont ajoutées, corrigées ou modifiées sans affecter notre code.

  • Vers lodash@4.17.11 : mise à jour majeure – des fonctionnalités sont modifiées, remaniées ou supprimées et peuvent casser notre code qui repose dessus.

Une mise à jour majeure demande de lire attentivement la documentation du module pour comprendre le volume de travail à fournir avant de monter en version. La mise à jour mineure peut occasionnellement demander du travail selon l’interprétation des développeurs de modules npm.

Tableau 2. Différentes manières d’exprimer des versions
Symbole Version Représentation alternative Représentation étendue

1.0.0

-

-

^

^1.0.0

1.x.x

>=1.0.0 <2.0.0

~

~1.0.0

1.0.x

>=1.0.0 <1.1.0

*

*

x.x.x

>=0.0.1

Je ne pense pas qu’il soit nécessaire de toujours choisir la dernière version majeure. Les versions patch et mineures sont plus importantes à mes yeux, car elles contiennent des corrections dont bénéficient nos applications.

💡
Pratique Calculateur de version

L’outil en ligne semver.npmjs.com sert à tester la syntaxe des versions sémantiques avec de véritables modules npm.

7.7. Mises à jour

Nous avons appris à installer des modules npm dans les versions de notre choix et à les réinstaller depuis la liste contenue dans le fichier package.json. Comment savoir s’il faut les mettre à jour ?

L’utilisation combinée des commandes npm outdated et npm update va nous permettre d’y arriver.

Commençons par installer d’anciennes versions des modules lodash et cowsay :

npm install lodash@2.0.0 cowsay@1.0.0

La commande npm outdated affiche les dépendances qui ne sont pas à jour :

npm outdated
Package  Current  Wanted   Latest  Location
cowsay     1.0.0   1.3.1    1.3.1  nodebook.chapter-05
lodash     2.0.0   2.4.2  4.17.11  nodebook.chapter-05

Le numéro de version affiché dans la colonne Wanted est celui qui sera atteint avec la commande npm update.

npm update
+ cowsay@1.3.1
+ lodash@2.4.2
added 7 packages and updated 3 packages in 2.717s

Observons ce qui a changé dans les résultats de la commande npm outdated :

npm outdated
Package  Current  Wanted   Latest  Location
lodash     2.4.2   2.4.2  4.17.11  nodebook.chapter-05

Les modules cowsay et lodash ont été mis à jour au plus sûr et seul le deuxième est désormais listé ; il peut rester en l’état si nous n’avons pas le temps de rendre notre code compatible avec ses changements.

Sinon, une installation manuelle s’impose avec l’étiquette latest :

npm install lodash@latest
+ lodash@4.17.11

Un dernier appel à npm outdated nous en donne le cœur net :

npm outdated

Si rien ne s’affiche, c’est que tout est bon : nos modules sont à jour !

8. Autres manières d’installer et d’utiliser des modules npm

Dans la section précédente, nous avons vu comment installer des modules depuis le registre npm. Maintenant, nous allons apprendre à les installer depuis des sources variées, uniquement à des fins de développement ou en tant que commandes exécutables au niveau du système d’exploitation.

8.1. Depuis GitHub, GitLab ou un dépôt Git

Il arrive que l’auteur·e d’un module npm corrige un problème sans publier le correctif sur le registre npm. Il arrive aussi qu’un module soit hébergé de manière publique ou privée sur une plate-forme d’hébergement Git comme GitLab ou GitHub, sans passer par le registre npm.

Le module cowsay est publié sur le registre npm, mais il est aussi hébergé sur GitHub à l’adresse github.com/piuccio/cowsay. Installons-le depuis cette source :

npm install https://github.com/piuccio/cowsay
+ cowsay@1.3.1
updated 1 package in 5.866s

L’exécutable npm vérifie qu’un fichier package.json est situé à la racine du dépôt. Dans ce cas de figure, il utilise le programme Git pour obtenir le code source du module.

Une écriture raccourcie existe pour installer un module depuis un hébergement Git populaire, sans avoir à écrire l’URL en entier :

npm install github:piuccio/cowsay
+ cowsay@1.3.1
updated 1 package in 4.513s
⚠️
Considérations Performance d’accès à Git

L’installation est plus lente depuis un dépôt Git que depuis un registre npm. L’exécutable npm fait appel à l’exécutable git pour cloner l’historique du dépôt et de ses dépendances pour extraire la version adéquate de la copie de travail.

Le temps de téléchargement sera proportionnel au nombre de commits.

L’exécutable npm sait aussi installer des modules avec le protocole Secure Shell (SSH) désigné par git+ssh :

npm install git+ssh://git@github.com:piuccio/cowsay.git
+ cowsay@1.3.1
updated 1 package in 10.263s

Les clients Git et SSH doivent être configurés au niveau du système pour être en mesure de s’authentifier sur l’hôte distant.

C’est une solution intéressante pour automatiser l’installation de modules privés. L’étape suivante serait de déployer un registre npm privé ou de souscrire une option payante sur le registre principal.

8.2. Dépendances de développement

Les dépendances de développement sont des modules npm utilisés pour exécuter les tests unitaires ou pour de l’outillage sont aussi des dépendances de développement. Ce sont des modules que nous n’appelons pas directement avec les fonctions require() et import.

Par exemple, le module npm mocha est utilisé pour structurer et exécuter des tests unitaires pour Node et les navigateurs web. Il est donc logique de l’installer comme dépendance de développement. L’option --save-dev signale cette intention à l’exécutable npm :

npm install --save-dev mocha
+ mocha@5.2.0

L’exécutable npm range alors ce module dans une nouvelle section du fichier package.json – la section devDependencies :

{
  ...
  "dependencies": {
    "cowsay": "^1.3.1",
    "lodash": "^4.17.11"
  },
  "devDependencies": {
    "mocha": "^5.2.0"
  }
}
💡
Optimisation Installer seulement les dépendances de production

La commande npm install accepte l’option --production. Elle installe seulement les dépendances listées dans la section dependencies :

npm install --production

Le poids d’installation est ainsi réduit. C’est l’idéal dans le cas du déploiement de fonctions événementielles (chapitre 6).

8.3. Exécutable système (installation globale)

Certains modules npm s’installent comme des programmes exécutables. Ils s’appellent ensuite dans un terminal, exactement comme nous le faisions jusqu’à présent avec l’exécutable npm.

C’est le cas du module serve (npmjs.com/serve), par exemple. Il démarre un serveur web en ligne de commande pour tester le rendu de fichiers HTML sans avoir à configurer de logiciels comme Apache ou nginx.

L’installation est rendue globale – à l’échelle du système d’exploitation – avec l’utilisation de l’option --global :

npm install --global serve
+ serve@10.0.0
💬
Question Comment savoir si un module npm s’installe comme un exécutable système ?

En général, les modules qui se prêtent bien au jeu du npm install --global sont ceux qui documentent des exemples de commande à exécuter, qui se décrivent comme des outils en ligne de commande ou qui mentionnent explicitement l’installation globale.

L’exécutable serve est disponible suite à l’installation globale :

serve --version
10.0.0

Le module npm s’exécute de manière transparente, sans invoquer Node ni l’exécutable npm :

serve .
INFO: Accepting connections at http://localhost:3000
💡
Documentation L’option --help

Par convention, les modules npm qui s’utilisent en ligne de commande sont accompagnés d’une documentation. Ce manuel décrit des cas d’usage ainsi que les options à disposition.

Affichage de la documentation du module npm serve depuis la ligne de commande :

serve --help

Un module installé de manière globale se désinstalle en passant l’option --global à la commande npm uninstall :

npm uninstall --global serve

Le chapitre 8 sera l’occasion d’entrer plus en détail dans le développement d’exécutables système écrits en ECMAScript.

9. Outiller un projet avec les scripts npm

Les scripts npm sont des outils puissants qui autonomisent l’outillage projet, automatisent des actions manuelles et simplifient des actions trop complexes à mémoriser.

Ils sont consignés dans la section scripts du fichier package.json. Ils se basent sur des scripts Node et des modules npm pour lancer des actions quand des fichiers sont modifiés, transformer des feuilles de style, exécuter des tests unitaires ou fonctionnels, déployer le projet, entre autres.

Ils permettent de créer des conventions entre nos projets. Nous réutiliserons ainsi les mêmes noms et adapterons les commandes au projet en question.

9.1. Démarrer l’application

Le script npm start concerne les projets dont le script principal tourne en continu – une application web par exemple.

L’exemple suivant démarre un serveur web sans que nous ayons à connaître la commande associée :

npm start

nodebook.chapter-05@1.0.0 start          
micro examples/app.js                    

micro: Accepting connections on port 3000
  1. L’exécutable npm affiche le nom du script en cours d’exécution

  2. micro examples/app.js est la commande réellement exécutée par npm

Nous sommes libres de renseigner la valeur du champ scripts.start du fichier package.json comme bon nous semble :

package.json
{
  ...
  "scripts": {
    "start": "micro examples/app.js"
  },
  "dependencies": {
    "micro": "^9.3.3"
  }
}

Nous avons utilisé le module npm micro (npmjs.com/micro) pour démarrer une application web. Plus exactement, nous avons utilisé l’exécutable fourni par ce module.

💡
Pratique Les modules exécutables dans les scripts npm

Les modules npm exécutables sont disponibles au niveau du système lorsqu’ils sont installés avec l’option --global.

Les exécutables des modules listés dans dependencies et devDependencies sont utilisables dans les scripts npm.

Nous pouvons ainsi contenir tous les exécutables nécessaires dans les dépendances du projet.

Nous verrons dans le chapitre 6 que les plates-formes de service utilisent aussi la valeur du champ scripts.start pour déterminer comment démarrer notre application.

9.2. Exécuter des tests

Le script npm test concerne tous les projets pour lesquels nous avons écrit des tests qu’ils soient unitaires ou fonctionnels.

L’intention de la commande lancée par le script npm est de terminer en erreur si un des tests n’aboutit pas au résultat escompté.

L’exemple suivant lance un test unitaire qui s’assure de la cohérence d’un des exemples précédents :

npm test

nodebook.chapter-05@1.0.0 test   
mocha examples/tests.js          

app.js
  ✓ prints a cow as a response

1 passing
  1. L’exécutable npm affiche le nom du script en cours d’exécution.

  2. mocha examples/tests.js est la commande réellement exécutée par npm.

Cette fois-ci, nous avons personnalisé la valeur du champ scripts.test du fichier package.json :

package.json
{
  ...
  "scripts": {
    "test": "mocha examples/tests.js"
  },
  "devDependencies": {
    "mocha": "^5.2.0"
  }
}

Nous avons eu recours au module npm mocha (npmjs.com/mocha), de même qu’avec le script de démarrage, nous nous sommes basés sur l’exécutable fourni par le module. En revanche, nous l’avons listé dans la section devDependencies car il est relatif à l’outillage du projet.

Les services d’intégration continue lancent le script npm test lorsqu’ils détectent qu’ils ont affaire à un projet Node.

💬
Documentation Scripts définis par npm

D’autres scripts que test et start sont définis par l’exécutable npm. Ils sont tous documentés sur :

9.3. Créer un script npm personnalisé

Les scripts npm personnalisés sont utiles lorsque nous souhaitons outiller notre projet sans forcément que ce soit en rapport avec le lancement des tests ou de l’application.

Les scripts personnalisés se démarrent avec npm run :

npm run print-args

nodebook.chapter-05@1.0.0 print-args
node examples/print-args.js

Rien à signaler.

Nous avons créé ce script en configurant la valeur du champ scripts.print-args du fichier package.json :

package.json
{
  ...
  "scripts": {
    "print-args": "node examples/print-args.js"
  }
}
💡
Pratique Lister les scripts disponibles

La commande npm run (sans argument) liste tous les scripts npm du projet.

Avec le temps, j’ai développé les conventions suivantes :

  • npm run build : construit les artefacts à déployer.

  • npm run deploy : déploie le projet vers l’hébergeur.

  • npm run lint : applique un vérificateur syntaxique au code du projet.

  • npm run watch : démarre l’application et la relance à chaque changement.

💡
Pratique Passer des arguments à un script npm

Une option spéciale nous aide à transmettre des arguments à un script. Les arguments doivent être placés à droite de l’option -- :

npm run print-args un --test=true
['un']
npm run print-args ##--## un --test=true
['un', '--test=true']

Un script npm peut faire appel à d’autres :

package.json
{
  ...
  "scripts": {
    "lint": "eslint ./examples",
    "test": "npm run lint && mocha examples/tests.js"
  }
}

J’ai plutôt tendance à découper mes scripts de sorte à ce qu’ils fassent tous une chose et une seule. Je peux ainsi les appeler de manière individuelle.

package.json
{
  ...
  "scripts": {
    "lint": "eslint ./examples",
    "test": "npm run lint && npm run test:unit",
    "test:unit": "mocha examples/tests.js"
  }
}

La section suivante va nous aider à orchestrer l’exécution des scripts les uns par rapport aux autres.

💡
Pratique Accéder aux valeurs du fichier package.json

Toutes les sections du fichier package.json sont accessibles depuis les scripts npm sous forme de variables d’environnement. Leur nom est préfixé par npm_package suivi de leur nom “mis à plat”. Ainsi, le champ version est accessible en tant que $npm_package_version et le champ config.port en tant que $npm_package_config_port :

package.json
{
  ...
  "config": {
    "port": "4000"
  };
  "scripts": {
    "start": "node server --port $npm_package_config_port",
  }
}

9.4. Exécuter des commandes avant et après des scripts npm

L’ordre d’exécution des scripts se contrôle en utilisant les préfixes pre et post. Par exemple, les scripts nommés pretest et posttest seront exécutés respectivement avant et après le script test.

package.json
{
  ...
  "scripts": {
    "lint": "eslint ./examples",
    "test": "mocha examples/tests.js",
    "pretest": "npm run lint"
  }
}

Dans cet exemple de configuration, l’exécution de la commande npm test lancera d’abord le script pretest, puis lint puis enfin test :

npm test

nodebook.chapter-05@1.0.0 pretest
npm run lint
...

nodebook.chapter-05@1.0.0 lint
eslint ./examples
...

nodebook.chapter-05@1.0.0 test
mocha examples/tests.js
...

Ce mécanisme est utile pour s’intercaler sur des temps particuliers du cycle de vie d’un projet Node. En voici une sélection.

Script Quand ? Pourquoi ?

pretest

Avant les tests

Préparer l’espace de travail

posttest

Après les tests

Vérifier les règles de syntaxe de notre code

postinstall

Après installation les dépendances

Préparation significative du projet (téléchargements supplémentaires, etc.)

prestart

Avant de démarrer l’application

Préparatifs légers

prepublishOnly

Avant de publier le module

Préparation du projet avant de le distribuer (compilation de fichiers, etc.)

9.5. Automatiser tout l’outillage projet

Les scripts npm suffisent à outiller la majorité des projets. Cependant ils deviennent difficiles à lire lorsque les lignes deviennent trop longues.

npm-run-all (npmjs.com/npm-run-all) est un module qui parallélise leur exécution et simplifie l’appel d’un groupe de scripts.

package.json
{
  "scripts": {
    "build": "npm-run-all --parallel 'build:*'", // (1)
    "build:front-end": "browserify ...",
    "build:backend": "browserify ...",
    "build:css": "sass ..."
  }
  "devDependencies": {
    "npm-run-all": "*"
  }
}
  1. Les trois scripts préfixés par build: seront appelés en parallèle en exécutant npm run build.

Il est aussi possible de déclencher des actions parallèles après une première action séquentielle.

package.json
{
  "scripts": {
    "clean": "rm -rf ./dist",
    "build": "npm-run-all clean --parallel 'build:*'", // (1)
    "build:front-end": "browserify ...",
    "build:backend": "browserify ..."
  }
  "devDependencies": {
    "npm-run-all": "*"
  }
}
  1. npm-run-all exécute le script clean avant les autres scripts préfixés par build:.

Ils vous appartient d’orchestrer les scripts en les groupant avec un motif de noms ainsi qu’en combinant les options --parallel (alias -p) et --sequential (alias -s) pour activer ou désactiver le parallélisme d’exécution.

💬
Remarque Pourtant j’ai entendu parler de Gulp et de Grunt

L’énorme avantage d’outiller un projet avec la commande npm run et l’exécutable npm-run-all est que nous utilisons directement les outils dont nous avons besoin.

Gulp et Grunt introduisent une complexité d’apprentissage et des couches d’abstraction qui augmentent la fragilité de l’outillage et la barrière d’entrée de nos projets.

C’est tant mieux si nous pouvons nous en passer pour façonner nos propres outils.

10. Anatomie du fichier package.json

Le fichier package.json est essentiel pour tirer parti de l’exécutable npm. Tout projet concerné par l'installation de modules npm ou par l'outillage des scripts va forcément avoir ce fichier quelque part dans son arborescence.

Il se décompose en plusieurs parties : les informations générales qui aident les utilisateurs et utilisatrices à découvrir le module en effectuant une recherche, les points d’entrée pour inclure ou exécuter le module et la configuration projet qui affecte le fonctionnement de l’exécutable npm.

Tableau 3. Informations pour faciliter la découverte et la compréhension
Section Obligatoire ? Type Modifiable Description

version

Semver

Avec npm version

description

Texte

À la main

Explique l’intention du module à une personne qui le découvre

keywords

Tableau de texte

À la main

Facilite sa découverte sur npmjs.com

homepage

Texte (URL)

À la main

Indique où trouver de la documentation et des exemples d’utilisation – cela peut être l’adresse du dépôt GitLab ou GitHub

license

Texte

À la main

Explicite les conditions de réutilisation du code dans un autre projet

bugs.url

Texte (URL)

À la main

Facilite la remontée de bogues

repository.type

Texte

À la main

En général la valeur est git

repository.url

Texte (URL)

À la main

Facilite la découverte du code source à l’origine du module

Tableau 4. Points d’entrée pour utiliser votre module
Section Obligatoire ? Type Modifiable Description

name

Texte

À la main

Correspond au nom à spécifier aux fonctions require() et import. Un changement de nom obligera à mettre à jour tous les scripts qui appellent ce module

main

Texte (chemin)

À la main

Script qui sera utilisé lors de l’appel à require() et import (par défaut index.js)

bin

Texte (chemin)

À la main

Script qui sera utilisé comme exécutable lors de l’appel npx <module>

bin

Objet (nom/chemin)

À la main

Idem – forme qui permet de déclarer plusieurs exécutables au sein d’un même module

Tableau 5. Configuration projet pour l’exécutable npm
Section Obligatoire ? Type Modifiable Description

private

Booléen

À la main

Empêche la publication accidentelle sur le registre npm

engines

Objet (nom/SemVer)

À la main

Certains hébergeurs utilisent ce champ pour déterminer la version de Node à utiliser

dependencies

Objet (nom/SemVer)

Avec npm install

Voir la section “Installer des modules npm

devDependencies

Objet (nom/SemVer)

Avec npm install

Voir la section “Dépendances de développement

scripts

Objet (nom/commande)

À la main

Voir la section “Scripts npm

💬
Documentation Tout sur le fichier package.json

La page docs.npmjs.com/files/package.json documente de manière exhaustive les sections du fichier package.json.

En la lisant, vous apprendrez l’existence d’autres sections qui pourraient peut-être vous intéresser.

11. Quelques commandes pour aller plus loin

Nous venons de voir les commandes les plus utilisées de l’exécutable npm. Il en existe d’autres dont l’intérêt varie en fonction de vos envies et de vos pratiques de développement. Pas d’inquiétude donc si vous ne les utilisez pas toutes : j’en parle pour éclairer quelques points intéressants à explorer.

11.1. npm view : voir les informations d’un module

La commande npm view donne une vue synthétique d’un module npm donné. Elle est similaire à celle que nous pourrions trouver sur le registre npm, mais condensée pour l’affichage dans un terminal.

Nous y retrouvons des informations fournies par les personnes en charge du module ainsi que d’autres, fournies par le registre npm.

npm view nodebook

nodebook@0.9.1 | CC-BY-NC-SA-4.0 | deps: 6 | versions: 21
Node.js – Apprendre par l'exemple

keywords: nodejs, book, french, livre, learn, apprendre

bin: nodebook

dist
.tarball https://registry.npmjs.org/nodebook/nodebook-0.9.1.tgz
.shasum: 5ea87e9b85782e23164705a49cb7bd2dc4063775
.integrity: sha512-...
.unpackedSize: 15.0 MB

dependencies:
finalhandler: ^1.1.1  serve-static: ^1.13.2
get-port: ^3.2.0      update-check: ^1.5.2
glob: ^7.1.2          yargs: ^11.1.0

maintainers:
- oncletom

dist-tags:
latest: 0.9.1

published 23 hours ago by oncletom
Sélection de champs que je trouve intéressants
bin

Indique la présence d’un moins un exécutable.

dist

Donne des informations à propos du fichier téléchargé avec npm install <module>.

dependencies

Liste les modules additionnels téléchargés lors de l’installation.

dist-tags

Précise les étiquettes définies par personnes en charge du module, utiles quand nous souhaitons jongler entre ses différentes versions.

Nous pouvons aussi zoomer sur une métadonnée. Par exemple, spécifions le champ dependencies pour ne lister que les dépendances directes :

npm view nodebook dependencies
{ finalhandler: '^1.1.1',
  'get-port': '^3.2.0',
  glob: '^7.1.2',
  'serve-static': '^1.13.2',
  'update-check': '^1.5.2',
  yargs: '^11.1.0' }

Il est même possible de zoomer sur un niveau plus fin de métadonnée, avec une annotation similaire à celle d’un objet ECMAScript :

npm view nodebook dist.unpackedSize
14985184
npm view nodebook dist
{ integrity:
   'sha512-...',
  shasum: '5ea87e9b85782e23164705a49cb7bd2dc4063775',
  tarball:
    'https://registry.npmjs.org/nodebook/nodebook-0.9.1.tgz',
  fileCount: 486,
  unpackedSize: 14985184 }

11.2. npx : exécuter un module sans l’installer

L'installation globale est idéale pour disposer d’un module npm sous forme d’exécutable système. On peut cependant vite arriver à en installer beaucoup sans vraiment penser à les enlever quand on n’en a plus besoin.

L’exécutable npx (pour npm executable) s’installe automatiquement avec npm. Il agit comme un raccourci : il va récupérer le module désiré et l’exécute en lui passant les arguments souhaités.

npx cowsay Magique !
npx: installed 10 in 2.122s
 ___________
< Magique ! >
 -----------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

C’est en quelque sorte l’équivalent des trois commandes suivantes :

npm install --global cowsay
cowsay Magique !
npm uninstall cowsay

11.3. npm home : visiter le site web d’un module

Vous vous demandez où trouver davantage de documentation à propos d’un module npm ? npm home ouvre un nouvel onglet de navigateur et dirige ce dernier vers sur le site web du module de votre choix.

npm home lodash
npm home micro

11.4. npm audit : vérifier la sécurité des dépendances

La commande npm audit part à la recherche de vulnérabilités connues dans l’intégralité des dépendances d’un projet.

npm install effectue un audit de manière implicite afin de s’assurer que notre projet n’est pas compromis à notre insu. Les deux dernières lignes sont issues de la fonctionnalité d’audit de sécurité :

npm install lodash@3
+ lodash@3.10.1
added 1 package from 5 contributors in 1.95s
found 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

Un affichage plus détaillé est présenté en exécutant npm audit :

npm audit

Run  npm install lodash@4.17.10  to resolve 1 vulnerability
Recommended action is a potentially breaking change
┌───────────────┬───────────────────────────────────────────┐
│ Low           │ Prototype Pollution                       │
├───────────────┼───────────────────────────────────────────┤
│ Package       │ lodash                                    │
├───────────────┼───────────────────────────────────────────┤
│ Dependency of │ lodash                                    │
├───────────────┼───────────────────────────────────────────┤
│ Path          │ lodash                                    │
├───────────────┼───────────────────────────────────────────┤
│ More info     │ https://nodesecurity.io/advisories/577    │
└───────────────┴───────────────────────────────────────────┘

found 1 low severity vulnerability
  1 vulnerability requires semver-major dependency updates.

Chaque module concerné par une faille connue est listé à l’écran (champ Package). Le champ Path spécifie l’arbre de dépendances qui mène à la vulnérabilité – c’est utile pour identifier quelle dépendance directe actualiser.

La commande npm audit précise la marche à suivre dès qu’elle le peut. Ici, elle indique qu’une mise à jour majeure est nécessaire pour se débarrasser du problème. Cela nécessitera peut-être d’ajuster le code utilisant cette dépendance sous peine de casser notre application.

La commande npm audit fix corrigera toutes les dépendances pour lesquelles il est possible de changer la version de manière automatique et sans risque. Les mises à jour majeures sont toujours manuelles et demandent votre intervention.

11.5. npm clean-install : installer à toute vitesse

La commande npm clean-install (npm ci) est destinée à installer les modules listés dans le fichier package.json. Elle vise à s’exécuter plus rapidement et dans des environnements autres que ceux de développement : en intégration continue, en production, etc.

Cette commande fait une chose de plus que npm install : elle supprime systématiquement le répertoire node_modules pour rendre chaque installation reproductible à l’identique. Elle fait aussi une chose de moins : elle se contente d’installer les modules tels que listés dans le fichier package-lock.json. C’est ce dernier point qui rend cette commande si rapide – moins de vérifications, moins d’allers-retours, moins de complexité.

.travis.yml
language: node_js
node_js: {nodeCurrentVersion}
install: npm ci    <span data-bash-conum="1"></span>
script: npm test
cache: npm         <span data-bash-conum="2"></span>
  1. Surcharge la commande par défaut (npm install)

  2. Les modules npm seront sauvegardés entre deux jobs – l’installation ira plus vite si les modules sont obtenus depuis le cache plutôt que depuis le registre npm.

💡
Pratique Remettre un projet à zéro

La commande npm clean-install est pratique pour remettre un projet à zéro, en cas de problème d’installation ou après avoir bidouillé dans le répertoire node_modules par exemple.

11.6. npm doctor : vérifier l’état du système

npm doctor est une commande utilitaire qui vérifie que npm trouve tout ce qu’il lui faut pour bien fonctionner.

L’exécutable npm inspecte le système à la recherche de Git, teste la connectivité vers le registre npm et s’assure qu’il a accès en écriture à des répertoires essentiels à son bon fonctionnement.

npm doctor
Check                               Value
npm ping                            OK
npm -v                              v6.4.0
node -v                             v10.0.0
npm config get registry             https://registry.npmjs.org
which git                           /usr/local/bin/git
Perms check on cached files         ok
Perms check on global node_modules  ok
Perms check on local node_modules   ok
Verify cache contents               verified 4066 tarballs

11.7. npm config : changer les réglages de l’exécutable npm

La commande npm config affiche et modifie la configuration de l’exécutable npm. Elle se découpe en plusieurs sous-commandes comme en atteste cette tentative d’utilisation :

npm config
npm ERR! Usage:
npm ERR! npm config set  
npm ERR! npm config get []
npm ERR! npm config delete 
npm ERR! npm config list [--json]
npm ERR! npm config edit

La sous-commande get affiche la valeur par défaut d’une clé de configuration :

npm config get loglevel
notice

Cette configuration reflète le degré d’affichage de l’exécutable. Elle agit comme un curseur pour choisir une vue plus ou moins détaillée de ce qui se trame sous le capot.

Augmentons le taux d’affichage avec la sous-commande set :

npm config set loglevel http

Nous voyons désormais les requêtes HTTP lancées (ici, en rejouant l’exemple d'installation globale du module serve) :

npm install --global serve
GET 200 https://registry.npmjs.org/serve 653ms
GET 304 https://registry.npmjs.org/chalk 271ms (from cache)
GET 304 https://registry.npmjs.org/arg 274ms (from cache)
...

La sous-commande ls récapitule tous nos changements de configuration. Elle affiche tous les réglages par défaut en la suffixant de l’option --long :

npm config ls
npm config ls --long
💡
Pratique Sauvegarder sa configuration npm

Chaque appel à npm config set enregistre les changements dans un fichier de configuration ~/.npmrc.

Il est propre à l’utilisateur actif de notre ordinateur. Il vous appartient de le sauvegarder ou d’en fournir un spécifique dans le cadre de votre environnement de production ou d’intégration continue.

Pour y voir plus clair, voici une petite sélection des éléments de configuration que vous pourriez être amené·e à modifier sur votre machine de développement ou sur votre configuration de production :

Tableau 6. Sélection d’éléments de configuration de la commande npm
Clé Valeur par défaut Remarque

access

restricted

Passez à public pour faire en sorte que les modules faisant partie d’une organisation soient considérés comme publics.

audit

true

Passez à false pour désactiver l’audit automatique à chaque installation de module (voir npm audit).

cache

~/.npm

Modifiez le chemin pour que le cache des modules npm soit géré ailleurs.

color

true

Passez à false pour désactiver l’utilisation des couleurs de npm.

depth

Infinity

Le nombre utilisé limitera la profondeur d’affichage des commandes npm ls, npm outdated, etc.

git

git

Nom de l’exécutable ou chemin d’accès de l’exécutable git.

https-proxy

Adresse du proxy HTTPS – remplace alors la variable d’environnement HTTPS_PROXY.

loglevel

notice

Change le taux d’affichage des messages – silent, error, warn diminueront ce taux tandis que http, timing, info, verbose ou silly augmenteront le niveau de détail.

offline

false

Passez à true pour que l’installation des modules npm se fasse sans transiter par le réseau.

progress

true

Passez à false pour désactiver l’affichage de la barre de progression.

proxy

true

Adresse du proxy HTTP – remplace alors la variable d’environnement HTTP_PROXY.

registry

registry.npmjs.org/

Changez cette valeur par celle de votre registre privé ou auto-hébergé.

send-metrics

false

Passez à true pour envoyer des statistiques d’utilisation à l’équipe de npm.

tmp

$TMPDIR

Changez cette valeur pour utiliser un autre répertoire temporaire.

💬
Documentation Tout sur npm config

La page docs.npmjs.com/misc/config#config-settings documente de manière exhaustive toutes les clés de configuration et leur effet sur l’exécutable npm.

11.8. npm publish : publier un module npm

Nous savons comment installer des modules depuis le registre, mais nous n’avons pas encore vu comment contribuer nous-même à cet écosystème.

L’option --dry-run est peut-être la première à utiliser avec cette commande, puisqu’elle fait comme si nous voulions publier le module, mais sans aller jusqu’à téléverser le code sur le registre npm. Je la recommande pour voir de nos propres yeux ce qui serait transmis et rectifier un problème avant qu’il ne se produise – vous n’avez pas envie de mettre en ligne un fichier qui contient un mot de passe n’est-ce pas ?

npm publish --dry-run
npm notice
npm notice 📦  nodebook.chapter-05@1.0.0
npm notice === Tarball Contents ===
npm notice 754B    package.json
npm notice 59B     .eslintrc.yaml
npm notice 59.3kB  index.adoc
npm notice 133B    examples/app.js
npm notice 115B    examples/cow.js
npm notice 65B     examples/hello.js
npm notice 138B    examples/print-args.js
npm notice 223B    examples/tests.js
npm notice 46.3kB  images/module-content.png
npm notice 75.6kB  images/npm-package-falchion.png
npm notice 219.5kB images/npm-package-mysql-libmysqlclient.png
npm notice 170.2kB images/npm-package-mysql2.png
npm notice 172.7kB images/npm-registry-search.png
npm notice === Tarball Details ===
npm notice name:          nodebook.chapter-05
npm notice version:       1.0.0
npm notice package size:  656.8 kB
npm notice unpacked size: 745.1 kB
npm notice shasum:        7f2887b8840124cf8d0c2fa72e8d61cd739
npm notice integrity:     sha512-a6yvb8WO[...]yUeLy2jg/viXQ==
npm notice total files:   13
💡
Configuration Empêcher un module d’être publié

La section de configuration private est à ajouter dans le fichier package.json d’un module pour empêcher toute publication involontaire.

package.json
{
  "name": "...",
  "private": true
}

La publication d’un module implique que vous ayez configuré les sections main ou bin du fichier package.json pour respectivement indiquer quel fichier charger avec require('<module>') ou exécuter avec npx <module>.

La publication d’un module nécessite de se créer un compte sur le registre npmjs.com. Si c’est la première fois, l’exécutable npm vous demandera alors de vous identifier – le module sera ensuite publié en votre nom.

Idéalement, je recommande de ne pas publier de module à la main mais de préférer l’utilisation d’un service d’intégration continue comme Travis CI (travis-ci.com). La configuration d’un tel service permet de publier une nouvelle version seulement si les tests passent au vert.

💡
Pratique Ignorer des fichiers à publier

L’exécutable npm ignore par défaut les mêmes fichiers que Git. Il honore la présence des fichiers .gitignore et exclut les fichiers et répertoires concernés de la publication.

Le fichier .npmignore remplace .gitignore dans le cas où votre besoin de fichiers à versionner est différent de celui de fichiers à publier sur le registre npm.

.npmignore
.DS_Store        
node_modules     

src/*.html       
!src/index.html  
  1. Ignore un fichier nommé .DS_Store – courant sous macOS.

  2. Ignore le répertoire node_modules et tout ce qu’il contient.

  3. Ignore tous les fichiers .html contenus dans le répertoire src.

  4. À l’exception du fichier index.html contenu dans le répertoire src.

11.9. npm version : déterminer une nouvelle version sans se tromper

Nous ne pouvons pas publier deux fois une même version d’un module npm. Nous devons donc a minima modifier la valeur de la section version dans le fichier package.json.

La commande npm version automatise le calcul du prochain numéro de version, reflète ce dernier dans le champ version du fichier package.json et procède à un commit Git, étiqueté avec cette nouvelle version. Je trouve cette manière élégante, notamment en complément de la publication automatique par le biais d’un service d’intégration continue.

La commande npm version se complète forcément d’un argument pour indiquer la granularité de version sémantique concernée. Ainsi, si nous voulons mettre à jour un module actuellement en version 1.0.0 :

  • npm version patch la changera en 1.0.1.

  • npm version minor la changera en 1.1.0.

  • npm version major la changera en 2.0.0.

Cette montée en version se complète optionnellement de scripts npm pour automatiser d’autres actions lors d’une montée en version :

Ordre d’exécution des scripts npm lors d’une montée de version
preversion

Le nouveau numéro de version n’a pas encore été appliqué.

version

Le nouveau numéro de version est appliqué – vous pouvez encore ajouter de nouveaux fichiers au commit Git.

postversion

Le nouveau numéro de version est appliqué et un commit a été ajouté à l’historique Git.

💡
Avancé Déterminer la version depuis Git

Peut-être que vous gérez vous-même le numéro de version en l’attribuant directement avec un tag Git (git tag).

Dans ce cas, la commande npm version from-git reporte le numéro de version du dernier tag Git dans le fichier package.json.

12. Questions et mystères autour de npm

L’exécutable npm et le registre du même nom ont accompagné Node quasiment depuis le début. Il paraît simple de prime abord et je pense que c’est normal de se sentir surpris·e par ses résultats.

Passons en revue des critiques ou questionnements que j’entends régulièrement afin d’y voir plus clair.

12.1. Quand mettre à jour l’exécutable npm ?

De nouvelles versions de l’exécutable npm sont régulièrement publiées. Un message s’affiche dans notre terminal lorsque nous l’utilisons et qu’il détecte qu’une version plus récente est disponible.

╭─────────────────────────────────────╮
│                                     │
│   Update available 6.0.0 → 6.4.0  │
│     Run npm i -g npm to update      │
│                                     │
╰─────────────────────────────────────╯

Le module qui contient l’exécutable npm suit le principe des versions sémantiques. Ainsi, la mise à jour sera sans effort si le numéro majeur reste le même.

J’ai tendance à regarder du côté de github.com/npm/npm/releases pour lire tous les Breaking Changes et comprendre en quoi la mise à jour majeure m’affecte.

12.2. Je ne vois pas l’intérêt du fichier package-lock.json

Le fichier package-lock.json est créé automatiquement par l’exécutable npm dès que vous ajoutez votre première dépendance à un projet.

Jetons un œil à son contenu pour tenter d’en cerner les contours :

{
  "name": "nodebook.chapter-05",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "acorn": {
      "version": "5.6.1",
      "resolved":
        "https://registry.npmjs.org/acorn/-/acorn-5.6.1.tgz",
      "integrity": "sha512-...",
      "dev": true
    },
    "cowsay": {
      "version": "1.3.1",
      "resolved":
        "https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz",
      "integrity": "sha512-...",
      "requires": {
        "get-stdin": "^5.0.1",
        "optimist": "~0.6.1",
        "string-width": "~2.1.1",
        "strip-eof": "^1.0.0"
      }
    },
    ...
  }
}

Il ressemble beaucoup au fichier package.json. On notera qu’il contient exclusivement des données liées aux dépendances, ainsi qu’à toutes les dépendances des dépendances.

Si le fichier package.json contient une version sémantique, package-lock.json contient la version exacte de chaque dépendance ainsi que deux autres types d’informations : l’URL de téléchargement et une signature qui sert à vérifier si le fichier téléchargé est le bon (intégrité).

La représentation complète de l’arbre de dépendances dans le fichier package-lock.json traduit deux intentions : rendre l’installation des dépendances possibles en se basant uniquement sur ce fichier et accélérer le processus d’installation.

💬
Avantages
  • Le projet s’installe encore plus rapidement grâce à la commande npm ci.

  • Nous pouvons reproduire la même installation sur plusieurs ordinateurs.

  • La commande npm install installe plus rapidement en présence d’un fichier package-lock.json.

💬
Inconvénients
  • Nous devons vérifier manuellement s’il y a des patchs avec npm outdated et npm update.

  • C’est une chose de plus à apprendre même si on n’a pas l’intention de l’utiliser.

12.3. npm c’est pour le back-end et bower pour le front-end

bower (bower.io) est un gestionnaire de modules spécialisé dans le développement front-end.

Je pense qu’il était utile à une époque où l’outillage front-end disponible dans Node était encore confidentiel. Je pense aussi que cette époque est révolue, au sens où l’outillage dédié à Node et aux navigateurs web tend à se confondre.

En apprenant ECMAScript, Node et npm, nous gagnons non seulement un outillage disponible immédiatement, mais aussi la capacité à créer le nôtre. Pour en savoir plus sur comment développer pour le front-end comme on développe pour Node, je vous invite à lire le chapitre 9.

💬
Avantages
  • On peut installer un projet sans qu’il possède un fichier package.json.

  • On n’a pas nécessairement besoin de s’outiller pour utiliser les modules Bower.

💬
Inconvénients
  • Si on utilise déjà un fichier package.json pour d’autres besoins, autant l’utiliser pour les dépendances front-end ; donc autant utiliser npm.

  • Le développement de Bower stagne depuis 2015 et je pense que le projet sera arrêté tôt ou tard.

12.4. Est-ce que je dois versionner le répertoire node_modules ?

Le contenu du répertoire node_modules se recrée automatiquement en utilisant l’exécutable npm, que ce soit avec npm install ou npm update. Mieux vaut versionner les fichiers package.json et package-lock.json pour être certain·e de les recréer comme il faut.

Le répertoire node_modules n’a donc pas besoin d’être versionné. Je vous encourage à ajouter node_modules dans le fichier .gitignore. Ce fichier texte se situe en général à la racine de votre projet.

💬
Avantages
  • Je n’en vois pas.

💬
Inconvénients
  • C’est difficile à versionner avec Git en cas de conflit.

  • Ça va exploser si deux personnes utilisent des systèmes d’exploitation différents – certaines dépendances génèrent des fichiers en fonction du système.

  • Ça va exploser s’il manque un module quelque part – et ce sera plus un problème à régler que de ne pas versionner ce répertoire.

12.5. Il paraît que Yarn, c’est mieux

L’application Yarn (yarnpkg.com) veut être une alternative à l’exécutable npm. Le programme vise une installation rapide, hors-ligne et sécurisée.

npm rattrape régulièrement les fonctionnalités qui “donnent de l’avance” à Yarn. Le choix tient donc plutôt du goût ou de l’idéologie. Essayez donc Yarn et gardez-le pour les bonnes raisons – les vôtres.

💬
Avantages
  • Le mode workspace permet de lier plusieurs projets entre eux de manière déclarative.

💬
Inconvénients
  • Il faudra quand même savoir comment fonctionne npm pour les projets qui n’utilisent pas Yarn.

12.6. npm est lent, il installe la moitié d’Internet à chaque fois

L’exécutable npm passe le plus clair de son temps à faire des allers-retours vers le registre npm en utilisant votre connexion Internet. Il téléchargera un module seulement s’il ne l’a pas déjà téléchargé sur un autre projet.

L’équipe de développement de l’exécutable npm travaille à améliorer ses performances et sa qualité d’utilisation. Cette équipe n’a pas d’influence sur les choix faits par les personnes en charge des modules.

Le temps de téléchargement d’un module npm dépend de deux choses : du nombre de dépendances à installer et de leur poids, lequel correspond à la somme des poids des scripts et des ressources additionnelles (images, documentation). Dans les deux cas, plus il y en a, plus l’installation prendra du temps.

Par exemple, le seul ajout de webpack 4 (npmjs.com/webpack) augmente le coût de téléchargement de 14 Mo lors de npm install. Ce n’est pas rien et ce n’est certainement pas la faute de l’exécutable npm.

Le service en ligne Package Phobia (packagephobia.now.sh) garde un historique du poids des modules npm. Celui de webpack se trouve sur packagephobia.now.sh/result?p=webpack.

module content
Figure 5. Coût d’installation du module npm webpack (en foncé) et de ses dépendances (en clair)
💡
Pratique Connaître le coût des dépendances de son projet

Le module npm cost-of-modules (npmjs.com/cost-of-modules) calcule la quantité et le poids des dépendances listées dans un fichier package.json.

C’est pratique pour identifier quel module remplacer par un autre, plus léger et plus rapide à installer.

npx cost-of-modules                
┌───────────┬─────────────┬───────┐
│ name      │ children    │ size  │
├───────────┼─────────────┼───────┤
│ lodash    │ 0           │ 1.34M │
├───────────┼─────────────┼───────┤
│ micro     │ 19          │ 0.67M │
├───────────┼─────────────┼───────┤
│ cowsay    │ 9           │ 0.22M │
├───────────┼─────────────┼───────┤
│ 3 modules │ 28 children │ 2.22M │
└───────────┴─────────────┴───────┘
  1. La commande npx est un raccourci pour exécuter des modules npm sans les installer.

12.7. Que signifient les erreurs affichées pendant npm install ?

L’exécutable npm est généreux en messages pendant l’installation de modules. C’est parfois difficile à lire, notamment pour comprendre la raison du message et la solution à apporter.

Si npm WARN s’affiche, ce n’est pas une erreur mais un message à caractère informatif.
Si npm ERR débute la ligne, il y a un problème sur lequel nous avons une action immédiate à mener.

12.7.1. Module déprécié

Un module est déprécié quand il n’est plus maintenu, s’il est développé sous un nouveau nom ou si nous sommes encouragé·e·s à faire une mise à jour majeure.

Un module déprécié ne nous regarde pas sauf s’il est listé dans le champ dependencies ou devDependencies d’un fichier package.json.

Exemple d’encouragements à utiliser un autre module
npm WARN deprecated babel-preset-es2017@6.24.1:
  Thanks for using Babel: we recommend using babel-preset-env
  now: please read babeljs.io/env to update!

npm WARN deprecated babel-preset-babili@0.0.10: babili has
  been renamed to babel-minify.
  Please update to babel-preset-minify
Exemple de module qui n’est plus maintenu
npm WARN deprecated nomnom@1.6.2: Package no longer supported.
  Contact support@npmjs.com for more info.

Un module qui n’est plus maintenu ne recevra probablement plus de mises à jour. Il vaut mieux dans ce cas en trouver un autre qui fait plus ou moins la même chose.

12.7.2. Problème avec une dépendance optionnelle

Certains modules effectuent une opération de compilation : une partie de leur code source est écrit dans un autre langage que l’ECMAScript et ils font en sorte de créer un pont avec Node.

Il arrive que l’opération de compilation n’aboutisse pas pour diverses raisons – logiciel manquant, incompatibilité avec le système d’exploitation ou avec l’architecture CPU.

Le fait de voir écrit SKIPPING et OPTIONAL me laisse penser que ce n’est pas grave si l’opération ne se passe pas comme prévu.

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.1.3
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY:
  Unsupported platform for fsevents@1.1.3:
  wanted {"os":"darwin","arch":"any"}
  (current: {"os":"win32","arch":"x64"})

12.7.3. Module introuvable

Le module que vous cherchez à installer n’existe pas. Il s’agit peut-être d’une erreur de frappe ou alors le module a été retiré de la circulation.

npm i aria-roless
npm ERR! code E404
npm ERR! 404 Not Found: aria-roless@latest

12.7.4. Caractère de fin de ligne sous Windows

Les anciennes versions de npm avaient du mal à concilier les caractères de fin de ligne sous Windows (\r\n), différents des autres systèmes (\n).

npm error Expected linebreaks to be 'LF' but
  found 'CRLF' linebreak-style

Mettez npm à jour vers une version plus récente pour régler le problème.

12.7.5. Fichier package.json incomplet

Les messages suivants s’affichent quand les champs description et repository manquent à l’appel de notre fichier package.json.

npm WARN tmp@1.0.0 No description
npm WARN tmp@1.0.0 No repository field.

Référez-vous à la section “Anatomie du fichier package.json” pour savoir comment remplir ces champs manquants.

12.7.6. Dépendance complémentaire à installer

Certains modules nécessitent des modules complémentaires pour fonctionner. Toutefois ces derniers sont à installer manuellement. C’est la signification du message d’erreur suivant :

npm WARN react-power-picture@1.0.0 requires a peer of
  react@^15.0.0-0 || ^16.0.0-0 but none is installed.
  You must install peer dependencies yourself.

Cet exemple indique que nous avons installé le module npm react-power-picture et que le module complémentaire react est nécessaire mais que nous ne l’avons pas installé.

Si vous pensez que c’est une erreur ou une incompréhension, désinstallez le module et cherchez une alternative. Cela se produit généralement quand on ne s’aperçoit pas qu’un module est dédié à un certain framework – qu’on ne veut pas utiliser.

13. Conclusion

L’exécutable npm est un outil qui va bien au-delà de la simple installation de modules : il va jusqu’à créer un outillage autonome pour chacun de nos projets.

Nous avons appris à jongler entre les différentes versions d’un module pour comprendre la notion de version sémantique et son effet sur les commandes d’installation et de mise à jour.

Nous avons vu que les scripts npm représentent un outillage à portée de main. Ils nous facilitent la vie en plus d’être partagés avec les personnes impliquées dans un même projet.

Avec le langage ECMAScript (chapitre 3), l’environnement Node (chapitre 4) et maintenant npm, nous avons des fondations solides pour déployer du code (chapitre 6) et créer toutes sortes d’applications ECMAScript.