MicroProfile & WebComponents — der neue Standard (Teil 2)

Thomas Porocnik
6 min readAug 17, 2020

--

Komponenten ohne Framework

Das ist eine Fortsetzung des Artikels MicroProfile & WebComponents. Im ersten Teil wurde ein REST-Backend für eine Todo-App mit Quarkus und dem MicroProfile erstellt. Hier geht es jetzt um einen dazu passenden SPA-Client.

Eine kurze Historie

Seit Anwendungen für das Web programmiert werden, gibt es auch in regelmäßigem Abstand neue Frameworks. Webseiten wurden früher hauptsächlich serverseitig zusammengebaut. In der Java-Welt wurden damals Frameworks verwendet wie Struts, Wicket oder Velocity. Mit dem Aufkommen von Java Server Faces (JSF) entstanden weitere Frameworks und Bibliotheken wie IceFaces, PrimeFaces und RichFaces. Letztere kamen von JBoss, die auch noch Seam im Angebot hatten. Viele dieser mit diesen Frameworks gebauten Anwendungen verrichten immer noch ihren Dienst, wenngleich sie für neue Projekte selten noch erste Wahl sind.

Ajax war dann das neue Schlagwort, wenn es darum ging, flüssig reagierende Browseranwendungen zu bauen. Dieser Typ Anwendung baut den HTML-Code größtenteils browserseitig zusammen und macht nicht für jede HTML-Änderung einen Server-Roundtrip. Solche Anwendungen nennt man auch Single-Page Applications (SPA). Um das zu unterstützen und fehlende Standards auszugleichen enstanden JavaScript-Bibliotheken, wie das allgegenwertige jQuery. Letztlich kann man damit sein Anwendung beliebig zusammenbauen und so entstand der Bedarf, das Entwickeln von SPAs strukturierter anzugehen.

JavaScript-Frameworks

Damit entstand wieder eine ganz neue Familie von Frameworks. Manchmal wurden sie von großen Firmen entwickelt, die sie dann für die Öffentlichkeit freigaben (wie Angular von Google oder React von Facebook), manche kommen aber auch von einzelnen Entwicklern.

Diese Frameworks erfüllen alle mehr oder weniger den gleichen Zweck, sind aber zueinander nicht austauschbar, sodass ein Wechsel im Grunde einem Neuschreiben der Anwendung gleichkommt. Gut, wird manch einer fragen, warum soll ich das Framework wechseln wollen?

Manchmal reduzieren Firmen, die ein Framework unterstützen, ihren Einsatz (siehe GWT), führen Breaking-Changes ein (Angular) oder stellen es komplett ein. Wenn ein Framework von hauptsächlich einem einzelnen Commiter abhängt, ist die Gefahr vielleicht sogar noch größer.

Zusätzlich hat jedes Framework natürlich seine Lernkurve, sodass neue Entwickler, oder solche die das Projekt wechseln, wieder von vorne anfangen.

WebComponents

Proprietäre JavaScript-Frameworks entstanden aus dem Bedarf heraus und leisten auch gute Dienste. Seit ECMAScript 2015 sind nun viele Funktionen, für die man früher extra Bibliotheken benötigt hat, direkt im Sprachumfang integriert. Zusätzlich hat das World Wide Web Consortium (W3C) seit ein paar Jahren unter dem Überbegriff WebComponents viele weitere Dinge standardisiert, die man braucht, um mit JavaScript SPAs zu bauen. Und weil es ein W3C-Standard ist, wird er nativ von allen modernen Browsern ohne zusätzliche Bibliotheken unterstützt. (Für historische Browser gibt es auch passende Polyfills.)

Das heißt nicht, dass es grundsätzlich verkehrt wäre, Bibliotheken zu nutzen. Warum sollte man auch das Rad immer zu neu erfinden. Bibliotheken unterstützen die Anwendungen, aber immer nur zu einem bestimmten Grad und sind kein komplettes Framework, mit dem man auf Gedeih oder Verderben leben muss.

Die Beispiel-App

Schauen wir uns daher mal an, wie man auf Basis von WebComponents einen Komponenten-basierte SPA-Client für unser Todo-App bauen kann.

Dazu brauchen wir vorab im Grunde nichts zu installieren! Um die SPA laufen lassen zu können, muss allerdings ein lokaler Http-Server vorhanden sein. Dazu kann der von Python genutzt werden oder was zum Entwickeln sehr praktisch ist: Browsersync. Das Tool startet gleichzeitig einen Browser und aktualisert die Seite, sobald der Sourcecode geändert ist.

Wir starten mit der einfachen index.html als Ausgangspunkt:

Hier passiert noch nicht viel. Ein Stylesheet wird eingebunden (für das Beispiel wird das minimalistische Wing CSS genutzt) und die Viewport-Definition ist für mobile Browser. Der interessante Teil steckt im Body. Das Tag <todo-app> ist kein normales HTML-Element und würde vom Browser normalerweise ignoriert werden. Das Todo-App-Element ist aber das von uns definierte Element und wird in dem angeben JavaScript Modul beschrieben. Benutzerdefinierte Tags müssen immer einen Bindestrich in der Bezeichnung führen, damit sie von Standard-Tags unterschieden werden können.

Wie mit den üblichen Komponenten-Frameworks auch, macht man sich am besten im Vorfeld Gedanken darüber, wie die Gesamtstruktur aussehen soll. In diesem Fall beinhaltet die Haupt-Komponente zwei Teilkomponenten. Die eine listet die vorhandenen Tasks auf, die andere ermöglicht die Eingabe von neuen.

Komponentenstruktur

Ein WebComponent wird von der Basisklasse HTMLElement abgeleitet und muss für eine geeignete HTML-Darstellung sorgen. Da die TodoApp-Komponente dabei nichts Anspruchsvolles zu tun hat, erfolgt dies direkt über einen Template-String und die Zuweisung an die Eigenschaft innerHtml des Elements. Die Methode connectedCallback ist eine Lifecycle-Methode und wird aufgerufen, wenn das Element in das Dokument eingefügt wird.

Übrigens kann man die gesamte API-Doku zu WebComponents oder JavaScript (ES) direkt auf der Seite des Mozilla Developer Networks finden.

Mit dem Befehl customElements.define(“todo-app”, TodoApp) wird das Tag mit der Klasse verbunden. Die beiden Unterkomponenten müssen natürlich erst importiert werden, damit sie lokal bekannt sind.

Man kommt mit selbstdefinierten Templates, die übrigens auch dynamische Werte (über die Notation ${…}) beinhalten können, schon recht weit. Es muss allerdings berücksichtigt werden, dass man sich dann auch selbst um das Verhindern von böswilligen Injections kümmern muss. Außerdem ist das Binden von EventHandlern dabei etwas trickreich.

Lit-html

Eine sinnvolle Erleichterung ist es, hierfür externe Bibliotheken zu nutzen. In den beiden anderen Komponenten verwende ich daher lit-html für die Templates. Dieser Code wurde zusammen mit Polymer (eine von Google entwickelte WebComponents-Bibliothek) entwickelt und ist eine sehr effiziente, Template-Strings nutzende, Template Engine.

Der Einfachheit halber wird hier die CDN-Version genutzt, so dass keine Installation über z.B. npm erforderlich ist.

TaskList

Sehen wir uns daher die Komponente TaskList näher an. Die Klasse wird mit dem Schlüsselwort export definiert, damit ist sie in der TodoApp-Komponente importierbar. Im Konstruktor wird der Shadow DOM initiiert - dazu kommen wir aber später. In connectedCallback wird das Laden der Tasks vom Server gestartet und auch ein EventListener registriert, der auf das Hinzufügen von Tasks reagiert.

Zum Laden der Tasks wird ein sehr mächtiges Werkzeug von ES genutzt, nämlich die fetch-API. Damit kann man völlig ohne zusätzliche Bibliotheken wie jQuery nicht nur Daten vom Server holen, sondern auch die anderen HTTP-Methoden wie POST, DELETE etc. nutzen.

Sobald die Daten geladen sind, wird die Darstellung aufgerufen. In der Methode template wird das HTML-Gerüst dynamisch aufgebaut. Der Aufruf der aus lit-html importierten Funktion html bereitet das Template vor. Dieser Aufruf kann auch verschachtelt erfolgen. Der Ausdruck “@click” dient dazu, einen EventListener anzuhängen. In unserem Fall bereiten wir damit das Entfernen eines Task durch das Anklicken der dazugehörigen Checkbox vor.

Mit dem Aufruf von der Funktion render, die auch aus lit-html stammt, wird dann die Komponente mit den aktuellen Daten dargestellt. Dabei ist render sehr performant implementiert, da hierbei nur das ersetzt wird, was sich wirklich ändert.

NewTask

Die Komponente NewTask ist ähnlich aufgebaut.

Hier wird der EventHandler addTask am Button registriert, der per fetch ein POST an die REST-Api schickt. Die Description wird dabei als Json-Wert serialisiert.

Wie erreichen wir aber, dass danach die Liste der Tasks aktualisert wird?

Wenn das Posten zum Server erfolgreich war, wird ein Event erzeugt. Die Eigenschaft bubbles bestimmt dabei, ob das Event auch in Parent-Elementen sichtbar wird, also nach oben „blubbert“. Wie schon oben erwähnt, lauscht die TaskList-Komponente auf dieses Event und stellt dann die Liste neu dar.

Shadow DOM

Kommen wir nochmals zum Aspekt des Shadow DOMs. In beiden Komponenten wurde im Konstruktor mit attachShadow ein Shadow DOM erzeugt. Das wäre für unsere Zwecke nicht grundsätzlich nötig gewesen, sondern dient nur der Veranschaulichung.

Shaodow Root

Shadow DOM ist ein Konzept aus dem Standard, das ermöglicht, Elemente völlig isoliert und gekapselt vom restlichen DOM einzubringen (siehe auch den Screenshot des Edge Element-Inspektors). Das ist besonders hilfreich beim Verwenden von fremden Komponenten.

Es gibt bereits eine große Anzahl frei verfügbarer, sehr funktionaler, auf dem WebComponents-Standard beruhender 3rd-Party-Komponenten um z.B. Tabellen, DatePicker und andere Widgets zu realisieren (siehe UI5 Web Components von SAP oder Vaadin Components).

Aufgrund der Verwendung von Shadow DOM, mussten wir in der Funktion render das shadowRoot mit angeben. Die Verwendung von Shadow DOM machte es auch nötig, jeder Komponente das zu verwendende CSS explizit mitzuteilen. Das ist ja gerade Teil der Idee, dass der Shadow DOM diesbezüglich unabhängig ist.

Was bleibt?

Damit haben wir eine einfache, komponentenbasierte UI ohne Framework und unnötigen Abhängigkeiten implementiert.

Themen, die jetzt noch interessant wären, sind u.a.:

  • Wie kann man mit Bordmitteln vernünftige (flexible) Layouts bauen?
  • Was mache ich, wenn ich Routing brauche?
  • Wie teste ich das Ganze?

Das wären dann aber Themen für ein anderes Mal …

Links

--

--

Thomas Porocnik
Thomas Porocnik

Written by Thomas Porocnik

Software Architect. Trying to find out, why software is hard.