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

Seite 2: Progressive Web Apps

Inhaltsverzeichnis

Im Wesentlichen sind es nur zwei Techniken, die eine Web-App "progressiv" machen. Manifest-Dateien und Service Worker. Chrome erkennt häufiger benutzte Seiten und bietet an, sie auf dem Homescreen abzulegen (man kann das aber auch selbst direkt vornehmen). Seit Chrome 47 wird beim Start der Anwendung auch ein Splash-Screen angezeigt. Dazu benötigt der Browser bestimmte Daten: den Namen der App, ein Icon, den Namen der Datei, die die App lädt, die Hintergrundfarbe für den Splash-Screen et cetera. Das wird über die Mainfest-Datei zur Verfügung gestellt. Wie kaum anders zu erwarten, ist sie in JSON notiert.

Das hilft aber lediglich, eine Internet-Anwendung auf den Homescreen zu bringen, den Rest des "Jobs" erledigen Service Worker. Vereinfacht könnte man sagen, dass ein Service Worker der App vorgaukeln kann online zu sein, selbst wenn sie tatsächlich offline ist.

Service Worker klingt nach dem länger nutzbaren Web Worker, tatsächlich ist das dahinter stehende Verfahren ähnlich. In beiden Fällen wird eine separate JavaScript-Datei geladen, deren Code parallel ausgeführt wird. Es gibt keinen Zugriff auf das DOM, stattdessen wird über Events mit der eigentlichen App kommuniziert. Da enden dann aber die Gemeinsamkeiten, denn Service Worker dienen einem völlig anderen Zweck: Man erhält die Kontrolle über die Datei- und Datenbeschaffung. Dem Browser wird mitgeteilt, welche Anfragen nicht ins Netz gehen, sondern vom eigenen JavaScript-Code verarbeitet werden. Ab dem zweiten Start der WebApp, fällt dort die Entscheidung, ob der Server die Daten neu abholt oder ob man zwischengespeicherte Daten verwendet. Service Worker kollidieren daher mit einer weiteren, etwas älteren Technik – dem AppCache. Er wird selten genutzt, da die Spezifikation recht lang geraten ist und sich der Browser "gefühlt" unvorhersehbar verhält.

Man könnte Service Worker durchaus als programmierbaren AppCache bezeichnen. Einfache Dinge sind mit der älteren API sicherlich schneller umgesetzt, dafür stößt man beim Service Worker nicht so schnell an die Grenzen des Machbaren. Immerhin hat man es mit einer Art clientseitigen Netzwerk-Proxy zu tun. So kann man im Service Worker sogar auf langsame Verbindungen reagieren und dem Anwender erst einmal die gecachten Inhalte präsentieren. Der AppCache kennt hier nur on- oder offline.

Eine Einschränkung von "Workern" an sich betrifft auch den Service Worker: Er arbeitet streng asynchron. So lässt sich LocalStorage nicht als Cache nutzen, IndexedDB hingegen schon. Es kommen die seit ECMAScript 6 zum Sprachumfang von JavaScript gehörernden Promises zum Einsatz. Sie sollen die Arbeit mit asynchronen Abläufen erleichtern, sind aber selbst für langjährige JavaScript-Programmierer nicht auf Anhieb verständlich. Die Verwendung von Promises geht schon bei der Installation des Workers los, das folgende Beispiel setzt aber einen Schritt später an.

Im Browser hängen globale Variablen an window. Da Worker fensterlos in ihrem eigenen Thread ausgeführt werden, übernimmt diese Rolle self. Hier gibt es unter anderem das caches-Objekt (Cache Storage API), das wiederum mehrere benannte Cache-Objekte verwalten kann. Während der Installation geben Entwickler einem solchen Objekt dann mit, welche Dateien es cachen soll. Der folgende Code greift auf gecachte Dateien zurück. Das fetch-Ereignis tritt ein, wenn der Browser ins Netz will.

self.addEventListener('fetch', onFetch);

function onFetch(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
}
)
)
}

Das Fetch-Event hat eine respondWith-Methode, der die Funktion übergeben wird, die auf die Anfrage reagiert. Hier wird das ebenfalls am Event hängende Request-Objekt der match-Methode vom CacheStorage übergeben und die match-Methode jedes Cache-Objekts aufgerufen, bis das passende gefunden wurde. Die Methode gibt nur einen Promise zurück. Über dessen then-Methode können Entwickler auf das eigentliche Ergebnis von match() reagieren. War die Suche erfolgreich, bricht die Funktion mit der Rückgabe eines Response-Objekts ab, ansonsten wird das Request-Objekt benutzt, um die Datei aus dem Netz zu holen. Der Service Worker stellt hierzu fetch() zur Verfügung. Dieser Code stellt im Grunde das Minimum dar, um den AppCache zu ersetzen. Es kann durchaus eine Weile dauern, bis man sich in die diversen beteiligten APIs hineingedacht hat.

Für viele vermutlich überraschend: Der Service Worker überlebt das Schließen der App (respektive des Tabs), auf Android sogar das (scheinbare) Schließen von Chrome. Wie lange der JavaScript-Code eines Service Workers aktiv bleibt, hängt von Chromes "Laune" ab. Tut sich zu lange nichts, wird der Speicher freigegeben. Nutzt man die App hingegen wieder, wird auch der Service Worker wieder zum Leben erweckt. Das bedeutet leider auch, dass auf globale Variablen kein Verlass ist.

Das Verhalten ist aber notwendig, um eine der oben genannten Anforderung zu erfüllen: Benachrichtigungen anzeigen, auch wenn die Web-App gar nicht geöffnet ist. Das wird über Push Notifications erreicht, auf die Entwickler über einen Service Worker zugreifen können. Nicht nur das Starten der App, auch das Eintreffen einer solchen Nachricht aktiviert den Service Worker wieder. Auf dem Desktop funktioniert das nur, solange Chrome tatsächlich geöffnet ist, iOS ist hier komplett außen vor.