Microfrontend-Implementierungen im Detail: Module Federation, iFrames und Web Components
Daniel Hayes
Full-Stack Engineer · Leapcell

Einleitung
Die moderne Webentwicklung entwickelt sich ständig weiter, mit einem wachsenden Fokus auf den Aufbau skalierbarer, wartbarer und unabhängig einsetzbarer Anwendungen. Wenn Front-End-Anwendungen in ihrer Komplexität und Teamgröße wachsen, werden Monolithen oft zu Engpässen, die die Agilität und die Entwicklerproduktivität behindern. Diese Herausforderung hat den Weg für die Einführung von Microfrontend-Architekturen geebnet, einem Paradigma, das die Prinzipien von Microservices auf das Front-End überträgt. Durch die Zerlegung großer Front-End-Anwendungen in kleinere, autonome Einheiten versprechen Microfrontends mehr Flexibilität, schnellere Entwicklungszyklen und verbesserte Teamautonomie. Die Wahl der richtigen Implementierungsstrategie für Microfrontends ist jedoch entscheidend. Dieser Artikel wird drei prominente Technologien für den Aufbau von Microfrontends gründlich untersuchen: Module Federation, iFrames und Web Components, und dabei ihre Ansätze, Anwendungsfälle vergleichen und Ihnen helfen, diese kritische Entscheidung zu treffen.
Kernkonzepte in Microfrontends
Bevor wir uns mit den Einzelheiten jeder Technologie befassen, wollen wir ein gemeinsames Verständnis einiger Kernkonzepte im Zusammenhang mit Microfrontend-Architekturen schaffen, die in unserer Diskussion erwähnt werden.
Microfrontend: Ein Architekturstil, bei dem eine Webanwendung aus vielen unabhängigen Front-End-Anwendungen besteht, die autonom entwickelt, deployed und verwaltet werden können.
Host-Anwendung: Die Hauptanwendung, die verschiedene Microfrontends orchestriert und integriert. Sie bietet das Shell- oder Layout für das gesamte Benutzererlebnis.
Remote-Anwendung (oder Child Microfrontend): Eine unabhängige Front-End-Anwendung, die innerhalb der Host-Anwendung geladen und angezeigt wird.
Isolation: Der Grad, in dem ein Microfrontend unabhängig arbeitet, ohne unbeabsichtigt andere Microfrontends oder den Host zu beeinträchtigen oder von ihnen beeinträchtigt zu werden. Dies umfasst die Isolation von JavaScript, CSS und globalem Zustand.
Laufzeitintegration (Runtime Integration): Der Prozess des Ladens und Anzeigens von Microfrontends innerhalb der Host-Anwendung, während die Anwendung im Browser läuft.
Build-Zeit-Integration (Build-Time Integration): Der Prozess, bei dem Microfrontends während des Build-Schritts vor dem Deployment zusammengefasst und gebündelt werden.
Gemeinsame Abhängigkeiten (Shared Dependencies): Gemeinsame Bibliotheken oder Frameworks (z. B. React, Vue, Lodash), die mehrere Microfrontends möglicherweise benötigen und die idealerweise nur einmal geladen werden sollten, um Leistungseinbußen zu vermeiden.
Module Federation
Module Federation, eingeführt mit Webpack 5, ist eine leistungsstarke und relativ neue Funktion, die entwickelt wurde, um die Herausforderungen des Teilens von Code und Abhängigkeiten zwischen unabhängig erstellten Anwendungen zu bewältigen. Sie ermöglicht es verschiedenen Webpack-Builds, Module zur Laufzeit voneinander verfügbar zu machen und zu konsumieren.
Funktionsweise
Im Kern ermöglicht Module Federation einer "Host"-Anwendung, Code dynamisch von "Remote"-Anwendungen zu laden. Sowohl Host- als auch Remote-Anwendungen sind im Wesentlichen Webpack-Builds, die für diese Rollen konfiguriert sind. Eine Remote-Anwendung stellt bestimmte Module als "federated modules" zur Verfügung, die die Host-Anwendung dann so konsumieren kann, als wären es lokale Module. Entscheidend hierbei ist das Konzept der "shared modules", bei dem gemeinsame Abhängigkeiten (wie React oder ein Designsystem) herausgefiltert und nur einmal geladen werden können, auch wenn mehrere federated modules davon abhängen.
Implementierungsbeispiel
Betrachten Sie ein Szenario, in dem unsere Host-Anwendung eine ProductDetail
-Komponente von einer Remote-Anwendung namens ProductApp
laden muss.
Remote (ProductApp
's webpack.config.js
):
const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; module.exports = { mode: 'development', entry: './src/index.js', output: { publicPath: 'http://localhost:8081/', // Öffentliche URL, unter der diese App bereitgestellt wird }, devServer: { port: 8081, }, plugins: [ new ModuleFederationPlugin({ name: 'productApp', filename: 'remoteEntry.js', exposes: { './ProductDetail': './src/components/ProductDetail', }, shared: { react: { singleton: true, requiredVersion: '^17.0.2' }, 'react-dom': { singleton: true, requiredVersion: '^17.0.2' }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], // ... andere Webpack-Konfigurationen };
Host (HostApp
's webpack.config.js
):
const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; module.exports = { mode: 'development', entry: './src/index.js', output: { publicPath: 'http://localhost:8080/', // Öffentliche URL, unter der diese App bereitgestellt wird }, devServer: { port: 8080, }, plugins: [ new ModuleFederationPlugin({ name: 'hostApp', remotes: { productApp: 'productApp@http://localhost:8081/remoteEntry.js', }, shared: { react: { singleton: true, requiredVersion: '^17.0.2' }, 'react-dom': { singleton: true, requiredVersion: '^17.0.2' }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], // ... andere Webpack-Konfigurationen };
Host (HostApp
's src/App.js
):
import React, { Suspense } from 'react'; const ProductDetail = React.lazy(() => import('productApp/ProductDetail')); const App = () => { return ( <div> <h1>Host Application</h1> <Suspense fallback={<div>Lädt Produktdetails...</div>}> <ProductDetail productId="123" /> </Suspense> </div> ); }; export default App;
In diesem Beispiel lädt die HostApp
dynamisch die ProductDetail
-Komponente von der ProductApp
zur Laufzeit. Gemeinsame Abhängigkeiten wie React werden optimiert, um nur einmal geladen zu werden.
Anwendungsfälle
Module Federation eignet sich hervorragend für Szenarien, in denen:
- Sie große, komplexe Komponenten oder ganze Teilapplikationen teilen müssen.
- Die optimierte Ladezeit gemeinsam genutzter Bibliotheken für die Leistung entscheidend ist.
- Teams hohe Autonomie über ihre Front-End-Entwicklungen und Deployments benötigen.
- Die Frameworks in verschiedenen Microfrontends gleich oder kompatibel sind (obwohl Module Federation im Allgemeinen gut mit Framework-Unterschieden umgeht, insbesondere mit Wrappern).
- Sie eine Single-Page-Anwendung (SPA) erstellen, die Funktionen aus mehreren Teams kombiniert.
iFrames
iFrames (Inline Frames) sind ein traditionelles HTML-Element, das es ermöglicht, ein anderes HTML-Dokument in das aktuelle HTML-Dokument einzubetten. Sie bieten ein hohes Maß an Isolation, da jeder iFrame in seinem eigenen Browserkontext läuft.
Funktionsweise
Ein iFrame erstellt im Wesentlichen einen separaten Browsing-Kontext. Das bedeutet, dass JavaScript, CSS und lokaler Speicher innerhalb eines iFrames vollständig vom übergeordneten Dokument und anderen iFrames isoliert sind. Die Kommunikation zwischen dem Elternteil und dem Kind (oder Kind-zu-Kind) basiert typischerweise auf der postMessage
-API, die sicher Nachrichten zwischen verschiedenen Ursprüngen sendet.
Implementierungsbeispiel
Host (index.html
):
<!DOCTYPE html> <html> <head> <title>Host App mit iFrame</title> </head> <body> <h1>Host Application</h1> <iframe id="productIframe" src="http://localhost:8082/product-app.html" style="width: 100%; height: 400px; border: 1px solid blue;" ></iframe> <script> const iframe = document.getElementById('productIframe'); // Auf Nachrichten vom iframe warten window.addEventListener('message', (event) => { if (event.origin !== 'http://localhost:8082') { // Ursprung zur Sicherheit überprüfen return; } console.log('Nachricht vom iframe:', event.data); // Beispiel: Host-UI basierend auf iframe-Nachricht aktualisieren }); // Nachricht an den iframe senden, nachdem er geladen wurde iframe.onload = () => { iframe.contentWindow.postMessage({ type: 'INIT_DATA', payload: { userId: 'abc' } }, 'http://localhost:8082'); }; </script> </body> </html>
Remote (product-app.html
ausgeführt auf http://localhost:8082
):
<!DOCTYPE html> <html> <head> <title>Product App (im iFrame)</title> </head> <body> <h2>Produktdetails</h2> <div id="product-info">Lädt...</div> <script> const productInfoDiv = document.getElementById('product-info'); // Auf Nachrichten vom übergeordneten Fenster warten window.addEventListener('message', (event) => { if (event.origin !== 'http://localhost:8080') { // Ursprung überprüfen return; } console.log('Nachricht vom Host:', event.data); if (event.data.type === 'INIT_DATA') { productInfoDiv.innerHTML = ``; } }); // Beispiel: Eine Nachricht an den Host senden setTimeout(() => { window.parent.postMessage({ type: 'PRODUCT_LOADED', payload: { productId: '456' } }, 'http://localhost:8080'); }, 2000); </script> </body> </html>
Anwendungsfälle
iFrames eignen sich, wenn:
- Maximale Isolation erforderlich ist, z. B. beim Einbetten von Inhalten Dritter oder potenziell unsicheren Anwendungen.
- Verschiedene Komponenten mit stark unterschiedlichen Technologie-Stacks erstellt werden und sie unabhängig voneinander ohne Störungen funktionieren müssen.
- Sie Legacy-Anwendungen in eine moderne Shell einbetten müssen.
- Sicherheitsbedenken eine strenge Trennung von Umgebungen erfordern (z. B. Zahlungs-Gateways).
- SEO keine primäre Sorge für den eingebetteten Inhalt ist (da Suchmaschinen historisch mit iFrame-Inhalten Schwierigkeiten hatten, obwohl sich dies verbessert hat).
- Der Performance-Overhead mehrerer Browser-Kontexte akzeptabel ist.
Web Components
Web Components sind eine Reihe von W3C-Standards, die es Entwicklern ermöglichen, benutzerdefinierte, wiederverwendbare und gekapselte HTML-Tags zu erstellen. Sie bieten eine native Möglichkeit, modulare Komponenten zu erstellen, die über verschiedene Frameworks und in Vanilla JavaScript verwendet werden können.
Funktionsweise
Web Components bestehen aus vier Haupttechnologien:
- Custom Elements: Ermöglicht das Definieren neuer HTML-Tags.
- Shadow DOM: Bietet eine gekapselte DOM und Stile für eine Komponente, isoliert sie von den Stilen und Skripten des Hauptdokuments.
- HTML Templates: Ermöglicht das Deklarieren von Markup-Fragmenten, die erst gerendert werden, wenn sie instanziiert werden.
- ES Modules: Zum Importieren und Exportieren von Modulen.
Sie bieten eine starke Kapselung von Markup und Stilen, was bedeutet, dass die interne Struktur und das Styling einer Komponente nicht nach außen dringen oder von externem CSS beeinflusst werden und umgekehrt. Die Kommunikation erfolgt typischerweise über Standard-DOM-Ereignisse und Eigenschaften/Attribute.
Implementierungsbeispiel
Benutzerdefinierte Elementdefinition (product-card.js
):
class ProductCard extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); // Ein Shadow DOM erstellen const template = document.createElement('template'); template.innerHTML = ` <style> .card { border: 1px solid #ccc; padding: 16px; margin: 16px; border-radius: 8px; font-family: sans-serif; } h3 { color: #333; } .price { font-weight: bold; color: green; } </style> <div class="card"> <h3></h3> <p class="description"></p> <p class="price"></p> <button>In den Warenkorb</button> </div> `; this.shadowRoot.appendChild(template.content.cloneNode(true)); this.titleElement = this.shadowRoot.querySelector('h3'); this.descriptionElement = this.shadowRoot.querySelector('.description'); this.priceElement = this.shadowRoot.querySelector('.price'); this.button = this.shadowRoot.querySelector('button'); this.button.addEventListener('click', () => { this.dispatchEvent(new CustomEvent('add-to-cart', { detail: { productId: this.getAttribute('product-id') }, bubbles: true, composed: true // Ermöglicht der Ereignis, die Shadow DOM-Grenze zu überschreiten })); }); } // Beobachtete Attribute definieren static get observedAttributes() { return ['product-id', 'title', 'description', 'price']; } // Auf Attributänderungen reagieren attributeChangedCallback(name, oldValue, newValue) { if (name === 'title') { this.titleElement.textContent = newValue; } else if (name === 'description') { this.descriptionElement.textContent = newValue; } else if (name === 'price') { this.priceElement.textContent = `$ {parseFloat(newValue).toFixed(2)}`; } } } // Das benutzerdefinierte Element definieren customElements.define('product-card', ProductCard);
Host (index.html
):
<!DOCTYPE html> <html> <head> <title>Host App mit Web Components</title> <script type="module" src="./product-card.js"></script> </head> <body> <h1>Host Application</h1> <div id="product-list"> <product-card product-id="P001" title="Super Widget" description="Das beste Widget, das Sie je besitzen werden." price="29.99" ></product-card> <product-card product-id="P002" title="Mega Gadget" description="Ein revolutionäres Gerät für das moderne Leben." price="99.00" ></product-card> </div> <script> document.getElementById('product-list').addEventListener('add-to-cart', (event) => { console.log('Produkt in den Warenkorb gelegt:', event.detail.productId); // Warenkorb-Logik in der Host-Anwendung verarbeiten }); </script> </body> </html>
Anwendungsfälle
Web Components eignen sich am besten für:
- Die Erstellung wiederverwendbarer UI-Komponenten, die über verschiedene Projekte, Teams und sogar Frameworks hinweg geteilt werden können.
- Entwicklung von Framework-agnostischen Komponenten.
- Wenn eine starke Kapselung von UI und Stilen gewünscht ist, ohne den Overhead von iFrames.
- Den Aufbau von Designsystemen, bei denen einzelne Komponenten hochgradig portabel und wartbar sein müssen.
- Hybride Anwendungen, bei denen Teile mit unterschiedlichen Frameworks erstellt werden (z. B. React-Host mit Vue Web Components).
Vergleich der Microfrontend-Implementierungen
Fassen wir die wichtigsten Merkmale, Vor- und Nachteile jeder Methode zusammen:
Merkmal/Kriterium | Module Federation | iFrames | Web Components |
---|---|---|---|
Isolationsgrad | Hoch (Laufzeit), gemeinsam genutzter Kontext für JS & CSS oft | Sehr hoch (Browserkontext) | Hoch (Shadow DOM für CSS, native JS-Isolation) |
Laufzeit-Performance | Gut (gemeinsame Abhängigkeiten, Lazy Loading) | Mittel bis schlecht (neuer Browserkontext pro iFrame) | Gut (native Browserfunktionen) |
Entwicklungskomplexität | Mittel (Webpack-Konfiguration kann komplex sein) | Niedrig bis mittel (Standard-HTML, postMessage ) | Mittel (native APIs, kann umständlich sein) |
Teilen von Abhängigkeiten | Hervorragend (native Webpack-Funktion) | Schlecht (jeder iFrame lädt seine eigenen Abhängigkeiten) | Mittel (globaler Geltungsbereich, externe Mechanismen) |
Framework-Agnostik | Gut (kann verschiedene Frameworks wrappen) | Hervorragend (vollständig getrennte Apps) | Hervorragend (nativer Standard) |
Routing | Hochgradig flexibel (Integration in den Host-Router) | Komplex (jeder iFrame hat seine eigene Historie/URL) | Flexibel (Integration in den Host-Router) |
Kommunikation | Direkte JS-Aufrufe, gemeinsames Zustandsmanagement | postMessage -API | Benutzerdefinierte Ereignisse, Eigenschaften/Attribute |
SEO-Auswirkung | Generell gut (dynamisches Laden, einheitliche DOM) | Potenziell schlecht (Inhalt in getrennten Kontexten) | Generell gut (einheitliche DOM) |
Anwendungsfälle | SPAs mit lose gekoppelten Funktionen, gemeinsame Bibliotheken, Teamautonomie | Einbetten fremder Inhalte, Legacy-Apps, hohe Sicherheitsisolation | Wiederverwendbare UI-Komponenten, Designsysteme, Framework-agnostische Bibliotheken |
Stilgebung | Gemeinsame Stylesheets, CSS-in-JS, CSS-Module | Völlig isoliert (separate Stylesheets) | Gekapselt (Shadow DOM CSS) |
Bundle-Größe | Optimiert durch Teilen gemeinsamer Bibliotheken | Kann groß sein (jede App bündelt ihre eigenen Abhängigkeiten) | Relativ klein (native APIs) |
Fazit
Die Wahl zwischen Module Federation, iFrames und Web Components für die Implementierung von Microfrontends hängt weitgehend von Ihren spezifischen Projektanforderungen, der vorhandenen Infrastruktur, dem Budget und dem gewünschten Grad der Isolation ab. Module Federation bietet eine ausgeklügelte, Webpack-gesteuerte Lösung für die Realisierung hochgradig integrierter und performanter Microfrontends, insbesondere innerhalb eines JavaScript-Ökosystems. iFrames bieten eine unübertroffene Isolation und eignen sich daher ideal zum Einbetten unterschiedlicher Anwendungen oder nicht vertrauenswürdiger Inhalte, allerdings auf Kosten potenzieller Performance- und Integrationskomplexität. Web Components bieten eine native und Framework-agnostische Möglichkeit, wiederverwendbare UI-Komponenten mit starker Kapselung zu erstellen und die Lücke für gemeinsame Designsysteme und Framework-übergreifende Integrationen zu schließen. Jede Methode bringt einzigartige Stärken mit sich, und ein klares Verständnis ihrer jeweiligen Kompromisse ist unerlässlich für den Aufbau robuster und skalierbarer Microfrontend-Architekturen.