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.
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 😉
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 :
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.
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.
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 ?
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 :
Ici, notre gestionnaire de paquet va installer les paquet dans l'ordre suivant :
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).
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.
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 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.
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.
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 😊
De plus en plus de serveurs sont attaqués par du flooding HTTP, mettant à genou votre serveur Apache, l’empêchant ainsi de répondre aux vraies requêtes qui lui sont adressées.
Retour sur notre projet de création d'extranet
Découvrez la planche #52 !