Die Micro-Frontend-Revolution: Webpack 5 Module Federation

Seite 3: Konfiguration der Shell

Inhaltsverzeichnis

Die Konfiguration der Shell ähnelt der von Micro Frontends. Für jedes Micro Frontend legt die Konfiguration einen Remote fest. Dazu bildet sie den Shell-internen Namen auf den Namen des Remotes ab. In diesem einfachen Beispiel kommt für beides der Name mfe1 (Micro Frontend 1) zum Einsatz.

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  output: {
    publicPath: "http://localhost:5000/",
    uniqueName: "shell"
  },
  optimization: {
    // Only needed to bypass a temporary bug in Angular CLI 11 Beta
    runtimeChunk: false
  },
  plugins: [
    new ModuleFederationPlugin({
        remotes: {
            'mfe1': "mfe1@http://localhost:3000/remoteEntry.js" 
        },
        shared: ["@angular/core", "@angular/common", "@angular/router"]
    })
  ],
};

Zur Vereinfachung enthält das Listing auch die URL des Remote Entry. Wie oben erwähnt kann dieser auch auf andere Wege angegeben werden, zum Beispiel über ein Script-Tag oder dynamisch über die Webpack Runtime API.

Der Code zum Laden des Micro Frontend unterscheidet sich nicht von jenem Code, der auch für Lazy Loading zum Einsatz käme. Das gezeigte Beispiel nutzt dazu eine Route-Konfiguration, die beim Aufruf des Pfades flights das Micro Frontend aktiviert.

export const APP_ROUTES: Routes = [
    […]
    {
      path: 'flights',
      loadChildren: () => import('mfe1/Module').then(m => m.FlightsModule)
    },
];

Aus Sicht dieses Quellcodes passiert hier nichts Besonderes. Die eigentliche Arbeit übernimmt Webpack im Unterbau. Da es feststellt, dass mfe1 auf eine externe Anwendung verweist, führt es die nötigen Schritte durch, um dessen veröffentlichtes Modul zur Laufzeit zu laden.

Lediglich TypeScript ist ein wenig zu besänftigen. Da es die hier importierte Datei mfe1/Module nicht kennt, liefert es ansonsten einen Kompilierfehler. Dieses Problem lässt sich jedoch mit einer Typdeklaration lösen. Dazu ist lediglich in einer beliebigen auf .dts endenden Datei der folgende Eintrag zu hinterlegen: declare module 'mfe1/Module';

Da Module Federation die zu nutzenden Versionen der geteilten Bibliotheken aushandelt, müssen diese über dynamische Importe geladen werden. Sie sind asynchron und erlauben das Ermitteln und Laden der zu nutzenden Bibliotheksversion.

Damit sich Entwicklerinnen und Entwickler nicht ständig mit dieser Einschränkung konfrontiert sehen, empfiehlt es sich, stattdessen die gesamte Anwendung über einen dynamischen Import zu laden. Der Einsprungspunkt der Anwendung – bei Angular ist das für gewöhnlich die Datei main.ts – besteht somit nur mehr aus einem einzigen dynamischen Import: import('./bootstrap');. Dieser lädt ein weiteres TypeScript-Modul, das sich um das Bootstrapping der Anwendung kümmert.