Nachdem es im ersten Teil um einen Überblick zur Web Component-Technologie ging, werden wir in diesem Teil der Serie unsere erste eigene Web Component mit LitElement bauen.
Dieser Beitrag ist Teil einer Serie:
- TEIL 1: Die Webstandards
- TEIL 2: Eine eigene Komponente mit LitElement
- TEIL 3: Attributes & Properties
LitElement und lit-html
LitElement ist eine Basisklasse für die Erstellung von Web Components. LitElement selbst nutzt lit-html, eine Bibliothek, die JavaScript-Template-Strings in HTML-Markup umwandelt. Beide Projekte werden federführend vom Polymer-Team entwickelt. Das Polymer-Projekt hat eine lange Geschichte in der Entwicklung der Web-Component-Technologie. Dies liegt insbesondere darin begründet, dass das Polymer-Team Teil des Google Chrome-Teams ist. Die Projekte verfolgen das Ziel, leichtgewichtig, schnell und effizient zu sein. Sie sind als Open Source-Projekte unter BSD 3 auf Github verfügbar.
Template-Strings
Lit-html nutzt zur Beschreibung von HTML-Templates sog. Template-Strings, die erst mit EcmaScript 2015 als nativem Sprachfeature eingeführt wurden. Es handelt sich um Strings, die mit Backticks begonnen und geschlossen werden. Sie bieten zwei wesentliche Vorteile gegenüber normalen Strings, die mit Anführungszeichen definiert werden:
- Sie können sich über mehrere Zeilen erstrecken.
- In ihnen kann Code evaluiert werden.
Dazu ein einfaches illustrierendes Beispiel:
Auch wenn es sich auf den ersten Blick nur um einen Weg aus der "Konkatenierungshölle" handelt, so sind damit auch deutlich komplexere Abläufe möglich, wie folgendes Beispiel zeigt:
Template-Strings sind Bestandteil von JavaScript seit ES6. Das heißt, alle Browser, die neuer sind als der Internet Explorer 11, unterstützen diese. Dabei handelt es sich um Firefox ab Version 34, Chrome ab Version 41, Safari ab Version 9.1, Microsoft Edge ab Version 13, iOS ab Version 9. Detaillierte Informationen kann man auf caniuse.com finden.
Wieso sind Template-Strings für unsere Web-Components so interessant? Lit-html verwendet sie als Beschreibungssprache für das HTML-Markup. Das heißt, wir können HTML-Templates bauen, die Variablen, Bedingungen, Schleifen und vieles mehr beinhalten. Wir können in unseren Template-Strings alles tun, was uns JavaScript bietet.
HTML-Template mit lit-html rendern
Wenn wir jetzt mit lit-html unseren Template-String rendern wollen, sollten wir ihn erst einmal um ein wenig HTML-Markup anreichern. Ich habe zum Beispiel aus der ersten Zeile eine Überschrift in h1 gemacht. Wir benötigen nun zwei Funktionen aus dem lit-html Package: html und render.
Die Funktion html nimmt einen Template-String entgegen und gibt ein TemplateResult zurück. TemplateResult ist die Datenstruktur, in der das Ergebnis des Template-Strings (also unser HTML-Markup) für das Rendering zwischengespeichert wird.
Um unser TemplateResult zu rendern, brauchen wir die Funktion render. Sie nimmt als Argumente ein TemplateResult und eine HTML-Node, in der wir das Template rendern wollen.
Im Folgenden habe ich das obige Beispiel adaptiert, um es in den Body eines HTML-Dokuments zu rendern.
Als Besonderheit fällt auf, dass die map-Funktion ein TemplateResult zurückgibt. Wir können also Templates verschachteln.
Unsere erste Web Component mit LitElement
Um LitElement nutzen zu können, müssen wir es zuerst per npm oder yarn installieren:
npm install lit-element
Da LitElement auf lit-html aufbaut, wird es direkt als Abhängigkeit mitinstalliert.
Für unsere erste Komponente brauchen wir zwei Dinge aus dem lit-element Paket: html und LitElement. Die Funktion html kennen wir bereits aus lit-html.
Wie im ersten Teil ausgeführt, handelt es sich bei einer Web Component um eine Klasse, die von HTMLElement erbt. Dies übernimmt die Klasse LitElement für uns. Die Klasse macht aber noch mehr: Sie prüft, ob sich ein Template ändert, und rendert es dementsprechend neu, aber dazu später mehr.
Wir implementieren also eine Klasse, die von der Klasse LitElement erbt. Jetzt müssen wir LitElement nur noch sagen, welches Template wir rendern wollen. Dies tun wir, indem wir eine Methode render implementieren, die ein TemplateResult zurück liefert.
Eine simple Komponente sieht zum Beispiel so aus:
Das Letzte, was jetzt noch fehlt, ist, dass wir dem Browser unsere Komponente bekannt machen müssen:
window.customElements.define('meine-komponente', MeineKomponente);
Hierbei ist wichtig, dass der Tag-Name zwingend mindestens einen Bindestrich enthalten muss. Dies hat den Hintergrund, dass man so Web Components und Elemente aus dem HTML-Standard ganz einfach unterscheiden kann.
Es kann auch pro Browserfenster logischerweise nur eine Klasse pro Tag-Name geben. Ansonsten wirft der Browser einen Fehler aus - z. B.: Cannot define multiple custom elements with the same tag name. Dies kann in der Entwicklung passieren (insbesondere im hier genutzten Web Editor). In diesem Fall hilft ein einfaches Neuladen der Seite. Sollte dies nicht funktionieren, so wird in der Anwendung die Komponente wirklich zweifach definiert.
Und damit haben wir unsere erste Web Component implementiert. Das ganze Ergebnis lässt sich im folgenden Editor bestaunen:
Etwas mehr aktion bitte!
Unsere erste Komponente war einfach zu implementieren, aber genauso einfach ist auch das Ergebnis. Von der dynamischen Natur der Template-Strings haben wir nichts gesehen. Dies wollen wir nun ändern. Dafür legen wir in unserer Komponente zuallererst einen Konstruktor an:
In diesem Konstruktor ist der super() Aufruf essenziell, weil wir sonst den Konstruktor unserer Basisklasse LitElement nicht aufrufen! Weiterhin definieren wir eine Klassenvariable werBinIch.
Um diese jetzt in unserem Template zu verwenden, müssen wir in unser Template nur ein ${this.werBinIch} einfügen. Et voilà, man könnte es fast schon DataBinding nennen, aber dazu kommen wir im nächsten Teil. Um noch etwas mehr Aktion zu haben, habe ich noch ein toUpperCase hinzugefügt:
Ziemlich cool, aber unsere Komponente kann noch mehr ...
Gekapselt per Default durch Shadow DOM
LitElement verwendet per Default Shadow DOM (mehr dazu im ersten Teil). In unserem Beispiel ist folgende Zeile am Ende von meine-komponente.js:
console.log(`Anzahl der p-Tags: ${document.querySelectorAll('p').length}`);
Preisfrage: Wie viele p-Tags findet document.querySelectorAll? Natürlich nur einen. Der eine, der in der index.html definiert ist. Warum? Das p-Tag von MeineKomponente befindet sich im Shadow DOM. Dieser ist gekapselt gegen Zugriffe etwa durch document.querySelector oder Styling von außen.
Im body der index.html ist ein globales Styling für p-Tags definiert. Der p-Tag in der index.html wird auch korrekt gestyled, das p-Tag in unser Web Component jedoch nicht. Shadow DOM kapselt unsere Komponente gegen Styling von außen (in einem späteren Teil der Serie werden wir unsere Komponente von innen stylen).
Warum ist dies hilfreich? Wir wollen unsere Web Component in verschiedenen Umgebungen einsetzen. Nur weil in der Webanwendung ein Styling oder JavaScript definiert ist, wollen wir Aussehen oder Verhalten unserer Komponente nicht ändern. Im schlimmsten Fall würden Änderungen von außen sogar Aussehen oder Verhalten zerstören.
Keine Kapselung - Unsere Web Component ohne Shadow DOM
Wenn wir keine Kapselung wollen, so können wir Shadow DOM für unsere Web Component ausschalten. Dies könnte insbesondere der Fall sein, wenn man ein globales Stylesheet verwenden möchte. Dafür müssen wir in unserer Klasse nur die Methode createRenderRoot überschreiben. In dieser Methode kann entweder Shadow DOM konfiguriert oder eben abgeschaltet werden. Am Ende geben wir einfach den Kontext zurück, in dem unser Template gerendert werden soll. Wenn wir keinen Shadow DOM wollen, so ist dieser Kontext einfach unsere Komponente:
Der betreffende Codeabschnitt ist auf Stackblitz im Beispiel vorhanden, er muss nur auskommentiert werden.
Schalten wir Shadow DOM für unsere Web Component ab, so wird unser p-Tag durch document.querySelectorAll gefunden. Und auch das Styling wird auf den p-Tag angewendet.
Wir haben unsere Web Component implementiert. Ich gebe zu, sie ist sehr einfach, aber ich hoffe, man erkennt, wo die Reise hingehen kann. Apropos Reise: Im nächsten Teil geht es um Properties und Attributes.
Dieser Beitrag ist Teil einer Serie:
- TEIL 1: Die Webstandards
- TEIL 2: Eine eigene Komponente mit LitElement
- TEIL 3: Attributes & Properties
zurück zur Blogübersicht