micro-frontends
Tutorials |

So nutzt du framework-unabhängige Web Components in deiner React-App

jannik.jpg.png

Jannik

1. Dezember 2020

Hinweis: Dieser Artikel wurde unter Zuhilfenahme von KI aus dem Englischen übersetzt. Hier geht's zum Originaltext.

Egal ob du eine Anwendung nach den Prinzipien der Micro Frontends entwickelst oder einfach nur eine dynamisch geladene Web Component in deiner React-Anwendung nutzen möchtest – dieser Blogbeitrag führt dich durch die notwendigen Schritte, um die erforderlichen Assets zu laden und die Komponente in deine Anwendung einzubetten.

Über Web Components

Falls du mit Web Components noch nicht vertraut bist, ist der Mozilla Developer Network Artikel zu diesem Thema wahrscheinlich ein guter Ausgangspunkt. Kurz gesagt: Web Components ermöglichen es Entwicklern, HTML, JavaScript und CSS so zu kapseln, dass es vom Host-HTML-Dokument isoliert und über ein benutzerdefiniertes Script-Tag im DOM wiederverwendbar ist. Du fragst dich, was daran so toll ist? Anwendungen oder Hilfskomponenten, die ordnungsgemäß in eine Web Component verpackt sind, können einfach und deklarativ in deinem HTML eingebettet werden, wobei die Web Component sich selbst instanziiert. Die Implementierungsdetails deiner Host-Anwendung spielen keine Rolle – wichtig ist nur, dass alle für Web Components erforderlichen Funktionen von deinen Ziel-Browsern unterstützt werden. Dies macht Web Components Framework-unabhängig. Wenn ein Entwickler eine Komponente mit Angular oder Vue erstellt, kannst du diese Komponente ohne speziellen Aufwand in deiner React-Anwendung verwenden. Dies wird besonders interessant im Kontext von Micro Frontends, wo Teile der Benutzeroberfläche von separaten Teams und sogar mit unterschiedlichen Bibliotheken oder Frameworks entwickelt werden können, am Ende aber alle Teile nahtlos zu einer Oberfläche zusammengefügt werden.

Überzeugen Sie sich selbst

Die bisher beschriebenen Konzepte werden in einer Beispielanwendung hier demonstriert. Die Anwendung lädt und bettet drei Web Components mit demselben Inhalt ein, die jedoch mit Vue, React und purem JavaScript implementiert wurden.

Definition von Web Components

Um eine Web Component zu erstellen, sind nur wenige Schritte erforderlich:

  • Erstellen eines benutzerdefinierten HTML-Elements.
  • Für isolierte Stile verwenden Sie das Shadow DOM – die treibende Kraft hinter der Kapselung von Web Components.
  • Registrieren des zuvor erstellten Elements in der Registry für benutzerdefinierte Elemente des Browsers, wodurch es über ein benutzerdefiniertes HTML-Tag verfügbar wird.

Ein benutzerdefiniertes Element zu erstellen ist so einfach wie eine Klasse zu erstellen, die von einem bereits existierenden HTML-Element erbt.

In der Klasse definieren wir zwei Eigenschaften:

  • connectedCallback – Diese Funktion wird aufgerufen, wenn das benutzerdefinierte Element, das wir hier erstellen, in das DOM eingefügt wird. Wir nutzen diese Funktion, um den eigentlichen Inhalt des Elements zu instanziieren, in diesem Fall "Hallo, Welt!".
  • disconnectedCallback – Diese Funktion wird aufgerufen, wenn das benutzerdefinierte Element aus dem DOM entfernt wird. Wir nutzen diese Funktion, um notwendige Aufräumarbeiten durchzuführen, da wir sonst möglicherweise Memory-Leaks verursachen könnten.

Schauen wir uns eine Implementierung an, die uns viel mehr Möglichkeiten bietet:

Lass uns aufschlüsseln, was hier passiert. Die Funktion nimmt ein Objekt mit drei Eigenschaften – mount, unmount und styles – und gibt eine Klasse zurück, die von HTMLElement des Browsers erbt.

In der connectedCallback-Funktion verwenden wir this.attachShadow({ mode: "open" });, um einen Shadow-DOM-Baum an das Element anzuhängen und so isolierte Stile für dieses Element zu ermöglichen. Dieser Schritt ist wichtig, wenn du keine widersprüchlichen Stilregeln zwischen deinem benutzerdefinierten Element und dem Host-Dokument haben möchtest. Alternativ könntest du auch Stile mit Namespaces schreiben, die nur im benutzerdefinierten Element angewendet werden. Die Verwendung des Shadow DOM macht dein Element überall einsetzbar, ohne dass du dir Gedanken über widersprüchliche oder überschreibende CSS-Regeln machen musst.

Dann erstellen wir ein Template, das die Stile innerhalb eines Style-Tags und einen DOM-Knoten enthält, der unser Einhängepunkt für den Inhalt ist, den dieses Element rendern wird. Schließlich wird das Template geklont und an den shadowRoot-Knoten des Elements angehängt, und die mount-Funktion wird mit unserem Einhängepunkt-Element aufgerufen.

Der disconnectedCallback ist viel einfacher, da wir nur die übergebene unmount-Funktion aufrufen, was bedeutet, dass jede Aufräumlogik anderswo definiert werden muss.

Durch die Akzeptanz von mount- und unmount-Funktionen ist die obige Implementierung völlig Framework-unabhängig und kann verwendet werden, um benutzerdefinierte Elemente zu erstellen, die beliebigen Inhalt rendern. Das ist alles, was unsere vorherige Beispielanwendung brauchte, um Web Components zu erstellen, die React-, Vue- und reinen JavaScript-Code rendern.

Um eine Web Component zu erstellen, die eine React-Anwendung rendert, können wir die oben definierte Funktion wie folgt verwenden:

Wir rufen makeCustomElement auf und definieren die Funktionen mount und unmount, um die React-Anwendung zu rendern und aufzuräumen. Da wir Webpack als Bundler verwenden, laden wir unsere Stile mit Webpacks raw-loader in einen String und übergeben diesen ebenfalls an unsere Funktion. Anschließend registrieren wir das Element bei der Registry für benutzerdefinierte Elemente mit:

Dadurch wird das Element in unserem HTML über das benutzerdefinierte composer-react-Tag verwendbar. Wir haben auch Beispiele für Vue und pures JavaScript erstellt.

Laden von Web Components

Du könntest natürlich Web Components direkt in deinem Anwendungscode erstellen und sofort verwenden, aber normalerweise würdest du Web Components entweder statisch oder dynamisch über Script-Tags einbinden. Gehen wir näher auf den Fall des dynamischen Ladens von Web Components zur Laufzeit der Anwendung ein. Dieser Ansatz kann in Fällen interessant sein, in denen du die Komponenten lazy laden möchtest oder erst zur Laufzeit weißt, welche Komponenten geladen werden müssen. Stell dir beispielsweise eine Dashboard-Anwendung vor, bei der jedes Widget von separaten Teams entwickelt wird. Jedes Widget ist als Web Component definiert und kann sogar auf einem anderen Server gehostet werden. Solange die Web Component ordnungsgemäß definiert ist und du den Speicherort der JavaScript-Datei kennst, die zum Ausführen der Komponente notwendig ist, bist du startklar!

Die folgende Logik zum dynamischen Laden von Skripten kann auch auf das Laden von Stylesheets angewendet werden. Sie finden die Implementierung für Skripte und Stylesheets hier, falls Sie sie benötigen.

Hier gibt es einiges zu entschlüsseln:

Da es nicht selbstverständlich ist, dass wir eine Schnittstelle nur einmal in unserer Kompositionsschicht einbetten, führen wir vor dem Versuch, ein Remote-Skript zu laden, einige Prüfungen durch. Diese Prüfungen sind notwendig, um sicherzustellen, dass wir eine Ressource nur einmal laden:

  • Wurde das Skript bereits geladen oder wird es gerade geladen? Dies geschieht, indem geprüft wird, ob ein Script-Tag mit derselben Quelle wie das Skript, das wir laden möchten, bereits im DOM existiert. Wenn ja, bedeutet das, dass das Skript entweder geladen wird oder bereits geladen und ausgeführt wurde.
  • Um zu wissen, ob das Skript – falls im DOM vorhanden – das Laden abgeschlossen hat, setzen wir ein benutzerdefiniertes Datenattribut data-loaded auf das Script-Element, wenn das Skript geladen wird. Jedes Mal, wenn wir versuchen, ein Skript zu laden, fügen wir dem load-Ereignis des Script-Tags einen neuen Event-Listener hinzu, um zu wissen, wann es fertig geladen ist. Auf diese Weise werden, wenn wir versuchen, dasselbe Skript zweimal zu laden, die durch den Aufruf von loadAssets erstellten Promises gleichzeitig aufgelöst.

In unserer Beispielanwendung verwenden wir die Funktion loadAssets über einen React-Hook, um Informationen über Lade- oder Fehlerzustände dort verfügbar zu machen, wo wir die Web Component einbetten.

Der Hook bietet nicht nur Ladefunktionalität, sondern ermöglicht auch den Versuch, die Assets bei aufgetretenen Fehlern neu zu laden.

Verwendung von Web Components

Kommen wir nun zur tatsächlichen Verwendung unserer dynamisch geladenen Web Components. Damit dies funktioniert, gehen wir davon aus, dass wir für jede Web Component, die wir laden oder verwenden, das HTML-Tag und die Speicherorte der erforderlichen Assets kennen. Diese Informationen könnten von dem Server bereitgestellt werden, der die statischen Assets für die benutzerdefinierten Elemente ausliefert. Es ähnelt einem Service-Discovery-Server für Backend-Dienste.

Die folgende Implementierung der Beispielanwendung verwendet React, kann aber mit Vue oder purem JavaScript umgeschrieben werden:

Hier sind vier Dinge zu beachten:

  • Die Komponente erhält das HTML-Tag, die Asset-Pfade und zusätzliche Informationen als Prop.
  • Sie übergibt die Asset-Pfade an unseren zuvor definierten Hook useStaticAssets.
  • Abhängig von einem loading- und error-Zustand rendert sie unterschiedliche Inhalte.
  • Sie verwendet das benutzerdefinierte HTML-Tag, um die Web Component in das DOM zu rendern.

Wie in https://micro-frontends.org beschrieben, könntest du Skeleton-UIs für den Ladezustand der Web Components oder einen Spinner wie in der Beispielanwendung verwenden. Der Browser zwischenspeichert die Assets der Web Components, sodass Ladezeiten nur beim ersten Laden oder nach Invalidierung des Cache ein Problem darstellen.

Zusätzliche Überlegungen

Der in diesem Blogbeitrag beschriebene Ansatz hat sich in Szenarien bewährt, in denen die Anforderungen dynamisch ladbare Schnittstellen umfassten, die auf verschiedenen Maschinen gehostet werden, sowie für Projekte, die von mehreren Teams entwickelt wurden, die möglicherweise unterschiedliche Programmiersprachen bevorzugen. Jede Schnittstellenimplementierung muss nur einem bestimmten Protokoll für die Einbettung folgen: Erstellen des Elements mit makeCustomElement und Registrieren über ein eindeutiges Tag.

Ich hoffe, du hast aus unseren Erfahrungen mit Web Components etwas gelernt. Die skizzierte Implementierung ist natürlich nicht der einzige Weg, dieses Problem anzugehen, aber sie ist ein guter Baustein, um dein Projekt zum Laufen zu bringen.

Links

web components

custom elements

shadow dom

micro frontends

react

vue

Weitere Themen

Lea, 04.04.2025

Vue clever nutzen – Wiederverwendbarkeit für Einsteiger:innen

Vue

JavaScript

Reusability

DRY Principle

Components

Composables

Zum Blogartikel
Header-top-ten-mistakes-to-avoid.png

Francesca, Ricarda, 02.04.2025

Die 10 häufigsten Fehler bei der Entwicklung digitaler Produkte – und wie du sie vermeidest

MVP development

UX/UI design

product vision

agile process

user engagement

product development

Zum Blogartikel

Judith, 31.03.2025

Von der Hypothese zur echten Erkenntnis: MVPs richtig einsetzen

Zum Blogartikel