Guide de Monorepos pour le code frontal
Publié: 2022-03-11Les monorepos sont un sujet de discussion brûlant. Il y a eu beaucoup d'articles récemment sur les raisons pour lesquelles vous devriez et ne devriez pas utiliser ce type d'architecture pour votre projet, mais la plupart d'entre eux sont biaisés d'une manière ou d'une autre. Cette série est une tentative de rassembler et d'expliquer autant d'informations que possible pour comprendre comment et quand utiliser monorepos.
Un Monorepository est un concept architectural, qui contient essentiellement toute la signification de son titre. Au lieu de gérer plusieurs référentiels, vous conservez toutes vos parties de code isolées dans un seul référentiel. Gardez à l'esprit le mot isolé - cela signifie que monorepo n'a rien de commun avec les applications monolithiques. Vous pouvez conserver de nombreux types d'applications logiques dans un seul référentiel. par exemple, un site Web et son application iOS.
Ce concept est relativement ancien et est apparu il y a une dizaine d'années. Google a été l'une des premières entreprises à adopter cette approche pour gérer leurs bases de code. Vous pouvez demander, s'il existe depuis une décennie, alors pourquoi est-ce un sujet si brûlant seulement maintenant ? Surtout, au cours des 5-6 dernières années, beaucoup de choses ont subi des changements spectaculaires. ES6, préprocesseurs SCSS, gestionnaires de tâches, npm, etc. - de nos jours, pour maintenir une petite application basée sur React, vous devez gérer des bundlers de projets, des suites de tests, des scripts CI/CD, des configurations Docker et qui sait quoi d'autre. Et maintenant, imaginez qu'au lieu d'une petite application, vous deviez maintenir une énorme plate-forme composée de nombreux domaines fonctionnels. Si vous pensez à l'architecture, vous voudrez faire deux choses principales : séparer les préoccupations et éviter les duplications de code.
Pour ce faire, vous souhaiterez probablement isoler de grandes fonctionnalités dans certains packages, puis les utiliser via un point d'entrée unique dans votre application principale. Mais comment gérez-vous ces packages ? Chaque package devra avoir sa propre configuration d'environnement de workflow, ce qui signifie que chaque fois que vous souhaitez créer un nouveau package, vous devrez configurer un nouvel environnement, copier tous les fichiers de configuration, etc. Ou, par exemple, si vous devez changer quelque chose dans votre système de build, vous devrez passer en revue chaque dépôt, faire un commit, créer une demande d'extraction et attendre chaque build, ce qui vous ralentit beaucoup. A cette étape, nous rencontrons les monorepos.
Au lieu d'avoir beaucoup de référentiels avec leurs propres configurations, nous n'aurons qu'une seule source de vérité, le monorepo : un exécuteur de suite de tests, un fichier de configuration Docker et une configuration pour Webpack. Et vous avez toujours l'évolutivité, la possibilité de séparer les préoccupations, le partage de code avec des packages communs et de nombreux autres avantages. Ça sonne bien, non? Et bien ça l'est. Mais il y a aussi quelques inconvénients. Examinons de près les avantages et les inconvénients exacts de l'utilisation du monorepo dans la nature.
Avantages de Monorepo :
- Un endroit pour stocker toutes les configurations et tous les tests. Étant donné que tout se trouve dans un référentiel, vous pouvez configurer votre CI/CD et votre bundler une fois, puis simplement réutiliser les configurations pour créer tous les packages avant de les publier à distance. Il en va de même pour les tests unitaires, e2e et d'intégration : votre CI pourra lancer tous les tests sans avoir à gérer de configuration supplémentaire.
- Refactorisez facilement les fonctionnalités globales avec des commits atomiques. Au lieu de faire une demande d'extraction pour chaque référentiel, en déterminant dans quel ordre créer vos modifications, il vous suffit de faire une demande d'extraction atomique qui contiendra tous les commits liés à la fonctionnalité sur laquelle vous travaillez.
- Publication de packages simplifiée. Si vous envisagez d'implémenter une nouvelle fonctionnalité dans un package qui dépend d'un autre package avec du code partagé, vous pouvez le faire avec une seule commande. C'est une fonction qui nécessite des configurations supplémentaires, qui seront discutées plus tard dans une partie de revue d'outillage de cet article. Actuellement, il existe une riche sélection d'outils, notamment Lerna, Yarn Workspaces et Bazel.
- Gestion simplifiée des dépendances. Un seul package.json . Inutile de réinstaller les dépendances dans chaque référentiel chaque fois que vous souhaitez mettre à jour vos dépendances.
- Réutilisez le code avec des packages partagés tout en les gardant isolés. Monorepo vous permet de réutiliser vos packages à partir d'autres packages tout en les gardant isolés les uns des autres. Vous pouvez utiliser une référence au package distant et les consommer via un point d'entrée unique. Pour utiliser la version locale, vous pouvez utiliser des liens symboliques locaux. Cette fonctionnalité peut être implémentée via des scripts bash ou en introduisant des outils supplémentaires comme Lerna ou Yarn.
Inconvénients de Monorepo :
- Pas moyen de restreindre l'accès uniquement à certaines parties de l'application. Malheureusement, vous ne pouvez pas partager uniquement une partie de votre monorepo - vous devrez donner accès à l'ensemble de la base de code, ce qui pourrait entraîner des problèmes de sécurité.
Mauvaises performances de Git lorsque vous travaillez sur des projets à grande échelle. Ce problème commence à apparaître uniquement sur d' énormes applications avec plus d'un million de commits et des centaines de développeurs effectuant leur travail simultanément chaque jour sur le même dépôt. Cela devient particulièrement gênant car Git utilise un graphe acyclique dirigé (DAG) pour représenter l'historique d'un projet. Avec un grand nombre de validations, toute commande qui parcourt le graphique peut devenir lente à mesure que l'historique s'approfondit. Les performances ralentissent également en raison du nombre de références (c'est-à-dire de branches ou de balises, résolubles en supprimant les références dont vous n'avez plus besoin) et de la quantité de fichiers suivis (ainsi que de leur poids, même si le problème des fichiers lourds peut être résolu en utilisant Git LFS).
Remarque : De nos jours, Facebook essaie de résoudre les problèmes d'évolutivité du VCS en corrigeant Mercurial et, probablement bientôt, ce ne sera pas un si gros problème.
- Temps de construction plus élevé. Parce que vous aurez beaucoup de code source au même endroit, il faudra beaucoup plus de temps à votre CI pour tout exécuter afin d'approuver chaque PR.
Examen de l'outil
L'ensemble d'outils de gestion des monorepos ne cesse de croître et, actuellement, il est très facile de se perdre dans toute la variété des systèmes de construction pour les monorepos. Vous pouvez toujours être au courant des solutions populaires en utilisant ce dépôt. Mais pour l'instant, jetons un coup d'œil aux outils qui sont fortement utilisés de nos jours avec JavaScript :

- Bazel est le système de construction orienté monorepo de Google. En savoir plus sur Bazel : awesome-bazel
- Yarn est un outil de gestion des dépendances JavaScript qui prend en charge les monorepos via les espaces de travail.
- Lerna est un outil de gestion de projets JavaScript avec plusieurs packages, construit sur Yarn.
La plupart des outils utilisent une approche très similaire, mais il y a quelques nuances.
Nous approfondirons le flux de travail Lerna ainsi que les autres outils dans la partie 2 de cet article car il s'agit d'un sujet assez vaste. Pour l'instant, donnons juste un aperçu de ce qu'il y a à l'intérieur :
Lerne
Cet outil aide vraiment à gérer les versions sémantiques, à configurer le flux de travail de construction, à pousser vos packages, etc. L'idée principale derrière Lerna est que votre projet a un dossier packages, qui contient toutes vos parties de code isolées. Et en plus des packages, vous avez une application principale, qui peut par exemple résider dans le dossier src. Presque toutes les opérations dans Lerna fonctionnent via une règle simple - vous parcourez tous vos packages et effectuez certaines actions dessus, par exemple, augmenter la version du package, mettre à jour la dépendance de tous les packages, créer tous les packages, etc.
Avec Lerna, vous avez deux options pour utiliser vos forfaits :
- Sans les pousser à distance (NPM)
- Pousser vos colis à distance
Lors de l'utilisation de la première approche, vous pouvez utiliser des références locales pour vos packages et ne vous souciez pas vraiment des liens symboliques pour les résoudre.
Mais si vous utilisez la deuxième approche, vous êtes obligé d'importer vos packages à distance. (par exemple, import { something } from @yourcompanyname/packagename;
), ce qui signifie que vous obtiendrez toujours la version distante de votre package. Pour le développement local, vous devrez créer des liens symboliques à la racine de votre dossier pour que le bundler résolve les packages locaux au lieu d'utiliser ceux qui se trouvent dans votre node_modules/
. C'est pourquoi, avant de lancer Webpack ou votre bundler préféré, vous devrez lancer lerna bootstrap
, qui liera automatiquement tous les packages.
Fil
Yarn est initialement un gestionnaire de dépendances pour les packages NPM, qui n'a pas été initialement conçu pour prendre en charge monorepos. Mais dans la version 1.0, les développeurs de Yarn ont publié une fonctionnalité appelée Workspaces . Au moment de la sortie, ce n'était pas si stable, mais après un certain temps, il est devenu utilisable pour des projets de production.
L'espace de travail est essentiellement un package, qui a son propre package.json et peut avoir des règles de construction spécifiques (par exemple, un tsconfig.json séparé si vous utilisez TypeScript dans vos projets.). Vous pouvez en fait gérer sans Yarn Workspaces en utilisant bash et avoir exactement la même configuration, mais cet outil aide à faciliter le processus d'installation et de mise à jour des dépendances par package.
En un coup d'œil, Yarn avec ses espaces de travail fournit les fonctionnalités utiles suivantes :
- Dossier
node_modules
unique à la racine pour tous les packages. Par exemple, si vous avezpackages/package_a
etpackages/package_b
— avec leur proprepackage.json
— toutes les dépendances seront installées uniquement à la racine. C'est l'une des différences entre le fonctionnement de Yarn et de Lerna. - Lien symbolique de dépendance pour permettre le développement de packages locaux.
- Fichier de verrouillage unique pour toutes les dépendances.
- Mise à jour ciblée des dépendances au cas où vous voudriez réinstaller les dépendances pour un seul paquet. Cela peut être fait en utilisant le drapeau
-focus
. - Intégration avec Lerna. Vous pouvez facilement faire en sorte que Yarn gère toute l'installation/les liens symboliques et laisser Lerna s'occuper de la publication et du contrôle de version. C'est la configuration la plus populaire jusqu'à présent car elle nécessite moins d'efforts et est facile à utiliser.
Liens utiles:
- Espaces de travail du fil
- Comment créer un projet mono-repo TypeScript
Bazel
Bazel est un outil de construction pour les applications à grande échelle, qui peut gérer les dépendances multilingues et prendre en charge de nombreux langages modernes (Java, JS, Go, C++, etc.). Dans la plupart des cas, l'utilisation de Bazel pour les petites et moyennes applications JS est exagérée, mais à grande échelle, cela peut offrir de nombreux avantages en raison de ses performances.
De par sa nature, Bazel ressemble à Make, Gradle, Maven et à d'autres outils qui permettent de construire des projets en fonction du fichier qui contient une description des règles de construction et des dépendances du projet. Le même fichier dans Bazel s'appelle BUILD et se trouve dans l'espace de travail du projet Bazel. Le fichier BUILD utilise son Starlark, un langage de construction de haut niveau lisible par l'homme qui ressemble beaucoup à Python.
Habituellement, vous n'aurez pas beaucoup à faire avec BUILD car il y a beaucoup de passe-partout qui peuvent être facilement trouvés sur le Web et qui sont déjà configurés et prêts pour le développement. Chaque fois que vous souhaitez créer votre projet, Bazel procède essentiellement comme suit :
- Charge les fichiers BUILD pertinents pour la cible.
- Analyse les entrées et leurs dépendances, applique les règles de construction spécifiées et produit un graphe d'action.
- Exécute les actions de génération sur les entrées jusqu'à ce que les sorties de génération finales soient produites.
Liens utiles:
- JavaScript et Bazel - Docs pour la mise en place d'un projet Bazel pour JS à partir de zéro.
- Règles JavaScript et TypeScript pour Bazel – Boilerplate pour JS.
Conclusion
Les monorepos ne sont qu'un outil. Il y a beaucoup d'arguments pour savoir s'il a un avenir ou non, mais la vérité est que dans certains cas, cet outil fait son travail et le traite de manière efficace. Au cours des dernières années, cet outil a évolué, gagné en flexibilité, surmonté de nombreux problèmes et supprimé une couche de complexité en termes de configuration.
Il y a encore beaucoup de problèmes à résoudre, comme les mauvaises performances de Git, mais j'espère que cela sera résolu dans un avenir proche.
Si vous souhaitez apprendre à créer un pipeline CI/CD robuste pour votre application, je vous recommande How to Build an Effective Initial Deployment Pipeline with GitLab CI .