Création d'un monorepo avec Lerna

Aujourd’hui, pour le même projet, nous avons souvent plusieurs solutions et outils techniques (un backend, un frontend, une app mobile, une API…) qui doivent communiquer entre eux afin de fournir un écosystème complet pour l’utilisateur final. Dans une gestion de projet classique, un repository serait égal à une solution, cependant, nous avons vu au fil du temps que maintenir l’ensemble de notre écosystème s’avère laborieux. C’est pourquoi nous avons besoin de Lerna !😃

Lerna est un outil de gestion pour les projets de type monorepo. Il vous permet de centraliser vos projets javascript, d’optimiser l’organisation de vos packages, d’automatiser la gestion de versions et de générer des changelogs en fonction des messages de commits de vos différents collaborateurs. Et tout ça seulement avec quelques lignes de commandes !

Magique, non ? Allez, tu es prêt à en voir plus ?

Installation de Lerna

Avant de commencer, assure-toi d’avoir déjà installé Node JS sur ton ordinateur. Si tu ne l’as pas, rends-toi sur le site sur nodejs.org pour récupérer l’installateur correspondant à ton système d’exploitation.

Commençons par installer Lerna sur notre ordinateur, pour cela, ouvrons l’invite de commande et lançons la commande suivante :

Le flag -g nous permettra d’utiliser les commandes propres à Lerna dans n’importe quels projets. Une fois l’installation terminée, assurons nous de pouvoir exécuter la commande en inspectant la version de Lerna depuis la commande.

Configuration du fichier lerna.json

Regardons de plus près le fichier “lerna.json” à la racine du projet, nous pouvons voir que Lerna a ajouté deux propriétés. La première, “packages”, prend comme valeur un tableau avec des chemins correspondant à notre projet. C’est ici que nous indiquerons à Lerna l’endroit où se situent nos packages, nous pouvons également lui ajouter d’autres chemins suivant les dispositions de notre projet. Nous avons également la propriété “independent” qui correspond à notre gestion des versions au sein de notre projet.

Par défaut, Lerna utilise le gestionnaire de paquets NPM, mais dans le cadre de notre projet nous préférons utiliser Yarn pour ses différents avantages. Nous pouvons donc lui spécifier ceci grâce à la propriété “npmClient: yarn”.

Par la suite, pour optimiser nos packages, nous utiliserons le système de workspaces que Yarn met à notre disposition. Cette fonctionnalité nous permettra d’installer toutes les dépendances de nos packages dans un seul et même dossier node_modules, qui lui, se trouvera à la racine de notre projet. L’utilisation de cette dernière nous fera gagner du temps et de l’espace de stockage en mutualisant les dépendances de nos packages.

Pour utiliser cette fonctionnalité, nous devons également indiquer à notre package.json qui se trouve à la racine de notre projet, la configuration du workspace à adopter. Pour cela, nous indiquerons à la propriété workspaces un objet avec la propriété "packages" qui contiendra lui-même un tableau avec le ou les chemin(s) des dépendances de nos packages à mutualiser.

Voilà ! La configuration de Lerna est maintenant terminée, nous pouvons commencer à développer ou à importer nos packages. Pour cela, Lerna met à notre disposition 2 commandes.

Création, configuration et développement du package button

Essayons de créer un nouveau package “button-component-tutotrompe” que nous appellerons par la suite durant cet article Button et suivons les instructions de la commande suivante.

Hop, Lerna a créé pour nous un nouveau package, Super sympa, merci Lerna ! Analysons ensemble la nouvelle structure de notre projet. Nous pouvons voir que nous avons un nouvel élément dans le dossier package, avec un package.json, un README ainsi que deux fichiers JS, un pour le test et un pour le composant. Nous pouvons supprimer le dossier de test, car nous n’en aurons pas besoin dans la suite de cet article.

Le package.json de notre nouveau dossier button a été écrit entièrement par Lerna et nous pouvons voir les principaux éléments comme le name, la version, l’auteur, etc. Ce sont autant de propriétés importantes pour le partage et l’utilisation de notre package.

Pour notre projet, nous utiliserons React, ne vous inquiétez pas si vous n’êtes pas familiers avec cette librairie, cela n'influence pas le comportement de Lerna.

Pour ajouter React ou n’importe quelle autre dépendance dans notre package, nous devons utiliser la ligne de commande suivante depuis la racine de notre projet :

Dans notre commande le flag “--scope=nomdupackage” est optionnel, sans cette indication Lerna ajoutera notre dépendance React dans tous nos packages.

En regardant de plus près notre package.json du package/button, nous pouvons voir que Lerna à lui-même créer la propriété “dependencies” et a ajouté React.

Nous pouvons maintenant créer un composant React très simple nous permettant ici de simuler la logique d’un package réutilisable. Pour cela, rendez-vous dans le dossier lib de votre package et ouvrez le fichier .js qui ci-trouve.

Codons maintenant notre composant Button, il prendra en paramètre “label”, une chaîne de caractère et “handleclick”, une fonction qui nous permettra d'interagir avec le composant à l’extérieur du package.

Pour conclure, nous importons et exportons notre composant “Button” dans un fichier “index.js” dans le dossier “src/” pour améliorer son utilisation dans d’autres packages. Nous ajouterons également la commande suivante dans le package.json de notre package “button” pour pouvoir compiler notre code en javascript afin d’éviter tout problème de compatibilité.

Voilà notre composant Button est maintenant prêt à être utilisé dans notre projet.

Import du repository "input"

Revenons sur Lerna, nous avons maintenant vu comment créer un package, regardons comment importer un package externe à notre projet grâce aux commandes import et bootstrap de Lerna.

Avant d’utiliser la commande d’import de Lerna, nous devons obligatoirement push notre projet sur Git, n'oubliez pas de créer un .gitignore en lui indiquant le chemin vers le dossier node_modules de la racine pour éviter de stocker des données inutiles dans votre repository.

Nous pouvons maintenant importer un petit composant React externe à notre projet qui contiendra un input. Ce composant prend en paramètre un “placeholder” et une fonction “handleChange” pour récupérer facilement son contenu. Actuellement, ce composant se trouve dans un dossier extérieur à notre projet.

Grâce à la commande suivante, le projet externe est copié dans notre dossier “packages”.

Si le composant possède des dépendances, il est possible de les ajouter dans le dossier node_modules de la racine de notre projet en utilisant la commande :

Cette dernière analysera tous les package.json de nos différents packages et installera les dépendances dans le dossier adéquat.

Nous connaissons maintenant 5 commandes ! Lerna init, create, add, import et bootstrap. Pas mal non ? Autant de commandes qui peuvent nous aider à développer nos projets plus rapidement et plus facilement.

Création et configuration du package "form"

Développons maintenant un nouveau package React qui utilisera nos autres packages “input” et “button”.

Créons un nouveau package du nom de “form” pour mettre en application nos connaissances. Pour cela rien de plus simple, lançons les commandes suivantes :

Import des packages “button” et “input” dans le package “form”

Notre base étant prête, nous pouvons lier les packages “button” et “input” à notre nouveau package avec la même commande.

Voilà, Lerna a ajouté nos packages “button” et “input” ainsi que leurs numéros de versions dans le package.json du package “form”.

Développement du package "form"

Nous n’avons plus qu’à développer le composant "form". Pour cela, nous devons nous rendre dans le fichier .js de notre dossier lib et ajouter le code suivant.

Nous importerons nos dépendances React, “button” et “input”, créerons un “functional component” qui prend en paramètre un label. Ce composant se chargera de recevoir une chaîne de caractère grâce à la fonction handleChange du composant Input et de compter le nombre de lettres au clic du composant Button grâce à la fonction handleClick.

Publication des packages publiques de notre projet sur NPM

Maintenant que le développement de nos packages est terminé, nous pouvons les publier sur notre gestionnaire de dépendance préféré. Pour notre exemple, nous publierons nos packages sur npmjs.com, cependant, il existe des alternatives pour publier nos packages uniquement en local comme par exemple Verdaccio.

Pour déployer nos packages sur npmjs.com, nous devons créer un compte sur cette plateforme en nous rendant sur npmjs.com/signup et suivre les instructions d'inscription. N’oubliez pas de valider votre compte depuis le lien reçu par mail afin de pouvoir vous connecter depuis l’invite de commande. Une fois l’inscription terminée, utilisez la commande suivante pour vous identifier.

Pour poursuivre la publication de vos packages, nous devons également push nos dernières modifications sur Github, c’est une étape obligatoire avant le déploiement de nos composants.

Lerna met à disposition 2 commandes relatives à la publication de nos packages.

La commande “lerna changed” nous permettra de connaître précisément le nom et le nombre de nos packages prêts à être publiés.

Quant à elle, la commande “lerna publish” exécutera un bon nombre d’actions pour faciliter l’intégration et la maintenance de nos packages. Elle se chargera de monter la version du package publié, mettre à jour le numéro de version des packages qui l’utilise et également générer un changelog en fonction de nos commits.

Pour des raisons de sécurité, nous ne pouvons pas publier nos packages sans ajouter de licence à nos packages. Dans le cadre de ce projet, notre licence sera open source, il existe une multitude de configuration à ce sujet sur le site “choosealicense.com”, vous y trouverez votre bonheur sans aucun doute! Une fois la licence récupérée, nous pouvons créer un fichier license.md à la racine de notre projet et y copier le contenu.

Tout est maintenant prêt pour publier nos packages. Par défaut Lerna publiera tous les éléments publics de notre dossier “packages”, cependant, nous pouvons préciser dans le package.json d’un package la propriété “private” à “true” pour ne pas le publier, nous choisirons dans notre exemple de ne pas publier le package "input".

Lançons la commande “lerna publish”, choisissons le système de gestion des versions qui correspond aux modifications de notre package, dans notre cas, nous choisirons une gestion custom, et cela, pour l’ensemble de nos packages. Nous pouvons voir que Lerna a bien réussi à publier 2 packages.

Il est également important de préciser que les packages privés importés en tant que dépendances dans un package public seront également publiés.

Et voilà, nos packages sont maintenant disponibles sur le site npmjs.com/lenomdupackage. Nous pouvons les installer sur l’ensemble de vos projets depuis le gestionnaire de package NPM. Super non ?! ✅

BONUS: Installation et configuration du StoryBook

Pour un monorepo contenant plusieurs composants réutilisables, il est intéressant de créer un Storybook pour documenter votre code et son utilisation. StoryBook est une librairie open source qui nous propose un environnement de développement pour nos packages. Grâce à lui, nous pouvons créer un catalogue, définir des scénarios d’utilisation et tester nos composants très facilement.

Couplé à Lerna, cela en fait un superbe outil de développement pour vos projets.

De plus, grâce à la commande ci-dessous, l’intégration du StoryBook sur notre projet n’a jamais été aussi simple.

Cette commande ajoute pour nous les scripts à exécuter afin de pouvoir lancer et build le Storybook. Elle crée également un dossier “.storybook” contenant 2 fichiers javascript de configuration.

Le fichier main.js du dossier .storybook contient les propriétés “stories” et “addons”. Une “story” est un scénario d’utilisation d’un composant tandis qu’un addon peut s’apparenter à un outil qui nous donne la possibilité de customiser nos interactions avec le composant.

Ici, la propriété stories prend comme valeur un tableau de chaîne de caractère contenant une regex qui cherchera dans tout le dossier packages les fichiers avec le suffixe .stories. Cette regex nous permettra d’indexer nos composants dans le Storybook. Nous lui préciserons le dossier relatif à nos packages à la ligne 3 et 4 pour que le StoryBook puisse récupérer les stories appropriées.

Nous n’avons plus qu’à exécuter la commande “yarn storybook” pour lancer notre Storybook. Une fois la compilation terminée, nous retrouvons notre composant "form", qui utilise les packages "input" et "button". Nous pouvons écrire dans l’input un nouveau texte, appuyer sur le bouton pour calculer le nombre de caractères. Nous pouvons maintenant interagir plus facilement avec nos packages.

Et voilà, ton premier monorepo avec Lerna est prêt, tu n’as plus qu’à développer ou intégrer de nouveau package pour étoffer tes projets ;) ✅

Un tuto vidéo est aussi disponible juste ici, sur la chaîne Youtube d'Attineos ! 😉

Fabien P.