Raus aus der Legacy-Falle: Single Page Applications und Micro-Apps

Web Components

Eine Alternative um Micro-Apps in eine Shell zu laden stellt der Einsatz von Web Components dar. Einer der Standards hinter Web Components ist für solch ein Vorhaben besonders wichtig: Custom Elements. Damit lassen sich eigene HTML-Elemente erstellen. Aus Sicht von JavaScript verhalten sie sich genau wie ihre eingebauten Gegenstücke, zum Beispiel input oder table. Tags beschreiben sie und sie nehmen Daten über Eigenschaften auf JavaScript-Ebene oder Attribute auf HTML-Ebene entgegen und veröffentlichen mit Ereignissen Informationen. Das nachfolgende Beispiel zeigt, wie sich Custom Elements einbinden und nutzen lassen:

<script src="micro-app.bundle.js"></script>

<custom-slider min="-273" max="unbound" onchange="adjust(event)"></custom-slider>
<create-passenger-dialog ondone="callback(event)"></create-passenger-dialog>
<micro-app appState="…" onmessage="handle(event)"></micro-app>

Zunächst importiert das Beispiel ein Bundle mit den Custom Elements und ruft es durch Nutzung ihrer Tags auf. Über Attribute gibt der Aufrufer Daten weiter und lässt sich über Ereignisse informieren. Das Beispiel zeigt, dass Custom Elements auf verschiedenen Granularitätsebenen vorliegen können: Anwendungsfall-unabhängige Widgets (custom-slider), Anwendungsfall-bezogene Elemente (create-passenger) und ganze Anwendungen (micro-app). Letzteres mag auf den ersten Blick ein wenig verwundern, technisch gesehen handelt es sich bei Anwendungen aber nur um Komponenten, die aus weiteren Komponenten bestehen. Eine weitere Eigenschaft von Custom Elements ist, dass sie sich extrem einfach bei Bedarf laden lassen. Das ist gerade bei Micro-Apps wichtig, da jedes Team seine Bestandteile unabhängig bereitstellt. Die Shell kann sich somit bei Bedarf das neue Bundle schnappen und nutzen. Anstatt die oben gezeigten Tags direkt in der Seite zu platzieren, erzeugt sie die Shell mit traditionellem DOM-Code zur Laufzeit:

const configItem = this.config[name];
const content = document.getElementById('content');
const script = document.createElement('script');
script.src = configItem.path;
content.appendChild(script);
const element = document.createElement(configItem.element);
element.setAttribute('state', 'init');
element.addEventListener('message', msg => this.handle(msg));
content.appendChild(element);

Das Beispiel erzeugt mit createElement zwei Elemente, setzt ihre Attribute, definiert Ereignisbehandlungsroutinen und fügt sie zur Seite hinzu. Genauso flexibel lassen sich wiederverwendbare Widgets (siehe Abb. 2) einbinden, die auf Custom Elements basieren.

Zur Kommunikation zwischen Shell und Micro-App kommen das Attribut state sowie das Ereignis message zum Einsatz. Die Shell nutzt die Standardmechanismen, um die einzelnen Apps über einen Client-internen Message-Bus zu verknüpfen. Auch im Punkt Isolation haben Web Components etwas zu bieten: Dank des Standards Shadow DOM kann die Komponente sicherstellen, dass sich ihr Styling nicht auf andere Komponenten oder die Shell auswirkt. Die Isolation ist zwar nicht so stark wie bei iframes, aber für viele Fälle ist sie ausreichend. Vor allem dann, wenn die einzelnen Teams dem selben Unternehmen angehören, sollten selten Probleme aufkommen.

Außerdem können mittlerweile die einzelnen Browser sehr gut mit Web Components umgehen. Die meisten implementieren beispielsweise Custom Elements und Shadow DOM nativ. Für ältere Browser wie Internet Explorer liegen Polyfills vor. Die erlauben zwar nicht alle Optionen des Standards, allerdings kann man sich damit arrangieren – vor allem wenn die Tests den Umstand von Anfang an berücksichtigen.

Jetzt stellt sich nur noch die Frage, wo man die benötigten Web Components herbekommt. Gute Nachrichten: Viele Frameworks, wie Angular oder Vue unterstützen die darunterliegenden Standards bereits ab Werk. Die richtigen Einstellungen zur Laufzeit oder beim Kompilieren überführen in solchen Fällen die Framework-Komponenten in Web Components. Aber auch die nativen Browser-APIs für Web Components sind einfach zu nutzen, sodass sich ohne viel Aufwand jeder bestehende Teil einer SPA unabhängig von der genutzten Technik in eine Web Component überführen lässt.