Webpack Module Federation ist ein Ansatz, um Module in modernen Anwendungen zu bündeln und zu teilen. Mit dieser Technik lassen sich Module erstellen, die zur Laufzeit anstelle zur Build-Zeit eingebunden werden. Dies eröffnet völlig neue Möglichkeiten für die Entwicklung von Micro-Frontends und dynamischen Anwendungen.
Um die technischen Details genau zu verstehen, empfehlen wir einen Blick auf unseren detaillierten Blogpost zu werfen: Micro Frontends Teil 3 - Webpack 5 Module Federation als Game Changer
Problem?
Die Integration von Webpack Module Federation ist prinzipiell einfach, allerdings treten Herausforderungen beim Build der nutzenden Anwendung auf:
Die Integration erfolgt zur Laufzeit durch Laden von JavaScript-Code. Zur Build-Zeit der nutzenden Anwendung fehlen somit für den TypeScript-Transpiler jegliche Typ-Informationen, was zu Fehlern und unsicherem Code führen kann. Um dies zu vermeiden, müssen die Typ-Informationen zur Build-Zeit korrekt eingebunden werden – aber wie genau?
Lösung!
Die Lösung für dieses Problem besteht darin, Type-Definitions als eigene NPM-Bibliothek zu erstellen und als Dev-Dependency in die Projektstruktur zu integrieren.
Doch eine Dependency?
Vielleicht kommt nun direkt ein Gedanke hoch: Jetzt gibt es ja wieder eine Build-Time-Dependency!
Die kurze Antwort darauf ist: Ja – und das ist gut so!
Die etwas längere Antwort ist: Eine Abhängigkeit gibt es in jedem Fall! Die nutzende Anwendung muss mit dem Modul interagieren, was über JavaScript-Calls oder Custom Elements erfolgen kann. In beiden Fällen existiert damit aber eine Schnittstelle, auf die sich beide Teilnehmer einigen müssen. In diesem Beispiel wird die Schnittstelle nun nur explizit und vom Transpiler prüfbar.
Aber wie funktioniert die Lösung?
Generell setzt der Ansatz darauf, dass zur Build-Zeit lediglich Typ-Informationen notwendig sind – jedoch keine Implementierungen. Zur Laufzeit ist alles JavaScript und es sind nur die Implementierungen und Bezeichner relevant.
Type-Definitions als eigene NPM-Bibliothek erstellen
Wir nehmen an, dass wir @viadee/my-library
für die Bibliothek verwenden wollen.
Zunächst erstellen wir eine neue NPM-Bibliothek für die Type-Definitions. Für @viadee/my-library
wäre das @types/viadee__my-library
. Diese Bibliothek wird sämtliche Typdefinitionen für das Modul enthalten. Der Typescript-Transpiler wird für Typ-Informationen automatisch auch in unserer Types-Bibliothek suchen.
Remote-Konfiguration für Webpack
In der Remote-Konfiguration von Webpack sollte der Name so gewählt werden, dass er zur erstellten Type-Definitions Bibliothek passt. Hier sollten wir also @viadee/my-library
verwenden.
Pfade der Type-Definitions abstimmen
Die Exporte der Module-Federation müssen den Pfaden der Type-Definitions entsprechen. Wenn beispielsweise ein Pfad /some/stuff
im Webpack-Export verwendet wird, muss es eine entsprechende Type-Definition /some/stuff.d.ts
oder /some/stuff/index.d.ts
geben.
Type-Definitions als Dev-Dependency importieren
Schließlich wird die Type-Definitions Bibliothek als Dev-Dependency zum Projekt hinzugefügt. Dadurch stehen bei einem Import auch die erforderlichen Typ-Informationen für den Compiler zur Verfügung:
npm install --save-dev @types/viadee__my-library
Nun können wir die Module mit den entsprechenden Typinformationen importieren:
import { myFunction } from '@viadee/my-library/some/stuff';
Zur Build-Zeit wird lediglich die Typ-Information aus unserer Dev-Dependency herangezogen. Zur Laufzeit wird Webpack für den Import das federated Module laden.
Kleine RahmenbedingunG
Wichtig ist dabei zu beachten, dass das Laden eines federated Modules aufgrund des http-Request asynchron ist. Damit muss der Import auch asynchron sein. Da das obige Beispiel jedoch kein asynchroner Import ist, müssen wir beim Initialisieren der nutzenden Anwendung sicherstellen, dass das Bootstrapping asynchron erfolgt.
D.h., dass alle Importe von Modulen in unserer Anwendung, welche von dieser Lösung Gebrauch machen, durch einen asynchronen Import entkoppelt werden müssen. Die einfachste Lösung dazu ist auch im oben verlinkten Blogpost bereits beschrieben:
Alle Importe aus unserer index.ts
wandern in eine bootstrap.ts
, die dann aus der index.ts
asynchron geladen wird:
import('./bootstrap.ts')
Fazit
Mit diesem Ansatz können wir Webpack Module Federation nutzen und gleichzeitig von den Stärken von TypeScript profitieren. Die Type-Safety bleibt erhalten und die Code-Basis bleibt robust und wartbar.
Haben wir Ihr Interesse geweckt? Melden Sie sich gerne bei uns.
zurück zur Blogübersicht