Lerna est un outil de gestion pour des projets de type monorepo.
Monorepo: monolithic repository, un projet qui contient plusieurs packages ou apps rassemblés pour faciliter les interactions et opérations (partage de code, déploiement, suites de tests, etc.)
Il vous permet d’optimiser la gestion de vos packages en vous offrant du tooling plus ou moins opiniâtre.
Dans les features les plus intéressantes on retrouve :
- Le versioning
- La publication des packages
- La génération de changelogs basés sur le contenu des messages de commits
Hands-on
Si vous aimez mettre les mains dans le camboui, forkez ce repo :
https://github.com/rednetio/monorepo-lerna-example
Et suivez les commandes données dans le tutoriel.
Si vous ne souhaitez pas le faire vous-même et voir directement le résultat de la section, suivez les commandes hands-on.
Si vous voulez juste regarder :
$ git clone git@github.com:rednetio/monorepo-lerna-example.git
$ git checkout step4
Lerna
Configuration basique
Hands-on :
git checkout step1
Prenons un exemple de monorepo:
monorepo/
apps/
backend
frontend
libs/
auth
utils
.gitignore
.prettierrc.yml
package.json
yarn.lock
Tout d’abord, on installe Lerna, il est conseillé de l’installer en global.
$ yarn global add lerna
Puis, à la racine du monorepo, on initialise le projet lerna :
$ lerna init --independent --exact
Le flag
--indepedent
nous permet d’incrémenter les versions de nos packages indépendamment les unes des autres. Il est également possible d’être en mode fixe, où tous les packages possèdent la même version, et un changement de version implique que tous les packages sont bumpés. C’est de cette façon qu’opère le repository Babel.
On se retrouve avec :
monorepo/
apps/
backend
frontend
libs/
auth
utils
.gitignore
.prettierrc.yml
_lerna.json_
package.json
yarn.lock
lerna.json
contient :
{
"packages": ["packages/*"],
"version": "independent"
}
Whoop-de-doo.
Par défaut, Lerna essaye de chercher les packages dans packages/
, mais vous pouvez lui préciser n’importe quel glob ou liste de globs.
Pour notre monorepo, ça donne :
{
"packages": ["apps/*", "libs/*"],
"version": "independent"
}
Si votre monorepo est configuré avec des workspaces yarn/npm, alors vous pouvez simplement mettre le flag useWorkspaces: true
:
{
"useWorkspaces": true,
"version": "independent",
"npmClient": "yarn"
}
Ici nous allons utiliser les workspaces.
Notez également que l’on précise yarn
comme client npm
.
Si vous ne savez pas comment faire : https://yarnpkg.com/en/docs/workspaces
Une fois l’emplacement des packages configuré, il est temps de bootstrapper :
$ lerna bootstrap --independent --exact
info cli using local version of lerna
lerna notice cli v3.14.1
lerna info Updating package.json
lerna info Updating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files
--independent
pour que chaque package ait une version individuelle,--exact
pour fixer la version de lerna.
Votre monorepo est bien lerna
-tized. Vous pouvez maintenant utiliser les différentes features de Lerna.
Nous allons voir plus bas le cœur de métier de lerna, la commande publish
. Mais d’abord, faisons une interlude sur Verdaccio.
Verdaccio
Verdaccio est un registry de packages privé, local, et sans configuration nécessaire.
C’est une alternative à npm
lorsque vous souhaitez mettre à disposition vos packages pour d’autres utilisateurs mais sans nécessairement vouloir les open sourcer.
Hébergez-le sur un serveur et boum, vous avez votre propre registry privé distant.
Ici nous allons utiliser Verdaccio en local.
Tout d’abord, installez-le en global :
$ yarn add global verdaccio --exact
Puis lancez-le :
$ verdaccio
Connectez-vous sur l’url par défaut : http://localhost:4873.
Pour l’instant il n’y a pas grand chose.
Avant de pouvoir publier un package, il faut créer un utilisateur pour pouvoir s’authentifier.
Note: Il faudrait faire la même chose pour
npm
. C’est exactement la même façon de procéder pour publier un package surnpm
. La différence ici est qu’on précise notre registry local Verdaccio.
$ npm adduser --registry http://localhost:4873
Vous pouvez vous connecter sur l’interface de Verdaccio avec les identifiants que vous avez choisis.
Nous allons tenter de publier notre librairie utils
.
Si vous passez par HTTPS, vous devez paramétrer le Certificate Authority. Voir https://docs.npmjs.com/misc/config#ca pour plus d’informations sur le sujet.
Depuis la racine, lancez :
$ npm publish ./libs/utils --registry http://localhost:4873
Si tout s’est bien passé, vous devriez avoir sur Verdaccio le package utils en version 1.0.0
déployé à l’instant.
Avant de passer à la suite, on va dépublier le package (c’était juste pour tester).
$ npm unpublish --force utils --registry http://localhost:4873
Et voilà, rien de bien compliqué. Ce n’est ni plus ni moins qu’un registry.
Back to Lerna
Configuration avec Lerna
Hands-on :
git checkout step2
Verdaccio étant prêt, on peut maintenant tenter de publier nos packages en passant par Lerna.
Pour ça, un peu de configuration est nécessaire.
Tout d’abord, il faut connaître l’intérêt de la propriété private: true
que l’on peut trouver dans le package.json
.
Si vous travaillez avec des workspaces yarn, vous avez sûrement du l’apercevoir dans le package.json
à la racine.
Cette propriété permet d’empêcher la publication du package lors d’un npm publish
. Comme ça n’a pas de sens de publier la racine, il est automatiquement mis à true
.
Si vous avez suivi, vous aurez compris qu’il faut mettre private: true
sur les packages que l’on ne souhaite pas publier.
Concernant notre monorepo, nous n’avons pas besoin de publier nos apps. Puisque ce que nous voulons, c’est exposer le code qui peut être réutilisé. Ce n’est pas le cas pour les apps.
Une fois que c’est fait, nous allons configurer Lerna pour la commande publish
.
On souhaite que nos libs soient publiées sur notre registry local. Pour cela, il suffit de paramétrer la propriété registry
dans lerna.json
.
"command": {
[…],
"publish": {
"registry": "http://localhost:4873/",
}
}
Afin d’optimiser la taille de nos packages, on va aussi dire à notre registry quels fichiers on veut publier. On fait ça avec la propriété files
.
Pour la librairie auth
par exemple :
"files": [
"dist",
"index.js"
],
On a besoin de nos fichiers minifiés, et notre index.js
pour exposer les composants.
On va également donner quelques petites informations à Lerna :
"command": {
[…],
"publish": {
"message": "chore(packages): release",
"conventionalCommits": true,
"npmClient": "npm",
"registry": "http://localhost:4873/",
"verifyAccess": false
}
}
message
: le message de commit utilisé à la publicationconventionalCommits
: on gère les versions suivant les conventional commits, voir prochaine sectionnpmClient
: le client que nous utilisons pour effectuer la publicationregistry
: le registry sur lequel seront publiés les packages. On peut aussi le conifgurer individuellement dans les packages.verifyAccess
: lors de la publication d’un package,npm
tente de fairenpm access ls-packages
pour vérifier à quels packages la team ou l’utilisateur ont accès, cette commande n’est pas supportée par Verdaccio, alors on metverifyAccess
àfalse
pour ne pas l’exécuter
Versioning et Conventional commits
Hands-on :
git checkout step3
Le versioning des packages suit la convention SemVer, et est fait de façon automatique en fonction des types de commits.
Ici, on utilise les conventional commits, popularisé par Angular.js.
En savoir plus sur les conventional commits
Le principe est :
- Un commit de type
fix:
déclenchera une montée de versionPATCH
. - Un commit de type
feat:
déclenchera une montée de versionMINOR
. - Un commit qui contient un
BREAKING CHANGE
déclenchera une montée de versionMAJOR
.
Pour éviter les écueils, vous pouvez utiliser un commit linter.
$ yarn add -WED @commitlint/{config-conventional,cli} husky
Puis rajoutez dans votre package.json
à la racine :
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
On rajoute un fichier .commitlintrc.yml
à la racine :
extends:
- '@commitlint/config-conventional'
Il faut maintenant modifier nos packages pour tester la montée de version et la publication.
Lerna publish
On arrive enfin à la partie intéressante, la publication des packages.
Il nous faut d’abord committer des modifications afin de provoquer une nouvelle version pour chaque package.
Vous pouvez tenter de faire des modifications vous-même ou alors passer directement à l’étape suivante avec la commande hands-on :
Hands-on:
git checkout step4
Vous pouvez faire un git log
pour voir les commits qui ont été faits.
On a un :
- Un fix sur le frontend
- Rien sur le backend
- Un breaking change sur auth
- Une feature sur utils
On devrait alors passer aux versions :
1.0.1
pour frontend1.0.1
pour backend2.0.0
pour auth1.1.0
pour utils
Ici backend est bumpé parce que c’est notre premier publish.
On vérifie d’abord quels packages ont été updatés :
$ lerna changed
info cli using local version of lerna
lerna notice cli v3.14.1
lerna info versioning independent
lerna info Assuming all packages changed
auth
utils
lerna success found 2 packages ready to publish
Les deux packages auth
et utils
(ceux que l’on veut publier) ont bien été détectés.
On va bientôt pouvoir passer à la publication.
Tout d’abord, sachez que lerna publish
fait beaucoup de choses.
Il va :
- Exécuter la montée de version
- Mettre à jour les versions des packages que l’on vient de mettre à jour au sein du monorepo
- Générer le changelog de chaque package
- Committer les changements avec le message configuré dans le
lerna.json
- Créer des tags de version pour chaque package (au format : `auth@1.0.0`)
- Pusher toutes les références git
- Publier les packages sur le registry
:warning: Si votre publish ne fonctionne pas, vous vous retrouverez avec tous les tags et commits de créés et poussés sur le serveur git.
Dans la pratique, vous risquez d’avoir des problèmes seulement à la mise en place de Lerna et Verdaccio. Une fois que tout est configuré vous n’aurez plus de problèmes.
Avant de pouvoir exécuter le publish, il faut pousser la branche sur laquelle on travaille.
Ici, on travaille sur la branche release.
Hands-on : pour créer la branche release, mergeons d’abord la branche step4 dans master.
# HANDS-ON ONLY
$ git checkout master
$ git merge step4
On créé et on pousse la branche release
:
$ git checkout -b release
$ git push
Allez, on passe enfin au publish :
$ lerna publish
info cli using local version of lerna
lerna notice cli v3.14.1
lerna info versioning independent
lerna info Assuming all packages changed
lerna info getChangelogConfig Successfully resolved preset "conventional-changelog-angular"
Changes:
- backend: 1.0.0 => 1.0.1 (private)
- frontend: 1.0.0 => 1.0.1 (private)
- auth: 1.0.0 => 2.0.0
- utils: 1.0.0 => 1.1.0
? Are you sure you want to publish these packages? Yes
lerna info execute Skipping GitHub releases
lerna info git Pushing tags...
lerna info publish Publishing packages to npm...
lerna notice Skipping all user and access validation due to third-party registry
lerna notice Make sure you're authenticated properly ¯\_(ツ)_/¯
lerna WARN ENOLICENSE Packages auth and utils are missing a license.
lerna WARN ENOLICENSE One way to fix this is to add a LICENSE.md file to the root of this repository.
lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.
lerna success published utils 1.1.0
lerna notice
lerna notice 📦 utils@1.1.0
lerna notice === Tarball Contents ===
lerna notice 168B package.json
lerna notice 354B CHANGELOG.md
lerna notice 469B config/webpack.config.js
lerna notice 1.1kB dist/utils.min.js
lerna notice 167B src/utils.js
lerna notice === Tarball Details ===
lerna notice name: utils
lerna notice version: 1.1.0
lerna notice filename: utils-1.1.0.tgz
lerna notice package size: 1.3 kB
lerna notice unpacked size: 2.2 kB
lerna notice shasum: e6bc61dff306830bef2ebaa67fd7ef0861e21738
lerna notice integrity: sha512-NVPWgFpMN9RjY[...]9zvwE6rE95uHA==
lerna notice total files: 5
lerna notice
lerna http fetch PUT 201 http://localhost:4873/utils 95ms
lerna success published auth 2.0.0
lerna notice
lerna notice 📦 auth@2.0.0
lerna notice === Tarball Contents ===
lerna notice 285B package.json
lerna notice 420B CHANGELOG.md
lerna notice 106B index.js
lerna notice 1.3kB dist/probe.min.js
lerna notice === Tarball Details ===
lerna notice name: auth
lerna notice version: 2.0.0
lerna notice filename: auth-2.0.0.tgz
lerna notice package size: 1.2 kB
lerna notice unpacked size: 2.1 kB
lerna notice shasum: 2f008d37cccfbc84909aafad41a32638be9e408f
lerna notice integrity: sha512-5XA5UWM9gavxn[...]3e83VBU5bwuEw==
lerna notice total files: 4
lerna notice
lerna http fetch PUT 201 http://localhost:4873/auth 6238ms
Successfully published:
- auth@2.0.0
- utils@1.1.0
lerna success published 2 packages
On vérifie vite fait sur verdaccio : http://localhost:4873/.
Nos packages ont bien été déployés.
Vous pouvez aussi vérifier sur votre repository, vos tags et votre commit de release ont bien été poussés.
Congratuwelldone !