zurück zum Artikel

Zukunft der Webentwicklung: Webkomponenten und Progressive Web Apps, Teil 1

Know-how
Zukunft der Webentwicklung: Webkomponenten und Progressive Web Apps, Teil 1

Bei Webanwendungen ist ein Umbruch im Gange. Wer sich künftig den Quelltext einer Webseite ansieht, könnte von einem einzelnen my-app-Element im Body der Seite überrascht werden. Keine Spur des üblichen Sammelsuriums hunderter Divs und anderer Elemente.

Die Nutzung von Komponenten bei der Softwareentwicklung ist keine neue Idee. Zum 25-jährigen Jubiläum von Visual Basic [1] sei an die ActiveX-Steuerelemente erinnert, die in ähnlicher Form auch unter .NET zahllosen Entwicklern das Leben erleichtert haben. Vom jQuery-Plug-in bis zu angesagten Frameworks wie Angular und React – alle bauen auf ihre Weise auf Komponenten, oft als Widgets bezeichnet. Warum also das wachsende Interesse gerade an Webkomponenten?

Die Antwort liegt im vorletzten Satz bei den Wörtern "auf ihre Weise". Alle "Controls" einer Plattform für die Desktop-Entwicklung (wie .NET) haben gemeinsam, dass sie auf eine bestimmte Art eingebunden werden und auf eine bestimmte Art mit der Plattform interagieren. Das macht deren Nutzung trotz aller spezifischer Unterschiede konsistent und damit einfach. Auch wird kein Steuerelement je ein anderes beeinflussen, es sei denn, es reißt gleich das ganze Programm in den Abgrund.

Beim Web als Plattform sieht die Sache anders aus. Bei größeren Projekten hat man die Qual der Wahl. Welches Framework, welche Library deckt die Anforderungen am besten ab? Nicht selten stellt man fest, dass eine Kombination aus mehreren Angeboten ideal wäre, aber das erhöht nicht nur unverhältnismäßig die Ladezeiten, da jedes Framework seinen eigenen Unterbau mitbringt. Auch die Chancen auf Unverträglichkeiten sind nicht gering, vor allem bei CSS. JavaScript lässt zudem viele Freiheiten, sodass die Nutzung jeder neuen Bibliothek und jedes neuen Plug-ins mit der Recherche beginnt, wie deren Einbindung umgesetzt wurde. Zum Teil wird vorhandenes HTML aufgegriffen, andere Produkte erzeugen es komplett selbst.

Die konsequente Verwendung von Webkomponenten könnte dem ein Ende setzen, denn es geht hier nicht nur um die Programmierung eigener HTML-Elemente, sondern auch um einen Paradigmenwechsel, der das DOM als Framework nutzt. So sind nicht wenige davon überrascht, dass Webkomponenten auch für Einsatzzwecke erstellt werden, die auf der Webseite nicht sichtbar sind. Die Webanwendung entsteht letztlich durch die verschachtelte Kombination der Komponenten. Treibt man das auf die Spitze, bleibt am Ende nur eine Webkomponente übrig, die die ganze Anwendung bildet. Bezogen auf Entwurfsmuster kommt hier das "Mediator Pattern" (Vermittler) zum Einsatz. Eine Webkomponente weiß von der Seite wenig, auf der sie sitzt; sie wird eingebunden und angeleitet von der über ihr sitzenden Komponente. Kommuniziert wird über die Werte von Attributen (auf deren Änderung die Komponente reagiert), letztlich über DOM-Ereignisse.

Glücklicherweise Standards

Webkomponenten haben zudem den Vorteil, dass sie auf W3C-Standards fußen, genauer den Spezifikationen für "Custom Elements", "HTML Import" und "Shadow DOM". Wichtige Browser-Hersteller haben sich zu diesen Standards bekannt, sie schon umgesetzt oder sind gerade dabei. Die Vergangenheit hat gezeigt, dass es individuelle Anwendungen auf die Dauer schwer haben, gegen Standards zu bestehen.

"Custom Elements" ermöglichen das eigentliche Erstellen der Komponente (zusammen mit den HTML5-Templates), "HTML Import" erlaubt es, diese auf die Seite zu laden, und der "Shadow DOM" sorgt schließlich dafür, dass die Komponenten sich nicht gegenseitig in die Quere kommen.

Nicht allgemein zugängliche Bereiche des DOM gibt es schon länger. Die Bedienelemente eines <video>-Elements sind in der Regel selbst wieder nur HTML-Elemente, sie tauchen im DOM allerdings nicht auf. Dieser Bereich liegt unter dem eigentlichen DOM, und zwar "in seinem Schatten". Browser-Entwickler haben lediglich Eigenschaften und Methoden für den <video>-Tag vorgesehen, die eine Interaktion über JavaScript ermöglichen. Die Shadow-DOM-Spezifikation erlaubt es Entwicklern, Ähnliches umzusetzen. Eine Webkomponente verhält sich, als säße sie in einem <iframe>, Entwickler können aber genau definieren, wo sie "durchlässig" ist, auch beim Styling. Zur Abgrenzung vom Shadow DOM wird der eigentliche DOM auch als "Light DOM" bezeichnet.

Webkomponenten sind im Browser zu registrieren. Mit jeder neuen Komponente entsteht neuer, sich wiederholender Code, der so ähnlich auch bei Desktop-Controls existiert. Dank einer IDE wie Visual Studio müssen sich Entwickler aber selten damit auseinandersetzen. Spätestens bei der zweiten oder dritten Komponente werden auch Webentwickler darüber nachdenken, wie sie diesen Prozess vereinfachen können, oder sie greifen auf vorhandene Bibliotheken wie Googles Polymer zurück.

Polymer

Polymer – Elemente nach dem Periodensystem

Trotz Angular hat Google mit Polymer ein Projekt gestartet, das die Nutzung von Webkomponenten vorantreiben möchte. Es kombiniert Polyfills und syntaktischen Zucker zu einer Library, die die Nutzung von Webkomponenten in jedem halbwegs aktuellen Browser ermöglicht.

Zum Projekt gehören fertig einsetzbare Elemente, die der "chemischen" Namensgebung folgend in solche des Periodensystems unterteilt wurden. So finden sich unter "Md" (Mendelevium) viele Komponenten, mit denen sich das von Google propagierte Material Design [2] umsetzen lässt. Es gab wohl viele Einwände gegen diese Art der Benennung, sodass Google bei den "App Elements" mittlerweile davon abwich. Hier finden sich unsichtbare Komponenten, die Aufgaben übernehmen, die Entwickler sonst mit Anwendungs-Frameworks angehen würden.

Angular vs. Polymer

Wer das Polymer-Team nach ihrem Verhältnis zu Angular fragt, bekommt erwartungsgemäß Antworten, die die Existenzberechtigung beider Projekte betonen. Es wäre tatsächlich möglich, beides zu verwenden. Beim Rewrite von Angular 2 schielte man schon in Richtung Webkomponenten. Wer in Polymer aber nur eine UI-Library sieht, die viele Aspekte von Angular außer Acht lässt, greift etwas zu kurz. Bei den "App Elements" des "Polymer Element Catalog" sind durchaus Überschneidungen mit Angular erkennbar.

Anfänglich stopften viele Polymer in die Schublade "Plug and Play". Nach dem Import wird das Element irgendwo auf der Seite eingesetzt – fertig. Das ist zwar möglich, aber bei komplexeren Anwendungen wird die Komponente eher als Teil einer eigenen genutzt, was sich sogar über zwei oder drei Ebenen fortsetzen kann, bevor das Element im Light DOM auftaucht.

Seit der IO 2016 wird deutlicher, wie sich Google die Zukunft der Internetentwicklung mit Polymer vorstellt. Die "Carbon Elements" wurden umbenannt, die "Polymer App Toolbox" eingeführt, und es gibt einen Shop für Oberbekleidung, der die neuen Möglichkeiten exemplarisch vorführt. Diese Anwendung wird hier näher betrachtet, wobei schnell klar sein dürfte, dass Google von Webentwicklern ein gewisses Umdenken verlangt.

Viele Webframeworks kommen mit einem Programm für die Kommandozeile (CLI), dass einem lästige Aufgaben abnimmt und so die Zeit bis zur ersten fertigen App verkürzt – so auch bei Polymer, dessen CLI auf Node.js basiert. Es unterstützt den gesamten Entwicklungsprozess vom Erstellen vorgefertigter Programmgerüste bis hin zur Kombination von Build-Tools, die das Projekt für das Deployment vorbereiten. Für größere Firmen interessant: Man kann alles auch ohne das CLI direkt in eigene Prozesse einbinden. Die Toolbox kombiniert also ein Hilfsprogramm mit den umfirmierten "App Elements", um bei Entwicklern Eindruck zu schinden, wie schnell sie eine App online bekommen. Selbstredend wird hier die Google-eigene Cloud-Umgebung Firebase unterstützt, die Entwickler auch nutzen können, wenn die App gar keine Datenbank benötigt.

Gebündelt oder nicht gebündelt?

Bei HTTP/1.1 entscheidet allein der Browser, was der Server laden soll. Das Laden jeder Ressource hat einen gewissen Overhead, da auch jedes Mal eine neue Verbindung aufgebaut wird. Deswegen macht es Sinn, die Dateien vor der Übertragung zu bündeln. HTTP/2 soll die Ladezeiten von Seiten verkürzen. Nicht mehr allein der Browser entscheidet, was geladen wird. Der Server nutzt seinen Informationsvorsprung und gibt einem schon geöffneteten Stream alles mit, was der Browser benötigt (noch bevor dieser das HTML analysiert hat). Das Bündeln von Dateien wäre hier kontraproduktiv, da der Server die Ressourcen für den Server-Push wieder herauslösen müsste. Daher produziert Polymer CLI zwei Varianten des Projekts, "bundled" und "unbundled" – je nachdem, ob der Webserver HTTP/2 unterstützt oder nicht.

Back-Button-Support

Je mehr eine Internetseite in Richtung Web-App geht, desto eher möchten Entwickler vermeiden, dass die Seite bei jeder Aktion der Anwender neu geladen wird. Nicht zuletzt, weil auch der Status der App dabei verloren geht. Entwickler setzen daher Single Page Apps ein, die zu Beginn der Nutzung nur einmal in den Browser geladen werden und sich ansonsten nur noch über XHR (AJAX), WebSockets oder Ähnliches mit dem Server austauschen.

Jetzt genügt aber ein Klick auf den Zurück-Knopf im Browser, und die App wird beendet. Inhalte, die die App verfügbar macht, sind auch für Suchmaschinen nicht mehr indizierbar, sie haben keine eigene URL. Für einen Shop wäre das fatal. Ein potenzieller Kunde könnte nicht einmal die Internetadresse zu einem Produkt in seinem Browser ablegen.

Als Lösung kommt "Client-side Routing" zum Einsatz. Der ursprünglich nur bei Netzwerkern verwendete Begriff meint bei Web-Apps den Prozess, eine URL auf Inhalte abzubilden. Die Internetadresse wird analysiert, um dann zu entscheiden, welche Teile des Programms welche Daten zur Ansicht bringen. In der Regel findet das auf dem Server statt, bei Single Page Apps ist das aber kein gangbarer Weg. Man muss allerdings tricksen, damit sich Suchmaschinen und Nutzer über Links in der App bewegen können, ohne dass die Seite neu geladen wird. Lange Zeit nutzte man hier den Hash, der zum Anspringen von Inhalten auf ein- und derselben Seite gedacht ist. Die URL ändert sich nur hinter dem "#", der Browser sieht keine Veranlassung, die Seite neu zu laden.

Zukünftig dürfte hier vermehrt die Methode pushState des history-Objekts zum Einsatz kommen. Alle gängigen Browser unterstützen die Manipulation des Browserverlaufs (der Internet Explorer ab Version 10). Mobil sperrt sich nur noch Opera Mini. history erlaubt es, sich in den Back-Button des Browsers einzuklinken. Die App wird informiert, wenn Besucher zurückwollen, und kann mittels pushState() die Anzeige der Adresse beeinflussen. Der Hash muss hier nicht mehr vorkommen. Zugleich wird der Klick auf einen Link abgefangen und via JavaScript abgewürgt, sodass der Browser die Seite nicht neu lädt. Selbstredend verwendet Google für seine Fallstudie die modernere Variante.

Jetzt haben Entwickler aber das Problem, dass eine im Browser (oder bei Google) gespeicherte Adresse nicht tatsächlich aufrufbar ist. Im Fall eines Shops adressiert sie ein Produkt, der Webserver geht jedoch davon aus, dass sich hinter der Adresse eine versendbare Datei befindet. Man benötigt für dieses Szenario eine gewisse Kontrolle über den Webserver, um alle Anfragen, die "ins Leere laufen", auf die Datei umzulenken, die den Shop lädt, typischerweise index.html. Beim Apache-Webserver erledigt das eine Rewrite-Anweisung.

Web als Plattform

Keine Scheu, die Plattform zu nutzen

Bei Polymer kümmern sich die App-Elemente <app-route> und <app-location> um das clientseitige Routing. Sie sind ein schönes Beispiel für die Zusammenarbeit von Webkomponenten, die eigentlich einfache Aufgaben erledigen, aber im Zusammenspiel mit weiteren Elementen den Job erledigen.

Das Folgende ist gleichzeitig ein Beispiel für Datenbindung von Polymer, die die Kommunikation zwischen den Elementen erleichtert. Den Seitenquelltext erzeugte im Wesentlichen Polymer CLI:

<!doctype html>
<html>
<head>
<title>demo</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="/manifest.json">
<script src="bower_components/webcomponentsjs/
webcomponents-lite.js"></script>
<link rel="import" href="src/demo-app/demo-app.html">
</head>
<body>
<demo-app></demo-app>
</body>
</html>

Als Erstes fällt das Einbinden der Manifest-Datei auf. Sie folgt der W3C-Spezifikation für Web-App-Manifest-Dateien [3] und enthält Metadaten zur Anwendung, wie sie für Googles Progressive-Web-Apps-Ambitionen benötigt werden. Dazu in einem künftigen Artikel mehr.

Als Nächstes wird webcomponents-lite.js geladen, es enthält die Browserlücken stopfenden Polyfills. Der Ordner "bower_components" stammt von der Verwendung des Paketmanagers Bower [4]. Das Node.js-Modul vereinfacht den Download der JavaScript-Bibliotheken. In erster Linie löst es Abhängigkeiten auf, was sonst vor allem bei neuen Versionen problematisch werden kann.

Schließlich wird demo-app.html importiert und im Body der Seite eingesetzt:

<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/app-route/
app-location.html">
<link rel="import" href="../../bower_components/demo-location/
demo-location.html">
<dom-module id="demo-app">
<template>
<app-location route="{{route}}"></app-location>
<demo-location route="[[route]]"></demo-location>
</template>

<script>
Polymer({
is: 'demo-app',
});
</script>
</dom-module>

Hier laden Entwickler zunächst alles, was sie für die App brauchen: Polymer selbst, das bei app-route untergebrachte app-location-Element und ein eigenes Element demo-location. In <template> steht das HTML, das <demo-app> einbindet. Wenn Polymer es analysiert, werden die Klammern im Wert des route-Attributs entdeckt. Sie zeigen an, dass Data Binding gewünscht ist: im Falle der geschweiften Klammern in beide Richtungen, bei den eckigen nur in eine. Im <script>-Element steht, was Polymer minimal benötigt. Hier wird das Element nur benannt. Wie einfach das Data Binding
funktioniert, verdeutlicht das demo-location-Element:

<link rel="import" href="../../bower_components/polymer/polymer.html">

<dom-module id="demo-location">
<template>
<p><span>location:</span> <span>{{route.path}}</span></p>
</template>

<script>
Polymer({
is: 'demo-location',
properties: {
route: {
type: Object
}
}
});
</script>
</dom-module>

Polymer sind die Eigenschaften bekannt zu machen, auf die es beim Element achten soll. Das geschieht durch Erstellen des Objekts properties. Minimum ist eine Typdeklaration. Da die route-Property der location-Komponente vom Typ Object ist, ist das hier anzugeben. Das Objekt hat eine Eigenschaft "path", die das Template verwendet.

Bei Annahme, dass die App durch http://localhost/polymer-demo/index.html im Browser aufgerufen würde, zeigt die Seite nun "location: /polymer-demo/index.html an. Die vom location-Element erzeugte Eigenschaft "path" wurde also in die Seite eingefügt, ohne dass Entwickler hierfür Code schreiben mussten. Bei Polymer geschieht das automatisch. Das funktioniert aber nur, solange man den Kontext einer mit Polymer erzeugten Komponente nicht verlässt. Würden die Elemente <app-location> und <demo-location> direkt im Light DOM auf der Seite sitzen, käme keine Bindung zustande.

Fähig für Lazy Loading

Polymer erlaubt es, Elemente erst dann zu laden, wenn sie tatsächlich gebraucht werden (Lazy Loading). Hierzu ist es erforderlich, in den Routing-Prozess einzugreifen. Man unterbricht das Laden neuer Inhalte, um sicherzustellen, dass die benötigten Elemente schon vorhanden sind. Wenn nicht, geht es erst weiter, wenn sie mit importHref geladen wurden. Im nächsten Schritt wird <demo-location> durch <app-route> ersetzt, so wie von den Polymer-Schöpfern vorgesehen.

<app-route
route="{{route}}"
pattern="/:page"
data="{{path}}"
tail="{{tail}}">
</app-route>

Man kann das Element wie eine Funktion ansehen, wobei die Attribute route und pattern die Eingabe darstellen, path und tail die Ausgabe. Das Element wird aktiv, wenn sich über die Datenbindung zu
<app-location> der Wert von route ändert, also wenn sich in der Adressbox des Browsers etwas tut. Mit dem Pattern lässt sich angeben, welche Teile des Pfads wie benannt werden sollen.

Das Vorgehen erinnert etwas an die extract-Funktion von PHP, die die Werte eines assoziativen Arrays zu einzelnen Variablen überträgt. Der obige Pfad mündet in ein Objekt mit der Eigenschaft "page", die den Wert "polymer-demo" hat, und dieses Objekt wird über das data-Attribut anderen Elementen bekannt gemacht. Was durch das Pattern nicht abgedeckt ist, landet im tail-Attribut, in diesem Fall also "index.html". Man ahnt, wie es weitergeht: Ein weiteres Element verbindet sich mit app-route und nutzt dessen Werte zur Darstellung von Inhalten.

Im Falle des Shops ist das iron-pages. Unter "Iron" (Fe) hat Google Komponenten grundsätzlicher Natur versammelt. Sie macht kaum mehr, als dafür zu sorgen, dass immer nur eines der Kindelemente sichtbar ist. Die Entwickler sprechen in diesem Zusammenhang von Views. Der Shop kennt fünf davon, und jede Ansicht ist wieder über eine eigene Komponente umgesetzt, <shop-home> für die Startseite, <shop-cart> für den Warenkorb et cetera.

<iron-pages selected="[[path.page]]" attr-for-selected="name" 
selected-attribute="visible">
<div name="one">1</div>
<div name="two">2</div>
<div name="three">3</div>
</iron-pages>

Der Wert des Attributs selected entscheidet, welcher <div>/i] sichtbar ist. Standardmäßig lässt sich hier der Index angeben. Der Autor folgt aber dem Shop und gibt den Elementen einen Namen. [i]<iron-pages> muss nun wissen, was es verwenden soll. Das geschieht über dessen Attribut attr-for-selected. Stände im selected-Attribut "two", würde im Beispiel also die Zahl 2 angezeigt. Der Shop verwendet außerdem selected-attribute. Hierüber setzen Entwickler ein Attribut beim gerade sichtbaren Element. Im Chrome-Inspector können Entwickler beobachten, dass bei der jeweiligen Komponente das visible-Attribut vorhanden ist. Über sogenannte Observer kann die Komponente nun darauf reagieren, ob sie gerade zu sehen ist oder nicht. Details lässt der Artikel außen vor, es soll nur deutlich werden, wie hier das DOM genutzt wird.

Im eigentlichen Shop übernimmt die übergeordnete Komponente <shop-app> ihre Rolle als Vermittler und reagiert selbst auf Änderungen bei <app-route>. Der Wert wird dann auf das eigene page-Attribut übertragen. Deswegen steht dort bei selected schlicht "[[page]]". Das ermöglicht zusätzliches Scripting, zum Beispiel soll zum Seitenanfang gescrollt werden.

Fazit

Polymer erlaubt es, Anwendungen aus kleinen, in sich geschlossenen Elementen zusammenzusetzten. Was die objektorientierte Softwareentwicklung so erfolgreich gemacht hat, findet sich jetzt im DOM wieder. Auch der von Google gewählte Twitter-Hashtag #UseThePlatform [5] unterstreicht, für den Ansatz einfach zu verwenden, was im Browser vorhanden ist.

Zukunft der Webentwicklung: Webkomponenten und Progressive Web Apps, Teil 2 [6]

Die konsequente Unterteilung der Anwendung in Komponenten spielt letztlich einem weiteren ambitionierten Google-Projekt in die Hände – den Progressive Web Apps, auf die ein zweiter Artikel eingehen wird. (ane [7])

Zukunftsmusik

Die kommende Version 2.0 von Polymer wird einige aus der Not geborene Eigenheiten wieder abschaffen, da Chrome schon bald nicht mehr der einzige Browser ist, der Webkomponenten vollständig unterstützt. Wer früher mit JavaScript fremdelte, wird sich wahrscheinlich darüber freuen, dass Komponenten dann auch als Klassen (eingeführt mit ES6) programmierbar sind.

Stefan Neumann [8]
ist Webentwickler der ersten Stunde und betreut bei der tma pure GmbH das hausinterne Shop-System. Für die Planung eines Open-Source-CMS werden von ihm die neusten Web-App-Trends genauer betrachtet.


URL dieses Artikels:
http://www.heise.de/-3355449

Links in diesem Artikel:
[1] https://www.heise.de/meldung/Rundes-Jubilaeum-Microsoft-feiert-25-Jahre-Visual-Basic-3216976.html
[2] https://material.google.com/
[3] https://w3c.github.io/manifest/
[4] https://bower.io/
[5] https://twitter.com/search?q=%23UseThePlatform
[6] https://www.heise.de/developer/artikel/Zukunft-der-Webentwicklung-Webkomponenten-und-Progressive-Web-Apps-Teil-2-3355490.html
[7] mailto:ane@heise.de
[8] mailto:neumann@tma-pure.de