Kolumne

Enterprise Tales: Ein Blick auf Web Components

Sven Kölpin
Funktionale Programmierung

© S&S_Media

Im Web haben sich komponentenbasierte UI-Frameworks durchgesetzt. Alle heute relevanten Single Page Application Frameworks setzen vollständig oder mindestens zu großen Teilen auf dieses Paradigma. Mit Web Components existiert ein vielversprechender Standard, der die Erstellung Framework-unabhängiger Komponenten ermöglicht und so bereits heute für mehr Wiederverwendbarkeit im Web sorgen kann.

Mithilfe von Komponenten lassen sich Benutzungsoberflächen aus konfigurierbaren, wiederverwendbaren Bausteinen (auch Widgets) zusammensetzen. Den Grundstein für die Erstellung eigener Komponenten im Web legten einst Bibliotheken wie jQuery/jQuery UI. Diese ermöglichen es, herkömmliche HTML-Elemente in Bedienelemente mit einem bestimmten Aussehen, Verhalten und Zustand zu verwandeln. Heute ist die Entwicklung von Webapplikationen ohne den Einsatz komponentenbasierter Frameworks wie Angular oder React schon gar nicht mehr denkbar.

Komponenten sind im Web traditionell Framework-spezifisch. Die APIs und sogar die Definition davon, was eine Komponente eigentlich im Konkreten ausmacht, unterscheiden sich zwischen den Frameworks. Deshalb ist es zum Beispiel nicht möglich, ein in Angular geschriebenes Bedienelement direkt in React zu verwenden (oder eben andersherum). Selbst grundlegende Widgets (z. B. Dialoge) müssen somit oftmals für verschiedene Webanwendungen neu entwickelt werden. Diese Herausforderung gewinnt im Zeitalter von Microservices und Microfrontends, in dem es durchaus vorkommen kann, dass verschiedene Frameworks parallel eingesetzt werden, zunehmend an Bedeutung. Genau hier können Web Components Abhilfe schaffen.

Der Web-Components-Standard

Streng genommen gibt es den einen Web-Components-Standard gar nicht. Vielmehr verbergen sich hinter Web Components eine Menge APIs, deren Kombination die Erstellung von UI-Komponenten für die Webplattform ermöglicht. Die Spezifikation schafft eine einheitliche und somit Framework-unabhängige Definition von webbasierten Komponenten und legt den Grundstein für wiederverwendbare UI-Widgets im Web.

W-JAX 2019 Java-Dossier für Software-Architekten

Kostenlos: Java-Dossier für Software-Architekten 2019

Auf über 30 Seiten vermitteln Experten praktisches Know-how zu den neuen Valuetypes in Java 12, dem Einsatz von Service Meshes in Microservices-Projekten, der erfolgreichen Einführung von DevOps-Praktiken im Unternehmen und der nachhaltigen JavaScript-Entwicklung mit Angular und dem WebComponents-Standard.

 

Web Components sind keineswegs eine neue Erfindung. Zum Beispiel gibt es bereits seit 2014 eine Implementierung im Chrome-Browser. Andere Browserhersteller haben frühe Versionen der Spezifikation aber größtenteils vernachlässigt, sodass eine plattformübergreifende Verwendung von Web Components lange ausschließlich mithilfe von Polyfills und Frameworks wie Polymer möglich war. Mittlerweile wurde der Standard, der jetzt den Versionsnamen V1 trägt, modernisiert und genießt eine breite Browserunterstützung (Abb. 1). Web Components können also heute in fast allen Browsern nativ verwendet werden. Aber auch Webbrowser ohne vollständige Unterstützung sind durch die bereits erwähnten Polyfills nicht ausgeschlossen. Allerdings leidet hier selbstverständlich die Performanz und Netzwerklast durch den zusätzlich benötigten JavaScript-Code.

Abbildung 1: Browser Support Web Components [4]

Abbildung 1: Browser Support Web Components

Die Bestandteile von Web Components

Die Web-Component-Spezifikation basiert auf insgesamt vier Standards: Custom Elements, HTML Templates, Shadow DOM und ES Modules. Jedes dieser einzelnen APIs wird, wie für die Webplattform typisch, über JavaScript angesprochen. Zudem sind die Spezifikationen alleinstehend, lassen sich also theoretisch auch unabhängig voneinander verwenden.

Web Components können mithilfe aller oder aus der Kombination von bestimmten APIs erstellt werden. Es ist zum Beispiel nicht zwingend notwendig, dass eine Komponente die Shadow DOM– oder HTML Template-Spezifikation verwendet. Um das volle Potential auszuschöpfen, ist aber in den meisten Fällen der Einsatz aller APIs zu empfehlen.

Im Folgenden werden die einzelnen Bestandteile von Web Components genauer beleuchtet.

Custom Elements

Der HTML-Standard liefert seit jeher Komponenten für die Erstellung von Benutzungsoberflächen. Diese häufig eher primitiven Elemente (z. B. Buttons, Eingabefelder oder Datumsfelder) reichen aber in den meisten Anwendungen nicht aus. Bislang musste man deshalb für aufwendige Bedienelemente auf nicht standardisierte Bibliotheken und Frameworks zurückgreifen. Das Custom Elements API hilft hier weiter. Mit diesem Standard lassen sich eigene HTML-Elemente (also Tags) definieren und mit Verhalten versehen. Die Custom Elements Spezifikation bildet die Grundlage für die Erstellung von Web Components.

In der Regel erben Custom Elements vom HTMLElement, das die Basis für alle im HTML-Standard definierten Komponenten ist. So wird unter anderem der Zugriff auf grundlegende Eigenschaften und Methoden (z. B. addEventListener, style, …) eines HTML-Elements ermöglicht. Aktuell kann nicht von konkreten nativen Komponenten abgeleitet werden (z. B. extends HTMLButtonElement). Hierzu gibt es einen gesonderten Spezifikationsvorschlag mit dem Namen customized built-in elements, der allerdings bislang nur in Chrome implementiert ist und von Safari abgelehnt wurde.

Das Registrieren von eigenen Tags erfolgt über das customElements.define-Interface. Der Name einer jeden erstellten Komponente muss mindestens einen Bindestrich beinhalten. Dadurch können Konflikte mit nativen HTML-Tags vermieden werden. Zudem lassen sich Web Components so stets am Namen des Tags erkennen. Das doppelte Registrieren eines Element-Namens innerhalb der gleichen Seite ist nicht möglich und führt zu einem Fehler. Listing 1 zeigt die Definition einer einfachen Komponente.

<body>
<!-- usage of the web component -->
<hello-component name="r10"></hello-component>

<script>
  class HelloComponent extends HTMLElement {
    static get observedAttributes() {
      //observe the name attribute
      return ['name'];
    }

    constructor() {
      //called for every hello-component tag
      super();
    }

    connectedCallback() {
      //called when the component is attached to the dom
      this.render();
    }

    disconnectedCallback() {
      //called when component gets removed from the dom
    }

    attributeChangedCallback(name, oldVal, newVal) {
      //called initially and whenever the name attribute changes
      if (name === 'name' && oldVal && oldVal !== newVal) {
        this.render();
      }
    }

    render() {
      const name = this.getAttribute('name');
      this.innerHTML = `
        <h1>Hello ${name}.</h1>
      `;
    }
  }

  //register the hello-component
  customElements.define('hello-component', HelloComponent);

  //simulate attribute change after 1 second
  setTimeout(() => {
    document
      .querySelector('hello-component')
      .setAttribute('name', 'r20');
  }, 1000);
</script>
</body>

Ein Custom Element definiert verschiedene Lebenszyklusmethoden. Einzige Ausnahme ist hier die Definition von Event Listeners wie onclick, onblur etc. Neben dem constructor, der für jede Instanz eines Custom Elements (für jedes Tag) aufgerufen wird, gibt es noch die Methoden connectedCallback, disconnectedCallback und attributeChangedCallback.

Die connected– und disconnected-Methoden werden aufgerufen, wenn die jeweilige Komponente in den DOM-Baum eingefügt beziehugsweise wieder gelöscht wird. Sie können beispielsweise dazu verwendet werden, das Rendering zu initiieren, Daten vom Server abzufragen oder Event Listener zu registrieren oder freizugeben (z. B. Websocket-Verbindungen).

Das attributeChangedCallback wird aufgerufen, wenn sich Attribute verändern, die vorher explizit als beobachtbar gekennzeichnet wurden. Dieses callback reagiert also auf Statusänderungen oder Änderungen der Konfiguration einer Komponente. Wie für HTML-Elemente üblich, können Attribute nur als String-basierte Werte übermittelt werden. Ein Transfer von komplexen JavaScript-Objekten oder Funktionen direkt aus dem HTML heraus ist also nicht möglich (der „adoptedCallback“ wird im Rahmen dieses Artikels nicht betrachtet).

Um solche Datenstrukturen trotzdem an Custom Elements zu übergeben, müssen (wie bei allen HTML-Elementen) Properties oder Methoden verwendet werden.

Die Custom-Element-Spezifikation ist bereits sehr mächtig und liefert noch viele weitere interessante APIs. Für eine tiefgehende Einführung eignet sich der dazugehörige Google-Developer-Beitrag.

HTML Templates

Das HTML Template API erlaubt die Erstellung von wiederverwendbaren HTML-Fragmenten, die unter anderem zum Rendern von Custom Elements nutzbar sind. Anders als der Name es vermuten lässt, verbirgt sich hinter der Spezifikation leider keine browsereigene Templating Engine mit Konstrukten wie if-Bedingungen oder Schleifen – zu diesem Thema wurde aber bereits ein API-Vorschlag eingereicht. Zum jetzigen Zeitpunkt sind HTML Templates vor allem dabei hilfreich, ein effizientes Rendering von Custom Elements zu erreichen. Browser-Engines können die Template-Fragmente nämlich sehr effizient verarbeiten, sodass deren Verwendung eine performante Alternative zum String-basierten Rendering bietet, wie es noch in Listing 1 dargestellt ist. Ein Template kann alles beinhalten, was über die innerHTML-Eigenschaft gesetzt werden kann, dazu zählen zum Beispiel auch <style/>-Tags oder <link/>-Elemente. Listing 2 zeigt ein Beispiel für die Verwendung von HTML Templates in Custom Elements.

<body>
<hello-component name="r10"></hello-component>

<script>
  //definition of a reusable template
  const template = document.createElement('template');
  template.innerHTML = `
    <style>
      p {
        background: rebeccapurple;
        color: white;
      }
    </style>
    <p>Hello to <span></span></p>
  `;

  class HelloComponent extends HTMLElement {
    connectedCallback() {
      //clone the template’s content
      this.appendChild(template.content.cloneNode(true));
      //set the name
      this.querySelector("p span").innerHTML = this.getAttribute('name');
    }
  }

  //register the hello-component
  customElements.define('hello-component', HelloComponent);
</script>
</body>

Shadow DOM

Die Shadow-DOM-(SD-)Spezifikation liefert, neben den Custom Elements, das wohl wichtigste API für die Erstellung wiederverwendbarer Komponenten. Mithilfe dieses Standards können gesonderte DOM-Strukturen erstellt werden, die isoliert von der Außenwelt sind. Damit lässt sich zum Beispiel der externe Zugriff auf den Inhalt und das Aussehen einer Web Component unterbinden.

Vor allem beim Styling von Komponenten bietet das Shadow DOM viele Vorteile. Alle innerhalb des SD definierten CSS-Regeln gelten nämlich nur für die Komponente. Regeln von außen werden zudem nur dann angewendet, wenn sie nicht explizit im Shadow DOM überschrieben sind. Auch alle im Shadow Tree vergebenen IDs und CSS-Klassennamen haben nur dort ihre Gültigkeit. Das macht die Definition von CSS-Regeln und das Selektieren von DOM-Elementen sehr einfach und zudem effizient. Mithilfe von CSS-Properties ist es zusätzlich möglich, das Aussehen bestimmter Bereiche des SD konfigurierbar zu machen und so isolierte, aber trotzdem wiederverwendbare Web Components zu erstellen. Listing 3 zeigt ein Beispiel für die Benutzung des Shadow DOM API.

<body>
<hello-component name="r10"></hello-component>

<script>
  const template = document.createElement('template');
  template.innerHTML = `
    <style>
      /* the style will only be used within the shadow dom */
      p {
        background: rebeccapurple;
        color: white;
      }
    </style>
  <p>Hello to <span id="content"></span></p>
  `;

  class HelloComponent extends HTMLElement {
    constructor() {
      super();
      //create shadow dom (open mode)
      this.attachShadow({mode: 'open'})
    }

    connectedCallback() {
      //clone template into shadow dom
      this.shadowRoot.appendChild(template.content.cloneNode(true));
      //id's can be used without causing conflicts
      this.shadowRoot
          .querySelector('#content')
          .innerText = this.getAttribute('name');
    }
  }

  //register the hello-component
  customElements.define('hello-component', HelloComponent);
</script>
</body>

Ein weiterer wichtiger Bestandteil des Shadow DOM ist das <slot/>-API, mit dem ganze DOM-Strukturen oder Web Components von außen in das SD eingefügt werden können. Jeder <slot/> kann dabei mit einer bestimmten Rolle versehen werden, was die Komposition verschiedener Web Components und damit eine sehr hohe Wiederverwendbarkeit ermöglicht (Listing 4).

Es ist wichtig darauf hinzuweisen, dass die Shadow DOM Spezifikation keineswegs ein Sicherheitsfeature ist. Beispielsweise lässt sich mit Hilfe der browsereigenen Entwicklungswerkzeuge weiterhin der Inhalt des versteckten DOM untersuchen. Auch der programmatische Zugriff auf den Shadow DOM einer Komponente ist möglich, zumindest im mode: open.

<body>
<hello-component>
  <!-- add child element as slot -->
  <h1 slot="title">R10</h1>
</hello-component>

<script>
  const template = document.createElement('template');
  template.innerHTML = `
    <p>Hello to <slot name="title"></slot></p>
  `;

  class HelloComponent extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({mode: 'open'})
    }

    connectedCallback() {
      //clone template into shadow dom. Slot will be rendered automatically
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
  }

  //register the hello-component
  customElements.define('hello-component', HelloComponent);
</script>
</body>

ES Modules

Der ES-(ECMAScript-)Modules-Standard ist, im Gegensatz zu den anderen APIs, nicht im Rahmen der Web-Component-Spezifikation entstanden. Das API ist Teil von ECMAScript 2015 und bietet unter anderem die Möglichkeit, JavaScript-Module zu definieren, die das (dynamische) Importieren und Exportieren von Funktionen und Datenstrukturen aus JavaScript-Dateien heraus erlauben. ES Modules werden mittlerweile von allen gängigen Browsern unterstützt.

Für den Web-Component-Standard legen ECMAScript Modules die Grundlage für das Zusammensetzen von Benutzungsoberflächen aus unterschiedlichen Komponenten. Mithilfe der Spezifikation können Bedienelemente in verschiedenen JavaScript-Dateien definiert und Abhängigkeiten formuliert werden.

In der veralteten V0-Version der Web-Component-Spezifikation sollten Komponenten noch mithilfe des HTML Import APIs modularisiert werden. Dieses API ist aber mittlerweile als obsolet gekennzeichnet. Vor allem in älteren Beiträgen wird dennoch von HTML Imports anstatt von ES Modules geredet, was leicht zu Missverständnissen führen kann.

Anwendungsfälle für Web Components

Der hauptsächliche Anwendungsfall für Web Components liegt darin, Framework-unabhängige UI-Komponenten zu erstellen. Durch die breite Browserunterstützung ist das bereits heute möglich. Auch die Integration nativer Web Components in die verschiedenen Single Page Application (SPA) Frameworks ist mittlerweile sehr gut. So können sowohl in Angular als auch in Vue.js Web Components ohne Einschränkungen verwendet werden. Die Unterstützung in React ist aktuell noch verbesserungswürdig. Eine genaue Zusammenstellung über den aktuellen Status der Web-Component-Unterstützung verschiedener Frameworks lässt sich Online nachlesen.

Weiterhin ist es durchaus möglich, ganze Webanwendungen ausschließlich auf Basis von Web Components zu entwickeln, also komplett auf externe Bibliotheken und SPA-Frameworks zu verzichten. Dieser Framework-lose Ansatz ist, zumindest in der Theorie, durchaus attraktiv. Schließlich muss man sich nicht mehr an etwaige Drittanbieter binden, sondern lediglich einen offiziellen Standard nutzen. Leider sind die APIs der Web-Component-Spezifikation aber vergleichsweise rudimentär. Deshalb muss in der Regel mehr Code geschrieben werden als bei den gängigen SPA-Frameworks. Abhilfe können hier leichtgewichtige Bibliotheken wie lit-html schaffen, die aber natürlich wieder eine gewisse Abhängigkeit zur Folge haben.

Fazit

Web Components sind mittlerweile eine breit unterstützte Technologie. Der Standard hilft dabei, typische Probleme bei der Entwicklung webbasierter Applikationen zu umgehen. Dazu zählt vor allem die Framework-übergreifende Wiederverwendbarkeit von Komponenten. Selbstverständlich bedeutet das aber nicht, dass beliebige Teile einer Anwendung automatisch austauschbar sind. In erster Linie eignet sich die Spezifikation für die Umsetzung technisch getriebener Komponenten (z. B. spezielle Buttonelemente oder modale Dialoge).

Vor allem das Custom Element API ist an vielen Stellen sehr rudimentär, sodass es für die Entwicklung von eigenen Web Components hilfreich sein kann, auf externe Bibliotheken zu setzen. Hier bieten zum Beispiel die Frameworks Stencil und Skate  vielversprechende Ansätze. Mit ihnen lassen sich native und damit austauschbare Web Components erstellen, ohne die oftmals umständlichen APIs direkt zu verwenden.

Web Components werden vor allem von Google vorangetrieben, das mit Chrome einen der am meisten verbreiteten Browser am Markt besitzt. Es lohnt sich also definitiv, einen näheren Blick auf die Spezifikation zu werfen.
In diesem Sinne: Stay tuned.

Geschrieben von
Sven Kölpin
Sven Kölpin
Sven Kölpin ist Enterprise Developer bei der open knowledge GmbH in Oldenburg. Sein Schwerpunkt liegt auf der Entwicklung webbasierter Enterprise-Lösungen mittels Java EE.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: