Die Micro-Frontend-Revolution: Webpack 5 Module Federation
Das neue Feature des Bundlers für JavaScript-Module erlaubt das Laden von Anwendungsteilen aus separat kompilierten und bereitgestellten Anwendungen.
![Die Micro-Frontend-Revolution: Webpack 5 Module Federation](https://heise.cloudimg.io/width/610/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/8/6/0/0/8/Pakete-51be00a491c081de.jpeg)
(Bild: QuinceMedia, gemeinfrei)
- Manfred Steyer
Es ist hinlänglich bekannt, dass monolithische Anwendungen schwierig zu warten sind. Im Backend sind deswegen Microservices derzeit äußerst beliebt. Jeder Microservice repräsentiert eine in sich geschlossene Domäne und soll möglichst wenig von anderen Microservices wissen. Einzelne Teams können somit weitgehend autark an einem Microservice arbeiten.
Analog dazu ist bei Clients von Micro Frontends die Rede: kleine Anwendungen, die gemeinsam ein größeres Ganzes ergeben, ohne voneinander abhängig zu sein. Die Implementierung solcher Software war bis jetzt jedoch schwierig. Es fehlte an guten Mechanismen, um die einzelnen Micro Frontends ineinander zu integrieren.
Die populäre Bundling-Software Webpack ändert das nun mit Version 5, indem es ein Feature namens Module Federation einführt. Damit hat eine Anwendung die Möglichkeit, Programmteile aus einer anderen Anwendung zu laden. Die einzelnen Applikationen lassen sich separat entwickeln, kompilieren und bereitstellen. Dieser Artikel zeigt, wie sich Module Federation nutzen lässt. Der verwendete Quellcode findet sich auf GitHub.
Die Notwendigkeit für Module Federation
Es mag verwundern, dass für das Laden von Programmteilen aus anderen Webanwendungen eine eigene Funktionsweise wie Module Federation notwendig ist. Letztlich bestehen sie aus einzelnen Dateien, die über eine URL erreichbar sind. Jede Applikation kann also prinzipiell auch Dateien aus dem Hoheitsgebiet anderer Anwendungen laden. Insofern müsste ein Aufruf wie dieser hier genügen:
const comp = await import('http://otherApp/comp'); // does not work!
Leider ist das jedoch zu kurz gedacht, denn Bundler wie Webpack erlauben so etwas nicht. Der Grund ist, dass Webpack vorsieht, dass sämtliche Abhängigkeiten beim Bauen der Anwendung vorhanden sind. Er kompiliert den gesamten Quellcode zu möglichst kleinen Bundles. Dank des sogenannten Tree-Shaking entfernt er auch nicht benötigte Codestrecken wie nie durchlaufene Framework-Bestandteile. Erst dann splittetet Webpack den Code in sogenannte Chunks, die sich per Lazy Loading in die Anwendung laden lassen.
Die Idee hinter Module Federation
Module Federation definiert zwei Arten von Anwendungen: den Host und den Remote. In einem Micro Frontend ist der Host die Rahmenanwendung (Shell), der Micro Frontends lädt, die als Remote auftreten. Lädt ein Micro Frontend weitere Micro Frontends, tritt es in beiden Rollen auf. Die Details hierzu sind in der Webpack-Konfiguration der Anwendungen einzutragen (s. Abb. 1).
Der Host definiert URLs, die auf einzelne Remotes verweisen. Im gezeigten Fall verweist jede URL, die mit mfe1
(Micro Frontend 1) beginnt, auf das daneben dargestellte Micro Frontend mit demselben Namen.
Das Micro Frontend veröffentlicht Teile seines Programmcodes. Jenes in Abbildung 1 veröffentlicht beispielsweise eine Komponente unter dem Namen Component
. Der Host kann diese Komponente nun mit einem dynamischen import
zur Laufzeit laden. Dazu nutzt er eine URL, die sich aus den auf beiden Seiten konfigurierten Namen zusammensetzt.
Damit die Shell weiß, wo sie das Micro Frontend findet und wie sie damit kommunizieren kann, generiert Webpack für das Micro Frontend einen sogenannten Remote Entry. Dabei handelt es sich um eine minimale JavaScript-Datei, die in die Shell zu laden ist (s. Abb. 2).
Der Remote Entry lässt sich auf verschiedene Weisen laden. Man kann ihn beispielsweise direkt in der Konfiguration des Hosts hinterlegen oder mit einem Script-Tag referenzieren. Letzterer lässt sich auch dynamisch mit JavaScript erzeugen.
Daneben kann eine Anwendung den Eintrag auch erst bei Bedarf mit der Webpack Runtime API laden. Das erlaubt dynamische Szenarien, bei denen sich die Shell zum Beispiel erst zur Laufzeit über die einzelnen Micro Frontends informiert.