Skip to main content

Les plugins Yarn 2 : une introduction

Écrit par
Plugin feature

13 avril 2020

0 minutes de lecture

Si vous souhaitez en savoir plus sur Yarn 2 et son architecture de plugins, commencez par consulter la documentation consacrée à cette architecture sur yarnpkg.com, puis suivez le tutoriel sur les plugins Yarn 2 disponible sur le site.

Si vous découvrez Yarn 2, je vous recommande vivement de lire mon article d’introduction à Yarn 2.

Créer un plugin pour Yarn 2

Commençons par configurer un environnement pour créer le plugin. Pour cela, créons un nouveau projet qui sera géré avec Yarn 2 :

mkdir my-app
cd my-app
yarn policies set-version berry
yarn init

Nous disposons ainsi d’un projet npm vierge géré avec Yarn 2.

Pour indiquer à Yarn que nous voulons qu’un plugin soit exécuté dans le cadre de son processus, créons un fichier nommé .yarnrc.yml et ajoutons-y la définition du plugin, avec le chemin d’accès au plugin que nous allons développer :

plugins:
 - ./plugin-hook-install-hello-world.js

Passons maintenant au code du plugin. Créons quelque chose qui s’accroche à une phase du processus de Yarn, appelée afterAllInstalled, puis affiche un message dans la console. Le code du plugin se présente alors comme suit :

const afterAllInstalled = project => {
 console.log("Everything is installed 🎉");
};

module.exports = {
 name: `plugin-hello-world`,
 factory: require => {
   return {
     default: {
       hooks: {
         afterAllInstalled
       }
     }
   };
 }
};

Pour tester notre plugin, exécutez simplement la commande yarn depuis l’interface de ligne de commande : le message suivant devrait s’afficher :

➜ yarn
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed in 0s
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 0s
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed in 0.01s
Everything is installed 🎉
➤ YN0000: Done in 0.04s

Génial, nous avons maintenant notre propre plugin Yarn 2 !

Les plugins Yarn 2 peuvent remplir de nombreuses fonctions : ajouter une commande à l’exécutable de yarn, créer de nouveaux résolveurs ou récupérateurs tels que Yarn les appelle, et même interconnecter des plugins yarn !

Pour apprendre à créer un plugin Yarn 2 qui ajoute des commandes à Yarn, consultez la documentation officielle sur les plugins Yarn, qui vous guidera pas à pas.

Utiliser un plugin Yarn 2 pour accéder à des informations détaillées sur les dépendances

Comme nous venons de le voir dans l’exemple précédent, certains hooks de Yarn nous permettent de nous intégrer plus étroitement en fournissant un objet public global contenant des métadonnées détaillées que Yarn collecte sur le projet.

Dans l’exemple suivant, nous nous intégrons à nouveau au hook afterAllInstalled, qui

est appelé avec un paramètre supplémentaire : l’objet public Project. Grâce à ce paramètre, nous pouvons accéder à toutes les informations recueillies par Yarn sur le projet : dépendances, manifeste du package, informations sur les espaces de travail, etc.

Dans l’exemple de plugin suivant, plugin-hook-install-hello-brave-world.js, nous stockons les informations de cet objet dans la variable _ et écrivons son contenu dans un fichier JSON afin de pouvoir l’examiner plus en détail :

const fs = require("fs");
const util = require("util");

module.exports = {
 name: `plugin-hello-brave-world`,
 factory: require => {
   return {
     default: {
       hooks: {
         afterAllInstalled(_) {
           console.log("🎉 afterAllInstalled hook invoked");
           fs.writeFileSync("afterAll.json", util.inspect(_, false, 10));
         }
       }
     }
   };
 }
};

La structure de l’objet Project est définie dans packages/berry-core/sources/Project.ts. Voici une liste partielle des clés de premier niveau de cet objet :

Project {
 configuration: {},
 cwd: {},
 workspaces: Map {},
 storedResolutions: Map {},
 storedDescriptors: Map {},
 storedPackages: Map {},
 storedChecksums: Map {},
 ...
}

Ces clés stored* vont nous aider à comprendre les dépendances et à construire l’arborescence de ce projet.

Pour simplifier, nous allons exécuter ce plugin sur un projet npm qui n’a qu’une seule dépendance : debug. À son tour, debug dépend également de ms.

L’objet storedPackages nous permet d’obtenir la liste des dépendances de ce projet. Notez qu’il contient également une entrée pour le nom du projet lui-même, désignée par la référence : workspace: key.

Voyons à quoi ressemble la dépendance debug dans la Map storedPackages :

'a1f870a4a95fff67eeac2388e06345982ebf1149c710f8efe18fe5d1967fec40db60987022a5c6fe55708167d1add4b63bc8ad6f755e20c6fba140d721180595' => { identHash:
       'd027b0b474dd440d333c0ae6200111acff30aa5931aeacf1841b1eb9212edea377606a118ff0f8675b69eabe9ff00db4f2f16659519c82810c6b534f9b8ad82d',
      scope: undefined,
      name: 'debug',
      locatorHash:
       'a1f870a4a95fff67eeac2388e06345982ebf1149c710f8efe18fe5d1967fec40db60987022a5c6fe55708167d1add4b63bc8ad6f755e20c6fba140d721180595',
      reference: 'npm:4.1.1',
      version: '4.1.1',
      languageName: 'node',
      linkType: 'hard',
      dependencies:
       Map {
         '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6' => { identHash:
            '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6',
           scope: undefined,
           name: 'ms',
           descriptorHash:
            '0742408cf974a8f1cd5081a9aec19656dc8016eec3c6e2358f302902e6f1f87241f601911cb45e855f3b0d26160c1520715ae79904c5ed1d29f5383ab906440e',
           range: 'npm:^2.1.1' } },
      peerDependencies: Map {},
      dependenciesMeta: Map {},
      peerDependenciesMeta: Map {},
      bin: Map {} },
    '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33' => { identHash:
       '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6',
      scope: undefined,
      name: 'ms',
      locatorHash:
       '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33',
      reference: 'npm:2.1.2',
      version: '2.1.2',
      languageName: 'node',
      linkType: 'hard',
      dependencies: Map {},
      peerDependencies: Map {},
      dependenciesMeta: Map {},
      peerDependenciesMeta: Map {},
      bin: Map {} },

L’entrée debug possède un hash unique qui permet de l’identifier, des métadonnées comme la version résolue, ainsi qu’un autre objet de dépendances imbriqué qui répertorie les dépendances de debug. Vous remarquerez toutefois que ms, présent dans cet objet de dépendances imbriqué, n’est pas résolu : il est uniquement désigné par une plage de versions.

Pour résoudre les dépendances imbriquées, nous devons utiliser descriptorHash et consulter la map storedResolutions, qui se présente comme suit :

storedResolutions:
  Map {
    '35f50d92512bedba8fbf78bdeae4f2bce60934a798f5e0a0ab58b087fb7dc73880c7ffee2e135c15e48ca70687336bbdf4163e75da3f90b55da5ba5e41d36051' => '35f50d92512bedba8fbf78bdeae4f2bce60934a798f5e0a0ab58b087fb7dc73880c7ffee2e135c15e48ca70687336bbdf4163e75da3f90b55da5ba5e41d36051',
    'ee78c55248c8a07a4079ce749bc462124c923d7b990785b50150c417b2821ca3363767b8dc304d80d4feae3faf9f95e1c2c46cebc582b81fe70a8f45a69b6377' => 'a1f870a4a95fff67eeac2388e06345982ebf1149c710f8efe18fe5d1967fec40db60987022a5c6fe55708167d1add4b63bc8ad6f755e20c6fba140d721180595',
    '0742408cf974a8f1cd5081a9aec19656dc8016eec3c6e2358f302902e6f1f87241f601911cb45e855f3b0d26160c1520715ae79904c5ed1d29f5383ab906440e' => '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33' },

Nous trouvons le descriptorHash de ms, soit 0742408cf974a8f1cd5081a9aec19656dc8016eec3c6e2358f302902e6f1f87241f601911cb45e855f3b0d26160c1520715ae79904c5ed1d29f5383ab906440e, à la troisième position dans storedResolutions. Il pointe vers le hash 9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33, qui renvoie à son tour à une entrée de dépendance dans la map storedPackages. Cette fois, il s’agit des métadonnées complètes de la dépendance ms :

'9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33' => { identHash:
       '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6',
      scope: undefined,
      name: 'ms',
      locatorHash:
       '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33',
      reference: 'npm:2.1.2',
      version: '2.1.2',
      languageName: 'node',
      linkType: 'hard',
      dependencies: Map {},
      peerDependencies: Map {},
      dependenciesMeta: Map {},
      peerDependenciesMeta: Map {},
      bin: Map {} },

En résumé : les plugins Yarn 2

Yarn 2 est encore récent et son adoption par les utilisateurs ainsi que les retours de la communauté continuent de progresser : c’est donc le moment idéal pour créer des plugins.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.

Publié dans: