Host, Remote, Shared Dependencies und Deployment
Module Federation in Webpack 5 löst das bisher schwierigste Problem bei Micro-Frontend-Architekturen: Wie mehrere unabhängige Teams ihre JavaScript-Bundles zur Laufzeit kombinieren, ohne gemeinsame Dependencies mehrfach zu laden oder eine zentrale Build-Pipeline zu teilen.
Inhaltsverzeichnis
- 1. Das Problem, das Module Federation löst
- 2. Host, Remote und Shared: die drei Kernkonzepte
- 3. Webpack 5 ModuleFederationPlugin konfigurieren
- 4. Shared Dependencies: Versionierung und Singleton
- 5. Dynamisches Laden von Remote-Modulen zur Laufzeit
- 6. Module Federation mit Vite: @originjs/vite-plugin-federation
- 7. Routing in Micro-Frontend-Architekturen
- 8. Deployment-Strategien und CDN-Konfiguration
- 9. Module Federation vs. andere Ansätze
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem, das Module Federation löst
Große Frontend-Anwendungen wachsen mit der Zeit zu schwer wartbaren Monolithen heran: Ein einziges Repository, eine einzige Build-Pipeline, ein einziges Deployment. Wenn 15 Teams an derselben Codebase arbeiten, wird jeder Merge zu einem Koordinationsproblem, jedes Deployment zu einem Risiko für alle. Micro-Frontends sind die Antwort: Dieselbe Frontend-Anwendung wird in unabhängige, team-eigene Teile aufgeteilt, die separat entwickelt, getestet und deployed werden können. Das klingt einfach – aber der technische Knackpunkt war lange, wie diese Teile im Browser zusammenkommen, ohne dass React dreimal geladen wird oder globale CSS-Klassen sich überschreiben.
Module Federation, eingeführt mit Webpack 5, ist die erste Build-Tool-native Lösung für dieses Problem. Es erlaubt einer JavaScript-Anwendung, zur Laufzeit Code aus einer anderen, völlig separaten Webpack-Build zu laden – mit expliziter Kontrolle über geteilte Dependencies und ohne den Overhead eines zentralen Build-Schritts. Das ist keine neue Idee: iframes, Script-Tags und Single-SPA haben dasselbe Ziel verfolgt. Aber Module Federation ist die erste Lösung, die tief in das JavaScript-Modulsystem integriert ist und damit natürlich mit modernen Frameworks funktioniert.
2. Host, Remote und Shared: die drei Kernkonzepte
Das Modell von Module Federation baut auf drei Rollen auf. Ein Host ist die Anwendung, die andere Module lädt – typischerweise die Shell oder das App-Skeleton. Ein Remote ist eine Anwendung, die Module für andere Hosts bereitstellt – ein Team-eigener Bereich der Anwendung, der separat deployed wird. Dasselbe Webpack-Bundle kann gleichzeitig Host und Remote sein: Eine Produktdetail-App kann Module von einer Bewertungs-App laden (Remote konsumieren) und gleichzeitig ihre eigenen Komponenten für eine übergeordnete Shell bereitstellen (als Remote auftreten).
Shared ist das dritte Konzept: Libraries, die zwischen Host und Remote geteilt werden sollen, ohne mehrfach in den Browser geladen zu werden. React ist das klassische Beispiel – wenn Host und Remote beide React laden, würden beide React-Instanzen aktiv sein, was zu Bugs führt, weil React intern globalen Zustand (Hooks, Context) nutzt. Mit shared: { react: { singleton: true } } stellt Module Federation sicher, dass nur eine React-Instanz im Browser existiert, egal welcher Teil der Anwendung zuerst geladen wird.
// webpack.config.js — HOST application (App Shell)
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
mode: "development",
plugins: [
new ModuleFederationPlugin({
name: "shell", // unique name for this application
remotes: {
// Reference remote applications by name + their manifest URL
productApp: "productApp@https://products.mironsoft.de/remoteEntry.js",
cartApp: "cartApp@https://cart.mironsoft.de/remoteEntry.js",
},
shared: {
react: { singleton: true, requiredVersion: "^18.2.0" },
"react-dom": { singleton: true, requiredVersion: "^18.2.0" },
},
}),
],
};
// webpack.config.js — REMOTE application (Product Team)
module.exports = {
mode: "development",
plugins: [
new ModuleFederationPlugin({
name: "productApp", // must match the key in host remotes
filename: "remoteEntry.js", // manifest file — must be publicly accessible
exposes: {
// Map public names to local module paths
"./ProductCard": "./src/components/ProductCard",
"./ProductDetail": "./src/pages/ProductDetail",
"./useCart": "./src/hooks/useCart",
},
shared: {
react: { singleton: true, requiredVersion: "^18.2.0" },
"react-dom": { singleton: true, requiredVersion: "^18.2.0" },
},
}),
],
};
4. Shared Dependencies: Versionierung und Singleton
Das Shared-Dependencies-Konzept in Module Federation ist der komplexeste Teil der Konfiguration und die häufigste Fehlerquelle. Wenn Host und Remote unterschiedliche Versionen einer Bibliothek angeben, entscheidet Module Federation anhand der Semver-Kompatibilität, ob eine Version für beide ausreicht oder ob beide Versionen geladen werden müssen. Mit requiredVersion: "^18.2.0" signalisiert ein Bundle, dass es mit jeder Patch- oder Minor-Version ≥ 18.2.0 kompatibel ist. Ist die höchste kompatible Version bereits geladen, wird sie wiederverwendet – kein zweiter Download.
Das singleton: true-Flag erzwingt, dass maximal eine Instanz im Browser existiert – auch wenn Host und Remote inkompatible Versionen hätten. In diesem Fall wird eine Warnung ausgegeben, und die höhere Version gewinnt. Für React ist Singleton zwingend, weil zwei React-Instanzen im Browser zu dem bekannten "Invalid hook call"-Fehler führen. Für andere Libraries wie Lodash oder Axios ist Singleton optional – mehrere Instanzen koexistieren, die Anwendung funktioniert, verschwendet aber Bandbreite. Die eager: true-Option lädt die Bibliothek sofort statt erst beim ersten Import – wichtig für den App-Einstiegspunkt, der sonst auf den asynchronen Ladevorgang warten muss.
5. Dynamisches Laden von Remote-Modulen zur Laufzeit
Die statische Konfiguration im webpack.config.js ist nur die halbe Geschichte. Module Federation unterstützt auch das vollständig dynamische Laden von Remote-Modulen zur Laufzeit – ohne dass die URL des Remote-Bundles zur Build-Zeit bekannt sein muss. Das ermöglicht Szenarien wie A/B-Tests, bei denen der Server zur Laufzeit entscheidet, welche Version einer Komponente geladen wird, oder Plugin-Systeme, bei denen Endnutzer eigene Module integrieren können.
Das Dynamic Remote Loading Pattern nutzt die __webpack_init_sharing__ und __webpack_share_scopes__ APIs, um den Shared-Scope manuell zu initialisieren, bevor ein dynamisch geladenes Remote-Modul verwendet wird. Im Kern läuft es auf einen import() des Remote-Manifests hinaus, gefolgt von einem Aufruf der init und get-Funktionen des Remote-Containers. Dieser Mechanismus macht Module Federation zur Grundlage für echte Plugin-Architekturen im Frontend, bei denen Drittparteien Module bereitstellen können, die die Host-Anwendung zur Laufzeit integriert.
// Dynamic remote loading — URL not known at build time
async function loadRemoteModule(remoteUrl, scope, module) {
// Step 1: inject the remote entry script dynamically
await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = remoteUrl;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
// Step 2: initialize the shared scope
await __webpack_init_sharing__("default");
// Step 3: get the container from the window (set by remoteEntry.js)
const container = window[scope];
await container.init(__webpack_share_scopes__.default);
// Step 4: get the requested module factory
const factory = await container.get(module);
return factory(); // returns the module
}
// Usage: load any remote at runtime
async function renderProductCard(productId) {
const { default: ProductCard } = await loadRemoteModule(
"https://products.mironsoft.de/remoteEntry.js",
"productApp", // window[scope] must match the remote's `name`
"./ProductCard" // must be listed in the remote's `exposes`
);
// Use ProductCard as a normal React component
}
// React lazy + Suspense integration
const RemoteProductCard = React.lazy(() =>
import("productApp/ProductCard") // static reference via webpack remotes config
);
// Usage: <Suspense fallback={<Spinner />}><RemoteProductCard id={1} /></Suspense>
6. Module Federation mit Vite
Während Module Federation nativ in Webpack 5 integriert ist, bietet das Ökosystem mit @originjs/vite-plugin-federation und dem offizielleren @module-federation/vite (ab 2024) Plugins für Vite-basierte Projekte. Die Konfiguration folgt demselben Host/Remote/Shared-Muster wie in Webpack, wird aber als Vite-Plugin in der vite.config.ts eingebunden. Ein wichtiger Unterschied: Vite nutzt im Development-Modus ES Modules und native Browser-Imports, während der Production-Build in ein Webpack-kompatibles Format kompiliert wird.
Die Interoperabilität zwischen Webpack-Hosts und Vite-Remotes (und umgekehrt) ist technisch möglich, erfordert aber sorgfältige Abstimmung der remoteEntry.js-Formate. Die neuere Spezifikation "Module Federation 2.0" zielt auf Build-Tool-Agnostizität ab und soll nahtlose Interoperabilität zwischen Webpack, Vite, Rspack und anderen ermöglichen. Für neue Projekte empfiehlt sich, das gesamte Micro-Frontend-Ökosystem auf dasselbe Build-Tool zu standardisieren und Vite 5 mit @module-federation/vite oder Rspack mit nativem Federation-Support zu verwenden.
7. Routing in Micro-Frontend-Architekturen
Routing ist einer der kniffligsten Aspekte von Micro-Frontend-Architekturen mit Module Federation. Wenn jedes Remote-Modul sein eigenes Router-Setup mitbringt, entstehen Konflikte: Welcher Router ist für welche URL zuständig? Das gängigste Muster ist "Top-Level-Routing im Host, Sub-Routing im Remote": Der Host-Router entscheidet auf Basis des URL-Pfades, welches Remote-Modul geladen wird. Das Remote-Modul erhält eine Base-URL als Prop und rendert sein eigenes Routing unterhalb dieses Basispfades.
Für React-Projekte bedeutet das konkret: Der Host verwendet React Router v6 mit einem <Route path="/products/*">-Catch-all, der das Remote-Modul lädt. Das Remote erhält basename="/products" und rendert seine eigenen Routen relativ dazu. Ein häufiger Bug: Das Remote-Modul importiert BrowserRouter statt MemoryRouter oder Router mit dem vom Host übergebenen History-Objekt. Das führt zu zwei konkurrierenden Router-Instanzen, die beide auf den Browser-History-Stack schreiben und gegenseitig die URL überschreiben.
8. Deployment-Strategien und CDN-Konfiguration
Das größte Versprechen von Module Federation ist das unabhängige Deployment: Jedes Team kann sein Remote-Modul deployen, ohne andere Teams zu informieren. Aber dieses Versprechen funktioniert nur, wenn das Deployment korrekt konfiguriert ist. Der publicPath in der Webpack-Konfiguration muss der tatsächlichen URL entsprechen, unter der das Bundle ausgeliefert wird. Für CDN-Deployments sollte publicPath: "auto" verwendet werden, damit Webpack die URL der remoteEntry.js automatisch als Basis für alle anderen Chunk-URLs verwendet.
Versionierung der Remote-Entries ist entscheidend für Zero-Downtime-Deployments. Wenn ein Host eine neue Version des Remote lädt und das alte Bundle noch im Browser-Cache ist, können Inkompatibilitäten entstehen. Das Standardmuster: remoteEntry.js wird nie gecacht (Cache-Control: no-store oder kurze TTL), während alle anderen Chunks mit inhaltsbasiertem Hashing langfristig gecacht werden. Der Host lädt immer die aktuelle remoteEntry.js, die dann auf die richtigen Chunk-URLs verweist. Dieser Mechanismus macht Module Federation Deployments so zuverlässig wie traditionelle CDN-Deployments.
9. Module Federation vs. andere Ansätze
Es gibt mehrere technische Ansätze für Micro-Frontend-Architekturen, die sich in Isolation, Performance und Komplexität unterscheiden.
| Ansatz | Isolation | Shared Dependencies | Komplexität |
|---|---|---|---|
| Module Federation | JS-Scope (kein CSS) | Automatisch via Plugin | Mittel |
| iframes | Vollständige Isolation | Keine – alles mehrfach geladen | Niedrig |
| Web Components | Shadow DOM für CSS | Manuell via Import Maps | Mittel |
| Single-SPA | JS-Scope | Manuell via SystemJS | Hoch |
| Import Maps | Keine | Nativ via Browser | Niedrig |
// vite.config.ts — Vite Module Federation (host)
import { defineConfig } from "vite";
import federation from "@originjs/vite-plugin-federation";
export default defineConfig({
plugins: [
federation({
name: "shell",
remotes: {
productApp: "https://products.mironsoft.de/assets/remoteEntry.js",
},
shared: ["react", "react-dom"],
}),
],
build: {
target: "esnext", // required for top-level await in federation runtime
minify: false, // easier debugging during federation setup
},
});
// vite.config.ts — Vite Module Federation (remote)
export default defineConfig({
plugins: [
federation({
name: "productApp",
filename: "remoteEntry.js",
exposes: {
"./ProductCard": "./src/components/ProductCard.tsx",
},
shared: ["react", "react-dom"],
}),
],
build: {
target: "esnext",
},
});
// Usage in host component — same as Webpack federation
// const ProductCard = React.lazy(() => import("productApp/ProductCard"));
Mironsoft
Micro-Frontend-Architektur, Module Federation und Frontend-Infrastruktur
Micro-Frontend-Architektur für euer Team einführen?
Wir entwerfen und implementieren Module Federation Architekturen für Multi-Team-Frontends – von der Webpack/Vite-Konfiguration über Shared-Dependency-Strategien bis zur CDN-Deployment-Pipeline.
Architektur-Design
Host/Remote-Aufteilung, Shared-Dependency-Strategie und Routing-Konzept
Build-Konfiguration
Webpack 5 und Vite Federation Plugin Setup mit CI/CD-Integration
CDN-Deployment
publicPath, Cache-Strategie und Zero-Downtime-Deployment für Remote-Entries
10. Zusammenfassung
Module Federation ist das Fundament moderner Micro-Frontend-Architekturen: Es ermöglicht das Laden von JavaScript-Modulen aus anderen, separat deployten Anwendungen zur Laufzeit. Das Host/Remote/Shared-Modell gibt Teams volle Autonomie bei Entwicklung und Deployment, während der Shared-Dependency-Mechanismus sicherstellt, dass React, Vue und andere Frameworks nicht mehrfach in den Browser geladen werden. Webpack 5 hat Federation nativ integriert; Vite-Plugins schließen die Lücke für Vite-basierte Projekte.
Die wichtigsten Fallstricke: Singleton-Konfiguration für React und Context-abhängige Bibliotheken, korrektes publicPath-Handling für CDN-Deployments und eine klare Routing-Strategie, die verhindert, dass mehrere Router-Instanzen gleichzeitig auf den Browser-History-Stack schreiben. Module Federation ist keine Technologie für jedes Projekt – ab etwa drei unabhängigen Teams mit getrennten Deployment-Zyklen beginnt die Investition in die Infrastruktur jedoch, sich durch reduzierte Koordinationskosten auszuzahlen.
Module Federation und Micro-Frontends — Das Wichtigste auf einen Blick
Host & Remote
Host konsumiert Remote-Module zur Laufzeit. Remote exposed Module via remoteEntry.js. Beide können gleichzeitig Host und Remote sein.
Shared Dependencies
singleton: true für React und Context-abhängige Libs. requiredVersion für Semver-Kompatibilitätsprüfung. eager: true für den App-Einstiegspunkt.
Deployment
publicPath: "auto" für CDN. remoteEntry.js ohne Cache (no-store). Chunks mit Content-Hash langfristig cachen. Teams deployen unabhängig.
Vite & Alternativen
@module-federation/vite für Vite-Projekte. Module Federation 2.0 für Build-Tool-Agnostizität. Rspack mit nativem Federation-Support als Webpack-Alternative.