Leitfaden für Monorepos für Front-End-Code
Veröffentlicht: 2022-03-11Monorepos sind ein heißes Diskussionsthema. In letzter Zeit gab es viele Artikel darüber, warum Sie diese Art von Architektur für Ihr Projekt verwenden sollten und warum nicht, aber die meisten von ihnen sind auf die eine oder andere Weise voreingenommen. Diese Serie ist ein Versuch, so viele Informationen wie möglich zu sammeln und zu erklären, um zu verstehen, wie und wann Monorepos verwendet werden.
Ein Monorepository ist ein architektonisches Konzept, das im Grunde alle Bedeutung in seinem Titel enthält. Anstatt mehrere Repositorys zu verwalten, behalten Sie alle Ihre isolierten Codeteile in einem Repository. Denken Sie an das Wort isoliert – es bedeutet, dass Monorepo nichts mit monolithischen Apps gemeinsam hat. Sie können viele Arten von logischen Apps in einem Repo aufbewahren; B. eine Website und ihre iOS-App.
Dieses Konzept ist relativ alt und erschien vor etwa einem Jahrzehnt. Google war eines der ersten Unternehmen, das diesen Ansatz zur Verwaltung seiner Codebasen übernommen hat. Sie fragen sich vielleicht, wenn es schon seit einem Jahrzehnt existiert, warum ist es dann erst jetzt so ein heißes Thema? Vor allem im Laufe der letzten 5-6 Jahre hat sich vieles dramatisch verändert. ES6, SCSS-Präprozessoren, Task-Manager, npm usw. – um heute eine kleine React-basierte App zu warten, muss man sich mit Projektbündlern, Testsuiten, CI/CD-Skripten, Docker-Konfigurationen und wer weiß was noch herumschlagen. Und nun stellen Sie sich vor, Sie müssten statt einer kleinen App eine riesige Plattform mit vielen Funktionsbereichen pflegen. Wenn Sie über Architektur nachdenken, sollten Sie vor allem zwei Dinge tun: Trennen Sie Bedenken und vermeiden Sie Code-Dupes.
Um dies zu erreichen, möchten Sie wahrscheinlich große Funktionen in einigen Paketen isolieren und sie dann über einen einzigen Einstiegspunkt in Ihrer Haupt-App verwenden. Aber wie verwalten Sie diese Pakete? Jedes Paket muss seine eigene Workflow-Umgebungskonfiguration haben, und das bedeutet, dass Sie jedes Mal, wenn Sie ein neues Paket erstellen möchten, eine neue Umgebung konfigurieren, alle Konfigurationsdateien kopieren müssen und so weiter. Oder wenn Sie beispielsweise etwas in Ihrem Build-System ändern müssen, müssen Sie jedes Repo durchgehen, einen Commit durchführen, einen Pull-Request erstellen und auf jeden Build warten, was Sie sehr verlangsamt. Bei diesem Schritt treffen wir auf die Monorepos.
Anstatt viele Repositorys mit ihren eigenen Konfigurationen zu haben, haben wir nur eine Quelle der Wahrheit – das Monorepo: einen Testsuite-Runner, eine Docker-Konfigurationsdatei und eine Konfiguration für Webpack. Und Sie haben immer noch Skalierbarkeit, die Möglichkeit, Bedenken zu trennen, Code-Sharing mit gemeinsamen Paketen und viele andere Vorteile. Klingt schön, oder? Nun, es ist. Aber es gibt auch einige Nachteile. Werfen wir einen genauen Blick auf die genauen Vor- und Nachteile der Verwendung des Monorepos in freier Wildbahn.
Monorepo-Vorteile:
- Ein Ort zum Speichern aller Konfigurationen und Tests. Da sich alles in einem Repo befindet, können Sie Ihr CI/CD und Ihren Bundler einmal konfigurieren und dann einfach die Konfigurationen wiederverwenden, um alle Pakete zu erstellen, bevor Sie sie auf Remote veröffentlichen. Gleiches gilt für Unit-, e2e- und Integrationstests – Ihr CI kann alle Tests starten, ohne sich um zusätzliche Konfigurationen kümmern zu müssen.
- Einfaches Refactoring globaler Funktionen mit atomaren Commits. Anstatt für jedes Repo eine Pull-Anfrage zu stellen und herauszufinden, in welcher Reihenfolge Ihre Änderungen erstellt werden, müssen Sie nur eine atomare Pull-Anfrage stellen, die alle Commits enthält, die sich auf die Funktion beziehen, an der Sie arbeiten.
- Vereinfachte Paketveröffentlichung. Wenn Sie vorhaben, ein neues Feature in einem Paket zu implementieren, das von einem anderen Paket mit gemeinsam genutztem Code abhängig ist, können Sie dies mit einem einzigen Befehl tun. Es handelt sich um eine Funktion, die einige zusätzliche Konfigurationen erfordert, die später in einem Tool-Review-Teil dieses Artikels besprochen werden. Derzeit gibt es eine große Auswahl an Tools, darunter Lerna, Yarn Workspaces und Bazel.
- Einfacheres Abhängigkeitsmanagement. Nur eine package.json . Sie müssen Abhängigkeiten nicht in jedem Repo neu installieren, wenn Sie Ihre Abhängigkeiten aktualisieren möchten.
- Verwenden Sie Code mit gemeinsam genutzten Paketen wieder, während Sie sie trotzdem isoliert halten. Mit Monorepo können Sie Ihre Pakete aus anderen Paketen wiederverwenden und sie gleichzeitig voneinander isoliert halten. Sie können einen Verweis auf das Remote-Paket verwenden und es über einen einzigen Einstiegspunkt nutzen. Um die lokale Version zu verwenden, können Sie lokale Symlinks verwenden. Diese Funktion kann über Bash-Skripte oder durch die Einführung einiger zusätzlicher Tools wie Lerna oder Yarn implementiert werden.
Monorepo-Nachteile:
- Keine Möglichkeit, den Zugriff nur auf einige Teile der App zu beschränken. Leider können Sie nicht nur einen Teil Ihres Monorepos freigeben – Sie müssen Zugriff auf die gesamte Codebasis gewähren, was zu einigen Sicherheitsproblemen führen kann.
Schlechte Git-Performance bei der Arbeit an großen Projekten. Dieses Problem tritt erst bei großen Anwendungen mit mehr als einer Million Commits und Hunderten von Entwicklern auf, die jeden Tag gleichzeitig ihre Arbeit über dasselbe Repo erledigen. Dies wird besonders problematisch, da Git einen gerichteten azyklischen Graphen (DAG) verwendet, um den Verlauf eines Projekts darzustellen. Bei einer großen Anzahl von Commits könnte jeder Befehl, der den Graphen durchläuft, langsam werden, wenn sich der Verlauf vertieft. Die Leistung verlangsamt sich auch aufgrund der Anzahl der Refs (d. h. Verzweigungen oder Tags, lösbar durch Entfernen von Refs, die Sie nicht mehr benötigen) und der Menge der verfolgten Dateien (sowie ihres Gewichts, obwohl das Problem mit großen Dateien gelöst werden kann). Git-LFS).
Hinweis: Heutzutage versucht Facebook, Probleme mit der VCS-Skalierbarkeit zu lösen, indem es Mercurial patcht, und wahrscheinlich bald wird dies kein so großes Problem sein.
- Höhere Bauzeit. Da Sie eine Menge Quellcode an einem Ort haben, dauert es viel länger, bis Ihr CI alles ausgeführt hat, um jeden PR zu genehmigen.
Tool-Überprüfung
Der Satz von Tools zur Verwaltung von Monorepos wächst ständig, und derzeit ist es wirklich einfach, sich in der ganzen Vielfalt von Aufbausystemen für Monorepos zu verirren. Mit diesem Repo können Sie sich immer über die beliebten Lösungen informieren. Aber lassen Sie uns zunächst einen kurzen Blick auf die Tools werfen, die heutzutage stark mit JavaScript verwendet werden:
- Bazel ist das Monorepo-orientierte Build-System von Google. Mehr zu Bazel: awesome-bazel
- Yarn ist ein Tool zur Verwaltung von JavaScript-Abhängigkeiten, das Monorepos über Arbeitsbereiche unterstützt.
- Lerna ist ein Tool zum Verwalten von JavaScript-Projekten mit mehreren Paketen, das auf Yarn basiert.
Die meisten Tools verwenden einen wirklich ähnlichen Ansatz, aber es gibt einige Nuancen.

Wir werden in Teil 2 dieses Artikels tiefer in den Lerna-Workflow sowie in die anderen Tools eintauchen, da es sich um ein ziemlich umfangreiches Thema handelt. Verschaffen wir uns zunächst einmal einen Überblick über den Inhalt:
Lerna
Dieses Tool hilft wirklich beim Umgang mit semantischen Versionen, beim Einrichten des Erstellungsworkflows, beim Pushen Ihrer Pakete usw. Die Hauptidee hinter Lerna ist, dass Ihr Projekt einen Paketordner hat, der alle Ihre isolierten Codeteile enthält. Und neben Paketen haben Sie eine Haupt-App, die beispielsweise im src-Ordner liegen kann. Fast alle Operationen in Lerna funktionieren nach einer einfachen Regel – Sie durchlaufen alle Ihre Pakete und führen einige Aktionen darüber aus, z. B. Paketversion erhöhen, Abhängigkeit aller Pakete aktualisieren, alle Pakete erstellen usw.
Mit Lerna haben Sie zwei Möglichkeiten, wie Sie Ihre Pakete verwenden können:
- Ohne sie in die Ferne zu schieben (NPM)
- Pushen Sie Ihre Pakete auf Remote
Während Sie den ersten Ansatz verwenden, können Sie lokale Referenzen für Ihre Pakete verwenden und interessieren sich im Grunde nicht wirklich für symbolische Links, um sie aufzulösen.
Aber wenn Sie den zweiten Ansatz verwenden, sind Sie gezwungen, Ihre Pakete aus der Ferne zu importieren. (z. B. import { something } from @yourcompanyname/packagename;
), was bedeutet, dass Sie immer die Remote-Version Ihres Pakets erhalten. Für die lokale Entwicklung müssen Sie Symlinks im Stammverzeichnis Ihres Ordners erstellen, damit der Bundler lokale Pakete auflöst, anstatt diejenigen zu verwenden, die sich in Ihren node_modules/
. Aus diesem Grund müssen Sie vor dem Start von Webpack oder Ihrem bevorzugten lerna bootstrap
starten, das automatisch alle Pakete verknüpft.
Garn
Yarn ist ursprünglich ein Abhängigkeitsmanager für NPM-Pakete, der ursprünglich nicht für die Unterstützung von Monorepos entwickelt wurde. Aber in Version 1.0 haben die Yarn-Entwickler eine Funktion namens Workspaces veröffentlicht. Zum Zeitpunkt der Veröffentlichung war es nicht so stabil, aber nach einer Weile wurde es für Produktionsprojekte nutzbar.
Workspace ist im Grunde ein Paket, das über eine eigene „package.json“ verfügt und einige spezifische Erstellungsregeln haben kann (z. B. eine separate „tsconfig.json“ , wenn Sie TypeScript in Ihren Projekten verwenden.). Sie können tatsächlich irgendwie ohne Yarn Workspaces mit Bash auskommen und genau das gleiche Setup haben, aber dieses Tool hilft, den Prozess der Installation und Aktualisierung von Abhängigkeiten pro Paket zu vereinfachen.
Auf einen Blick bietet Yarn mit seinen Arbeitsbereichen die folgenden nützlichen Funktionen:
- Einzelner
node_modules
Ordner im Stammverzeichnis für alle Pakete. Wenn Sie beispielsweisepackages/package_a
undpackages/package_b
– mit ihrer eigenen Datei „package.json
“ – haben, werden alle Abhängigkeiten nur im Stammverzeichnis installiert. Das ist einer der Unterschiede zwischen der Funktionsweise von Yarn und Lerna. - Abhängigkeits-Symlinking, um die lokale Paketentwicklung zu ermöglichen.
- Einzelne Sperrdatei für alle Abhängigkeiten.
- Fokussiertes Abhängigkeitsupdate für den Fall, dass Sie Abhängigkeiten für nur ein Paket neu installieren möchten. Dies kann mit dem Flag
-focus
. - Integration mit Lerna. Sie können Yarn ganz einfach die gesamte Installation/Symlinking übernehmen lassen und Lerna die Veröffentlichung und Versionskontrolle überlassen. Dies ist das bisher beliebteste Setup, da es weniger Aufwand erfordert und einfach zu handhaben ist.
Nützliche Links:
- Garn-Arbeitsbereiche
- So erstellen Sie ein TypeScript-Mono-Repo-Projekt
Basel
Bazel ist ein Build-Tool für umfangreiche Anwendungen, das mit mehrsprachigen Abhängigkeiten umgehen kann und viele moderne Sprachen (Java, JS, Go, C++ usw.) unterstützt. In den meisten Fällen ist die Verwendung von Bazel für kleine bis mittlere JS-Anwendungen übertrieben, aber im großen Maßstab kann es aufgrund seiner Leistung viele Vorteile bieten.
Von Natur aus ähnelt Bazel Make, Gradle, Maven und anderen Tools, die Projekt-Builds basierend auf der Datei ermöglichen, die eine Beschreibung der Build-Regeln und Projektabhängigkeiten enthält. Dieselbe Datei in Bazel heißt BUILD und befindet sich im Arbeitsbereich des Bazel-Projekts. Die BUILD -Datei verwendet Starlark, eine für Menschen lesbare High-Level-Build-Sprache, die Python sehr ähnlich sieht.
Normalerweise werden Sie nicht viel mit BUILD zu tun haben, da es viele Boilerplates gibt, die leicht im Web zu finden sind und die bereits konfiguriert und bereit für die Entwicklung sind. Wann immer Sie Ihr Projekt erstellen möchten, macht Bazel im Grunde Folgendes:
- Lädt die für das Ziel relevanten BUILD -Dateien.
- Analysiert die Eingaben und ihre Abhängigkeiten, wendet die angegebenen Erstellungsregeln an und erstellt ein Aktionsdiagramm.
- Führt die Build-Aktionen für die Eingaben aus, bis die endgültigen Build-Ausgaben erzeugt werden.
Nützliche Links:
- JavaScript und Bazel – Dokumente zum Einrichten eines Bazel-Projekts für JS von Grund auf neu.
- JavaScript- und TypeScript-Regeln für Bazel – Boilerplate für JS.
Fazit
Monorepos sind nur ein Werkzeug. Es gibt viele Argumente darüber, ob es eine Zukunft hat oder nicht, aber die Wahrheit ist, dass dieses Tool in einigen Fällen seine Arbeit erledigt und effizient damit umgeht. Im Laufe der letzten Jahre hat sich dieses Tool weiterentwickelt, viel mehr Flexibilität gewonnen, viele Probleme überwunden und eine Komplexitätsebene in Bezug auf die Konfiguration entfernt.
Es gibt noch viele Probleme zu lösen, wie z. B. schlechte Git-Performance, aber hoffentlich wird dies in naher Zukunft behoben.
Wenn Sie lernen möchten, wie Sie eine robuste CI/CD-Pipeline für Ihre App erstellen, empfehle ich How to Build an Effective Initial Deployment Pipeline with GitLab CI .