Développer une librairie plutôt qu’une application web plus classique amène de nouvelles problématiques. Parmi elles se trouve la question de comment versionner cette librairie. C’est sur la base de cette problématique ainsi que d’un retour d’expérience que nous allons tenter d’apporter des éléments de réponse.
Le projet sur lequel nous allons nous appuyer dans cet article consiste en la création d’un Design System pour un client.
L’objectif de ce Design System est d’homogénéiser l’ensemble de leurs applications en leur apportant une identité graphique. Cela passe donc par l’uniformisation des composants et des styles graphiques pour chacune des applications utilisées par le client. Le tout en apportant un réel gain de temps dans la production de nouvelles maquettes de design et dans le développement front.
Pour cela, nous avons développé une librairie de web components. L’objectif de ces composants étant qu’ils puissent être utilisés par n’importe quel framework.
Nous allons donc présenter dans cet article la manière dont nous avons appréhendé la gestion des versions de notre librairie. Que ce soient les points d’attention à garder à l’esprit, les erreurs commises ainsi que les solutions trouvées.
Nous nous sommes basés sur semver pour réaliser nos montées de versions. Voyons rapidement les grands principes :
Partons d’une version A.B.C
, avec :
A
: numéro de version majeurB
: numéro de version mineurC
: numéro de version de correctif (plus communément appelé “patch”)Une montée de version majeure implique un ou plusieurs Breaking changes, c’est-à-dire des changements dans la librairie qui impliquent aux personnes qui l’utilisent de devoir effectuer certaines modifications dans leur utilisation. Sans quoi certains services de la librairie, dans notre cas des composants web, par exemple, risquent de ne plus être fonctionnels. Une montée de version majeur n’est donc pas rétrocompatible.
Une montée de version mineure implique l’ajout d’une fonctionnalité qui ne provoque pas de Breaking change, qui est donc rétrocompatible. Par exemple, on peut imaginer l’ajout d’un nouveau composant, ou bien l’ajout d’une fonctionnalité sur un des composants existants.
Une montée de version de correctif (patch) implique la plupart du temps la correction d’un bug.
Le développement d’une librairie implique de devoir définir comment et quand monter de version. Comme vu dans le paragraphe précédent, la montée de version se fait selon le contenu de cette dernière et débouchera sur une montée de version de correctif, mineure ou majeure.
Il est important d’en livrer régulièrement pour faire vivre la librairie et assurer son développement et son adoption par les utilisateurs. Dans notre cas, il va s’agir principalement de livrer de nouveaux composants. Cependant, il arrive que certains composants soient plus longs à développer que d’autres, impliquant un plus long délai pour sortir une nouvelle version mineure. Des versions de correctif peuvent donc être livrées entre-temps pour assurer la régularité des livraisons de nouvelles versions. Et au-delà de cette notion de régularité, les versions de correctif assurent la robustesse de la librairie en corrigeant les problèmes. Elles sont donc primordiales, en plus d’assurer un contact régulier avec les utilisateurs, souvent à l’origine des remontées d’anomalies.
Une fois la montée de version effectuée, il est nécessaire que les utilisateurs de la librairie aient accès à une page leur expliquant les modifications qu’elle apporte. Plusieurs moyens peuvent être utilisés.
Nous avons décidé, pour ce projet, d’utiliser Storybook pour présenter notre librairie de composants à nos utilisateurs. Une page Release Notes dans l’arborescence du Storybook peut alors être mise-à-jour lors de la montée de version afin d’informer des changements apportés à chaque version. On pourra y trouver le numéro de la version, sa date de livraison ainsi qu’une liste des nouveautés. En plus de cette page, il peut être opportun de notifier les utilisateurs d’une nouvelle version, par le biais d’envois de mails, par un réseau social ou autre.
Une de nos erreurs a été dans un premier temps de monter de version à chaque changement sur la branche master, ce qui provoquait beaucoup de montées de version. Il est en fait préférable de grouper plusieurs changements dans une seule livraison. Un moyen d’y parvenir peut être de déclencher la montée de version non plus sur un changement de la branche master, mais sur celui d’une autre branche, release par exemple. Ainsi, la montée s’effectuera uniquement lorsque l’on décidera de fusionner (merge) la branche master, qui contient tous les changements, sur la branche release. Pour ne pas inclure certains changements dans la prochaine montée de version, il faudra donc attendre avant de les fusionner sur la master.
Une montée de version majeure, impliquant en général des breaking changes, ne peut pas se faire de la même manière que les versions mineures et patch. En effet, une montée de version majeure doit se préparer et ne doit pas avoir lieu trop souvent. Cependant, des évolutions impliquant des breaking changes arrivent régulièrement, le plus souvent dûs à des choix antérieurs qui n’étaient pas les bons. Cela arrivera dans tous les cas mais ce n’est pas forcément grave.
En attendant la version majeure, une solution est de passer, lorsque que cela est possible, par ce qu’on appelle du deprecated, c’est-à-dire une solution de transition où plusieurs manières de faire peuvent coexister. Cela en précisant bien aux utilisateurs la nouvelle manière de faire à l’aide de warnings dans la console du navigateur par exemple, en plus d’une mise-à-jour de la documentation de la librairie.
Prenons l’exemple d’un renommage d’attribut d’un composant :
Prenons un composant <my-button>
, pour lequel on avait défini un attribut boolean isDisabled. Ce nommage est correct dans la plupart des cas pour un attribut boolean (par exemple lors d’une application classique dans un framework JS tel que React, Vue, Angular), mais ne répond pas aux standards HTML quant au nommage d’attribut de composants Web, où l’on privilégiera des attributs nommés le plus simplement tels que disabled.
Pour aller plus loin, selon les standards HTML, un attribut boolean a la valeur false par défaut, ce qui implique que la présence ou non de l’attribut sur la balise HTML du composant lui attribue sa valeur.
Composant <my-button>
non disabled (absence de l’attribut disabled impliquant une valeur égale à false) :
<my-button></my-button>
Composant <my-button>
disabled (présence de l’attribut disabled impliquant une valeur égale à true) :
<my-button disabled></my-button>
Note : les standards relatifs aux attributs des balises HTML recommandent une écriture en snake-case (en reprenant l’exemple : is-disabled), et pas camelCase (isDisabled). Le camelCase pourra par ailleurs provoquer des problèmes de compatiblité avec certains frameworks.
Revenons-en maintenant à ce que ce renommage implique. Si nous décidions de renommer purement et simplement l’attribut dans le composant et que nous effectuions la montée de version dans la foulée, cela représenterait un breaking change car si des utilisateurs utilisaient un composant <my-button>
avec l’attribut isDisabled et non pas disabled, le composant cesserait de fonctionner correctement et cela demanderait forcément une mise-à-jour de chaque composant <my-button>
dans leur application.
Pour éviter un changement soudain et trop brutal, nous pouvons aussi décider de mettre l’attribut isDisabled en deprecated dans la documentation et dans les warnings tout en le gardant dans le composant. Ce dernier devra alors pouvoir supporter les 2 attributs isDisabled et disabled en même temps. Ainsi, les utilisateurs de la librairie auront le temps de se mettre à jour quant au nouvel attribut durant cette période de transition mais n’y seront pas contraints jusqu’à l’arrêt du support de l’ancien attribut, dans une prochaine version majeure à définir.
Comme vous l’aurez compris, la gestion des breaking changes dans le développement d’une librairie est assez délicate et demande du temps. C’est pourquoi il est préférable de grouper plusieurs breaking changes dans une même version majeure, de manière à ne pas avoir à monter le numéro majeur de la version trop souvent.
Comme évoqué plus haut, nos livraisons de nouvelles versions en production sont effectuées par notre CI/CD lors de changements sur la branche release, de manière à pouvoir regrouper plusieurs sujets sur une même montée de version.
De plus, juste avant de fusionner la branche master sur la branche release, nous tirons une branche depuis la master afin de monter la version (npm version minor
par exemple) et de mettre-à-jour la release note avec les modifications apportées.
Voici un schéma récapitulatif :
Nous avons commis plusieurs erreurs lors du développement de la librairie. Et comme évoqué précédemment, certaines ont pu impliquer une montée de version majeure en passant ou non par du deprecated. Le mauvais nommage d’attributs de certains de nos composants ou le fait d’avoir livré de nouvelles versions à chaque changement sur la branche master en font partie.
Pour récapituler, nous nous sommes basés sur semver pour versionner notre librairie de composants, oscillant entre montées de versions majeures, mineures et de correctif. L’un des points d’attention du semver se trouve dans l’utilisation des numéros majeurs de version, impliquant dans de nombreux cas des Breaking changes. Ces derniers demandent une attention particulière des développeurs dans leur gestion, en décidant de passer ou non par du deprecated. Dans le cas contraire, il faudra bien faire attention à ne pas changer de version majeure trop souvent, de peur d’être une contrainte trop grande pour les consommateurs de la librairie.
Une librairie, comme toute autre application web, a besoin d’un environnement adapté. La CI/CD doit être en mesure de livrer en production de nouvelles versions, contenant un ensemble maîtrisé de fonctionnalités et de correctifs. Pour cela, nous utilisons une branche dédiée à la livraison des nouvelles versions, nommée release.
Pour terminer, il faut garder à l’esprit que faire au plus simple pour les utilisateurs de la librairie reste l’un des points les plus cruciaux, et que c’est autour de ce principe que les solutions doivent se baser.