Yarn : Un nouveau gestionnaire de paquets pour JavaScript – Facebook Engineering
Dans la communauté JavaScript, les ingénieurs partagent des centaines de milliers de morceaux de code afin d’éviter de réécrire des composants de base, des bibliothèques ou des frameworks qui nous sont propres. Chaque morceau de code peut à son tour dépendre d’autres morceaux de code, et ces dépendances sont gérées par des gestionnaires de paquets. Le gestionnaire de paquets JavaScript le plus populaire est le client npm, qui donne accès à plus de 300 000 paquets dans le registre npm. Plus de 5 millions d’ingénieurs utilisent le registre npm, qui voit jusqu’à 5 milliards de téléchargements chaque mois.
Nous utilisons le client npm avec succès chez Facebook depuis des années, mais à mesure que la taille de notre base de code et le nombre d’ingénieurs augmentaient, nous avons rencontré des problèmes de cohérence, de sécurité et de performance. Après avoir essayé de résoudre chaque problème au fur et à mesure qu’il se présentait, nous avons entrepris de construire une nouvelle solution pour nous aider à gérer nos dépendances de manière plus fiable. Le produit de ce travail s’appelle Yarn – un client npm alternatif rapide, fiable et sécurisé.
Nous sommes heureux d’annoncer la sortie en open source de Yarn, une collaboration avec Exponent, Google et Tilde. Avec Yarn, les ingénieurs ont toujours accès au registre npm, mais peuvent installer des paquets plus rapidement et gérer les dépendances de manière cohérente sur plusieurs machines ou dans des environnements hors ligne sécurisés. Yarn permet aux ingénieurs d’avancer plus rapidement et en toute confiance lorsqu’ils utilisent du code partagé, afin qu’ils puissent se concentrer sur ce qui compte : la création de nouveaux produits et fonctionnalités.
L’évolution de la gestion des paquets JavaScript chez Facebook
Avant les gestionnaires de paquets, il était courant que les ingénieurs JavaScript s’appuient sur un petit nombre de dépendances stockées directement dans leurs projets ou servies par un CDN. Le premier grand gestionnaire de paquets JavaScript, npm, a été construit peu après l’introduction de Node.js, et il est rapidement devenu l’un des gestionnaires de paquets les plus populaires au monde. Des milliers de nouveaux projets open source ont été créés et les ingénieurs ont partagé plus de code que jamais auparavant.
Plusieurs de nos projets chez Facebook, comme React, dépendent du code du registre npm. Cependant, à mesure que nous nous développions en interne, nous avons été confrontés à des problèmes de cohérence lors de l’installation des dépendances sur différentes machines et différents utilisateurs, au temps nécessaire pour tirer les dépendances, et nous avons eu quelques problèmes de sécurité avec la façon dont le client npm exécute automatiquement le code de certaines de ces dépendances. Nous avons tenté de construire des solutions autour de ces problèmes, mais elles ont souvent soulevé elles-mêmes de nouveaux problèmes.
Tentatives de mise à l’échelle du client npm
Au départ, en suivant les meilleures pratiques prescrites, nous avons seulement vérifié package.json
et demandé aux ingénieurs d’exécuter manuellement npm install
. Cela fonctionnait assez bien pour les ingénieurs, mais tombait en panne dans nos environnements d’intégration continue, qui doivent être sandboxés et coupés d’Internet pour des raisons de sécurité et de fiabilité.
La solution suivante que nous avons mise en œuvre consistait à vérifier tous les node_modules
dans le référentiel. Bien que cela ait fonctionné, cela a rendu certaines opérations simples assez difficiles. Par exemple, la mise à jour d’une version mineure de babel générait un commit de 800 000 lignes difficile à atterrir et déclenchait des règles de lint pour des séquences d’octets utf8 invalides, des fins de lignes de fenêtres, des images non écrasées en png, et plus encore. La fusion des modifications apportées à node_modules
prenait souvent une journée entière aux ingénieurs. Notre équipe de contrôle des sources a également souligné que notre dossier node_modules
vérifié était responsable d’une énorme quantité de métadonnées. Le package.json
de React Native ne répertorie actuellement que 68 dépendances, mais après avoir exécuté npm install
, le répertoire node_modules
contient 121 358 fichiers.
Nous avons fait une dernière tentative pour faire évoluer le client npm afin qu’il fonctionne avec le nombre d’ingénieurs chez Facebook et la quantité de code que nous devons installer. Nous avons décidé de zipper l’ensemble du dossier node_modules
et de le télécharger sur un CDN interne afin que les ingénieurs et nos systèmes d’intégration continue puissent télécharger et extraire les fichiers de manière cohérente. Cela nous a permis de supprimer des centaines de milliers de fichiers du contrôle de la source, mais a fait en sorte que les ingénieurs aient besoin d’un accès Internet non seulement pour tirer le nouveau code, mais aussi pour le construire.
Nous avons également dû contourner des problèmes avec la fonctionnalité shrinkwrap de npm, que nous utilisions pour verrouiller les versions de dépendance. Les fichiers shrinkwrap ne sont pas générés par défaut et se désynchronisent si les ingénieurs oublient de les générer, nous avons donc écrit un outil pour vérifier que le contenu du fichier shrinkwrap correspond à ce qui se trouve dans node_modules
. Ces fichiers sont d’énormes blobs JSON avec des clés non triées, cependant, de sorte que les modifications apportées à ces fichiers généreraient des commits massifs et difficiles à réviser. Pour atténuer cela, nous avons dû ajouter un script supplémentaire pour trier toutes les entrées.
Enfin, la mise à jour d’une seule dépendance avec npm met également à jour de nombreuses dépendances non liées en fonction des règles de versionnement sémantique. Cela rend chaque changement beaucoup plus important que prévu, et le fait de devoir faire des choses comme commiter node_modules
ou le télécharger sur un CDN rendait le processus moins qu’idéal pour les ingénieurs.
Construire un nouveau client
Plutôt que de continuer à construire une infrastructure autour du client npm, nous avons décidé d’essayer d’envisager le problème de manière plus holistique. Et si, à la place, nous essayions de construire un nouveau client qui répondrait aux problèmes fondamentaux que nous rencontrions ? Sebastian McKenzie, dans notre bureau de Londres, a commencé à hacker sur cette idée et nous avons rapidement été enthousiasmés par son potentiel.
Tandis que nous travaillions sur ce sujet, nous avons commencé à parler avec des ingénieurs à travers l’industrie et nous avons constaté qu’ils étaient confrontés à un ensemble similaire de problèmes et avaient tenté plusieurs des mêmes solutions, souvent axées sur la résolution d’un seul problème à la fois. Il est devenu évident qu’en collaborant sur l’ensemble des problèmes auxquels la communauté était confrontée, nous pourrions élaborer une solution qui conviendrait à tous. Avec l’aide d’ingénieurs d’Exponent, de Google et de Tilde, nous avons développé le client Yarn, puis testé et validé ses performances sur tous les principaux frameworks JS et pour des cas d’utilisation supplémentaires en dehors de Facebook. Aujourd’hui, nous sommes ravis de le partager avec la communauté.
Introducing Yarn
Yarn est un nouveau gestionnaire de paquets qui remplace le flux de travail existant pour le client npm ou d’autres gestionnaires de paquets tout en restant compatible avec le registre npm. Il possède le même ensemble de fonctionnalités que les flux de travail existants tout en fonctionnant plus rapidement, de manière plus sécurisée et plus fiable.
La fonction principale de tout gestionnaire de paquets est d’installer un certain paquet – un morceau de code qui sert un objectif particulier – à partir d’un registre global dans l’environnement local d’un ingénieur. Chaque paquet peut dépendre ou non d’autres paquets. Un projet typique pourrait avoir des dizaines, des centaines, voire des milliers de paquets dans son arbre de dépendances.
Ces dépendances sont versionnées et installées sur la base du versionnement sémantique (semver). Semver définit un schéma de versionnement qui reflète les types de changements dans chaque nouvelle version, qu’un changement casse une API, ajoute une nouvelle fonctionnalité ou corrige un bogue. Cependant, semver s’appuie sur le fait que les développeurs de paquets ne font pas d’erreurs – les changements de rupture ou les nouveaux bogues peuvent se retrouver dans les dépendances installées si les dépendances ne sont pas verrouillées.
Architecture
Dans l’écosystème Node, les dépendances sont placées dans un node_modules
répertoire dans votre projet. Cependant, cette structure de fichiers peut différer de l’arbre de dépendances réel, car les dépendances dupliquées sont fusionnées ensemble. Le client npm installe les dépendances dans le répertoire node_modules
de manière non déterministe. Cela signifie que selon l’ordre d’installation des dépendances, la structure d’un répertoire node_modules
pourrait être différente d’une personne à l’autre. Ces différences peuvent provoquer des bogues « qui fonctionnent sur ma machine » qui prennent beaucoup de temps à traquer.
Yarn résout ces problèmes autour du versionnage et du non-déterminisme en utilisant des lockfiles et un algorithme d’installation qui est déterministe et fiable. Ces lockfiles verrouillent les dépendances installées à une version spécifique, et garantissent que chaque installation aboutit exactement à la même structure de fichiers dans node_modules
sur toutes les machines. Le fichier de verrouillage écrit utilise un format concis avec des clés ordonnées pour s’assurer que les changements sont minimes et que la révision est simple.
Le processus d’installation est décomposé en trois étapes :
- Résolution : Yarn commence à résoudre les dépendances en faisant des demandes au registre et en recherchant récursivement chaque dépendance.
- Récupération : Ensuite, Yarn regarde dans un répertoire de cache global pour voir si le paquet nécessaire a déjà été téléchargé. Si ce n’est pas le cas, Yarn va chercher le tarball du paquet et le place dans le cache global pour pouvoir travailler hors ligne et ne pas avoir à télécharger les dépendances plus d’une fois. Les dépendances peuvent également être placées dans le contrôle de source en tant que tarballs pour des installations hors ligne complètes.
- Lien : Enfin, Yarn lie tout ensemble en copiant tous les fichiers nécessaires du cache global dans le répertoire local
node_modules
.
En décomposant ces étapes proprement et en ayant des résultats déterministes, Yarn est capable de paralléliser les opérations, ce qui maximise l’utilisation des ressources et rend le processus d’installation plus rapide. Sur certains projets Facebook, Yarn a réduit le processus d’installation d’un ordre de grandeur, passant de plusieurs minutes à quelques secondes seulement. Yarn utilise également un mutex pour s’assurer que plusieurs instances CLI en cours d’exécution n’entrent pas en collision et ne se polluent pas mutuellement.
Pendant tout ce processus, Yarn impose des garanties strictes autour de l’installation des paquets. Vous avez le contrôle sur quels scripts de cycle de vie sont exécutés pour quels paquets. Les sommes de contrôle des paquets sont également stockées dans le fichier de verrouillage pour s’assurer que vous obtenez le même paquet à chaque fois.
Caractéristiques
En plus de rendre les installations beaucoup plus rapides et plus fiables, Yarn dispose de fonctionnalités supplémentaires pour simplifier davantage le flux de gestion des dépendances.
- Compatibilité avec les flux de travail npm et bower et prise en charge des registres de mélange.
- Possibilité de restreindre les licences des modules installés et un moyen de sortir les informations de licence.
- Expose une API JS publique stable avec une journalisation abstraite pour la consommation via les outils de construction.
- Sortie CLI lisible, minimale et jolie.
Yarn en production
À Facebook, nous utilisons déjà Yarn en production, et il a très bien fonctionné pour nous. Il alimente la gestion des dépendances et des paquets pour un grand nombre de nos projets JavaScript. Avec chaque migration, nous avons permis aux ingénieurs de construire hors ligne et contribué à accélérer leur flux de travail. You can see how install times for Yarn and npm compare on React Native under different conditions, which you can find here.
Getting started
The easiest way to get started is to run:
npm install -g yarn
yarn
The yarn
CLI replaces npm
in your development workflow, either with a matching command or a new, similar command:
-
npm install
yarn
With no arguments, the
yarn
command will read yourpackage.json
, fetch packages from the npm registry, and populate yournode_modules
folder. It is equivalent to runningnpm install
. -
npm install --save <name>
yarn add <name>
We removed the « invisible dependency » behavior of
npm install
and split the command. Running<name>
yarn add
is equivalent to running<name>
npm install --save
.<name>
Future
Many of us came together to build Yarn to solve common problems, and we knew that we wanted Yarn to be a true community project that everyone can use. Yarn is now available on GitHub and we’re ready for the Node community to do what it does best: Use Yarn, share ideas, write documentation, support each other, and help build a great community to care for it. We believe that Yarn is already off to a great start, and it can be even better with your help.