La commande npm install et les gestionnaires de paquets : guide complet

Si vous avez déjà développé avec l'aide d'un framework JavaScript, alors vous avez sûrement déjà lancé la fameuse commande `npm install`. Mais est-ce que vous savez réellement ce qu'il se passe à ce moment-là ? Comment cette simple commande fait pour gérer vos 783 dépendances qui elles-mêmes vont installer d'autres dépendances qui elles-mêmes vont installer d'autres… (`InternalError: too much recursion`). La réponse est dans cet article !
HeadersBlog.png
ed419141-e471-463d-89a3-bfbd8d5275be.jpeg
Antoine CHATAIGNIERlogo Linkedin
Développeur Fullstack, passionné par les univers JS et Java ! Mis à jour le 22 Janv 2025

17+

ans
d'experience

60+

experts
techniques

100K

écoutes de notre podcast
logo Axopen

C'est quoi un gestionnaire de paquets ?

Pour faire simple, un gestionnaire de paquets comme npm est un outil qui automatise le téléchargement, l'installation et la gestion des bibliothèques logicielles nécessaires à un projet. Ces bibliothèques, appelées "paquets" ou "dépendances", contiennent du code réutilisable qui simplifie le développement. En une seule commande, vous pouvez ajouter de nouvelles fonctionnalités à votre application, mettre à jour des outils ou sécuriser vos dépendances.

Que veut dire npm ?

La question peut paraître simple, mais elle n'en reste pas moins intéressante. Si l'on en croit le discours officiel (le README.md actuel), npm ne signifierait pas "Node Package Manager", contrairement à ce que beaucoup pourraient penser, mais serait plutôt inspiré d'un utilitaire batch appelé pkgmakeinst, abrégé en pm, auquel les créateurs de npm auraient ajouté un n, pour node ou new.

Cependant, cette histoire reste controversée. En effet, dans les premières versions du README.md, il était pourtant clairement écrit "Node Package Manager". Est-ce donc une erreur ? Un mensonge ? Un complot ?

Chacun est libre de choisir la version qui lui conviendra le mieux 😉

Pourquoi utiliser un gestionnaire de paquets ?

Un gestionnaire de paquets (ou « package manager ») simplifie la gestion des dépendances d'un projet. Une dépendance est une bibliothèque réutilisable, souvent optimisée, qui peut être gratuite ou payante. Les gestionnaires comme npm permettent de :

  • Télécharger des paquets ;
  • Gérer les versions ;
  • Identifier des problèmes de sécurité.

Voici comment installer simplement un paquet avec npm :

npm install monsuperpackage #récupère la dernière version disponible
npm install monsuperpackage@version #récupère une version spécifique

Ces commandes téléchargent le package depuis le registre npm (ou un registre personnalisé) et le stockent dans le dossier node_modules. Cela nous permet ensuite, à nous développeurs, d'utiliser ces fonctions dans notre code :

import { maSuperFonction } from 'monsuperpackage';

console.log(maSuperFonction());

Le véritable intérêt dans les gestionnaires de paquets réside dans leur capacité à gérer l'imbrication des dépendances. Lorsque les paquets dépendent eux-mêmes d'autres paquets qui dépendent eux-mêmes d'autres paquets, la gestion manuelle devient vite un cauchemar.

Comment gérer plusieurs paquets avec package.json ?

Le fichier package.json est un élément clé des frameworks modernes.

Dans le fichier suivant, on retrouve les dépendances nécessaires au projet, mais aussi celles nécessaires uniquement au développement (on peut y retrouver par exemple les dépendances liées à la gestion du lint, à la gestion des types etc..) :

{
  "name": "monprojet",
  "version": "0.0.0",
  "type": "module",
  "dependencies": {
    "vue": "^3.5.12"
  },
  "devDependencies": {
    "typescript": "~5.6.2"
  }
}

Il suffit ensuite de lancer une commande npm install pour que le gestionnaire de paquet s'occupe d'installer toutes ces dépendances.

Cette commande crée également un fichier package-lock.json, permettant d'enregistrer les versions exactes des dépendances et leurs sous-dépendances pour garantir une reproductibilité parfaite.

npm, comment ça marche ?

Initialement, npm utilisait une structure en arbre pour gérer les dépendances. Chaque paquet était téléchargé et stocké dans le dossier node_modules du projet. Ensuite, pour chaque paquet, un sous-dossier node_modules était créé pour contenir ses propres dépendances.

On pouvait alors obtenir ce genre d'architecture en arbre (tree) :

/node_modules (dépendances du projet)
  /A
    /node_modules (dépendances de A)
      /B
      /D
  /C
    /node_modules (dépendances de C)
      /D
          /node_modules (dépendances de D)
      /E

Mais cette architecture menait à beaucoup de duplications inutiles. C'est pourquoi aujourd'hui, npm privilégie une structure à plat (flat) où les paquets sont installés au même niveau dans le dossier node_modules :

/node_modules
  /A
  /B
  /C
  /D

C'est plus simple déjà non ? 🙂

Alors pourquoi n'ont-ils pas fait ça depuis le début ?

La gestion des versions

Comme rien n'est vraiment simple, un gestionnaire de paquet ne se content pas uniquement de gérer les paquets, mais aussi leurs versions. Imaginez un super projet sans bug, avec une performance top et un clean code impeccable. Vous êtes prêt à l'envoyer en prod quand, à H-4, un développeur en charge d'une dépendance majeure de votre projet décide de publier une nouvelle version qui va vous demander des jours de modifications. Le versioning des dépendances permet d'éviter ce genre de scénario catastrophe, mais cela complexifie forcément beaucoup le travail du gestionnaire de dépendances.

Imaginons les dépendances suivantes : A{B,C}, B{C,D@1}, C{D@2} (le @ symbolise la version).

Voici à quoi ressemblerait l'arbre de nos dépendances : Image not found

d9cc9025-5ba7-4cda-bac0-a6b957cddab9.webp

Ici, notre gestionnaire de paquet va installer les paquet dans l'ordre suivant :

  1. A
  2. B et C (car dépendance de A)
  3. C et D@1 (car dépendance de B)
  4. D@2 (car dépendance de C)

Pour obtenir une hierarchie adaptée tout en évitant la duplication, les paquets seront d'abord installés à la racine du dossier sauf quand une autre version du paquet existe déjà.

C'est le cas quand dans notre exemple quand on arrive au paquet D@2. Dans ce cas, un nouveau dossier node_modules sera alors créé dans le dossier du paquet C, créant ainsi une hiérarchie adaptée :

/node_modules
  /A
  /B
  /C
    /node_modules
      /D@2
  /D@1

C'est cette architecture exacte qui va être sauvegardée dans le fichier protégé package-lock.json.

Voilà pourquoi il ne faut jamais le supprimer sous peine de se retrouver avec des conflits de dépendances ou des versions différentes (dépendamment de si vous avez mis un ^ , un ~ ou bien aucun symbole devant le numéro de version de vos dépendances).

Alternatives à npm : Yarn et Pnpm

Avec ce que l'on vient de voir plus haut, on pourrait se demander pourquoi npm a été adopté aussi rapidement par une grande partie des frameworks et des nouveaux projets et pourquoi différents acteurs n'ont pas essayé de développer leur propre gestionnaire de paquet.

En réalité, concevoir un tel outil est une tâche bien plus complexe qu'il n'y paraît. Créer un système capable de gérer efficacement un réseau dense de dépendances tout en garantissant des performances optimales est un véritable défi technique. Lorsqu'on y ajoute des exigences de robustesse, de sécurité et d'adoption à grande échelle, la tâche devient tout simplement titanesque.

Ces dernières années, nous avons tout de même pu voir émerger de nouveaux outils apportant chacun de nouvelles idées ou de nouvelles optimisations pour se démarquer. On peut citer par exemple les deux autres grands noms dans l'univers des gestionnaire de paquets : yarn et pnpm.

Yarn

Créé par Facebook en 2016, Yarn (pour Yet Another Resource Negotiator) améliore npm sur plusieurs aspects, notamment en rendant l'installation des dépendances plus rapide grâce à un téléchargement en parallèle et un cache optimisé. Depuis Yarn 2.0 (aussi appelé Yarn Berry), le célèbre dossier node_modules est remplacé par un fichier .pnp.cjs, réduisant ainsi l'utilisation de l'espace disque. Cette nouvelle version inclue aussi une stratégie PnP (Plug'n'Play) qui utilise une approche de lien symboliques similaire à pnpm.

Pnpm

Pnpm se démarque des autres acteurs en stockant les paquets dans un espace partagé plutôt que dans un dossier dédié au sein de chaque projet. Il utilise des liens symboliques pour éviter les duplications et réduire l'utilisation de la mémoire. Cette méthode est particulièrement appréciée pour les projets multi-repo ou monorepo.

Il existe plusieurs benchmarks effectués sur différents gestionnaires de paquets. On peut retrouver ici les données d'un benchmark proposé par Cookielab.io dans cet article publié il y a presque un an. 

b0c8e4e3-1c80-4082-9c4d-7eabb91a1149.png

On y voit notamment la supériorité de Yarn Berry ou pnpm sur npm, mais si la vitesse compte, les habitudes des développeurs ont aussi la vie dure, c'est pourquoi npm reste aujourd'hui encore sur-représenté dans les nouveaux projets.

Les gestionnaires de paquets, piliers du développement moderne

Il est indéniable que les gestionnaires de paquets (que ce soit npm, yarn, pnpm ou d'autres) ont transformé le développement moderne, en démocratisant l'accès aux bibliothèques tierces et en simplifiant considérablement la gestion des dépendances. Mais comment garantir la pérennité de ces ressources open source, souvent développées et maintenues par un petit nombre de contributeurs, voire un seul développeur ?

L'open source offre un formidable levier pour l'innovation, en permettant à chacun d'apporter de nouvelles solutions et en favorisant la réutilisation de briques logicielles performantes. Mais cette philosophie ne perd-elle pas de sa substance lorsqu'une très grande entreprise profite de ce système pour forker ou exploiter à des fins commerciales des composants développés par des bénévoles, sans rien apporter en retour ?

Chaque développeur, qu'il utilise ou non des briques open-source, est libre de contribuer aux différents projets en proposant des améliorations, des fix ou en débattant avec la communauté dans les messages d'une issue ou d'une PR. C'est une chance, alors profitons-en 😊