Progressive Web Apps, Teil 2: Die Macht des Service Worker

ÜberKreuz  –  1 Kommentare

Progressive Web Apps sind keine klar abgegrenzte Technologie, sondern beschreiben eine Gattung von Webanwendungen, die bestimmte Eigenschaften aufweisen. Solche Eigenschaften stellen die Verbindungsunabhängigkeit oder das Reengagement des Anwenders über Push-Benachrichtigungen dar. Dank der Service Worker fallen die letzten Grenzen im Web, die native Anwendungen bislang von Webanwendungen unterschieden hatten.

Auch zuvor ließen sich Cross-Plattform-Anwendungen auf Basis von Webtechnologien umsetzen, die diese beiden Features unterstützen. Bislang sind dafür aber native Anwendungsrahmen wie Cordova oder Electron erforderlich. Mit den Service Workers kommen selbst Websites in den Genuss dieser erstklassigen Features.

"Ein Skript auf dieser Seite ist eventuell beschäftigt oder es antwortet nicht mehr." Diese Meldung hat jeder in seinem Webbrowser sicherlich schon einmal gesehen. Grund dafür ist, dass sich ein auf der Seite geladenes JavaScript "aufgehängt" hat – entweder weil es in eine Endlosschleife übergegangen ist oder die Bearbeitung einer synchronen Aufgabe ungewöhnlich lange dauert. In JavaScript gibt es zunächst nämlich nur einen einzigen Thread.

Um Multithreading ins Web zu bringen, wurden die sogenannten Web Worker eingeführt. Ein als Web Worker ausgeführtes Snippet läuft in einem eigenen Thread. Web Worker haben keinen Zugriff auf das Document Object Model (DOM) des Hauptdokumentes. Web Worker und Hauptdokument können sich aber über eine nachrichtenbasierte Schnittstelle austauschen. Ein Web Worker gehört immer zur jeweiligen Registerkarte oder Fenster und lebt maximal solange, bis die Registerkarte oder das Fenster geschlossen werden. Somit eignet sich der Web Worker zum Auslagern rechenintensiver Arbeit.

Service Worker als Proxy respektive Interceptor

Der Service Worker ist verwandt mit dem Web Worker: Auch er läuft in einem eigenen Thread, erlaubt keine direkte Manipulation des DOM des Elterndokuments und verfügt über die nachrichtenbasierte Schnittstelle. Der Service Worker agiert im Gegensatz dazu jedoch als Controller, Proxy respektive Interceptor: Er verfügt über einen eigenen Cache und kann sich zwischen jeden ausgehenden Netzwerk-Request schalten. Der Service Worker kann dann entscheiden, ob er eine Anfrage aus seinem Cache beantworten kann oder die Anfrage an das Netzwerk weiterleitet. Auf genau diese Art lässt sich die Offlinefähigkeit realisieren.

Der Service Worker kann zudem Hintergrundaufgaben ausführen, muss vor Verwendung installiert werden und bezieht sich auf den aktuellen Scope – das ist die Kombination aus der Origin (Protokoll, Hostname und Port) und dem Pfad, unterhalb dessen eine Website ausgeführt wird. Der Service Worker lebt zudem unabhängig vom Tab oder dem Fenster. Auf diese Art werden die Push-Benachrichtigungen umgesetzt: Der Service Worker agiert wie ein Hintergrunddienst, der auch dann läuft, wenn die Webanwendung nicht geöffnet ist (oder zum Beispiel auf Android die Chrome-App geschlossen wurde). Der Webbrowser kann den Service Worker zu bestimmten Anlässen aktivieren, etwa falls ein ausgehender Netzwerk-Request behandelt werden muss oder eine Push-Nachricht eintrifft.

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js')
.then(registration => console.log('Service worker successfully
registered for ${registration.scope}'))
.catch(err => console.log('Service worker registration
failed: ${err}'));
}

Das Listing zeigt die Registrierung eines Service-Worker-Skripts, hier "sw.js". Das if-Statement dient der Umsetzung des Prinzips "Progressive Enhancement": Nur wenn ein Browser auch Service Worker kennt, wird dieser registriert. Ansonsten läuft die Anwendung ohne Registrierung eines Service Worker weiter. Der Benutzer wird bei der Registrierung des Workers übrigens nicht gefragt. In Chrome können Sie auf der Seite chrome://serviceworker-internals sehen, welche Service Worker auf Ihrem System bereits installiert sind. Vermutlich werden Sie überrascht sein, wie viele es sind. Gerade weil der Service Worker so mächtig ist, muss die Website über das Hypertext Transfer Protocol Secure (HTTPS) ausgeliefert worden sein. Nur dann lässt sich der Service Worker installieren – für localhost gibt es zu Entwicklungszwecken natürlich eine Ausnahme.

self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request)
.then(cachedResponse => cachedResponse || fetch(event.request));
);
});

Das obige Listing zeigt einen Ausschnitt eines Service-Worker-Skripts, bei dem auf ausgehende Netzwerkanfragen reagiert wird. Um die Antwort zu manipulieren, registriert sich der Service Worker auf das Ereignis fetch. Findet der Service Worker einen passenden Eintrag in seinem Cache für den angegebenen Request, beantwortet er die Anfrage, ohne sie tatsächlich über das Netzwerk zu senden. Wird keine passende Antwort im Cache gefunden, wird über die Methode fetch ein echter Netzwerk-Request ausgeführt. Da geschieht völlig transparent für den Anwendungscode und gilt auch für übrige Ressourcen wie Bilder, JavaScript-Quelltext oder Stylesheets.

PWA-Pushbenachrichtigungen finden sich neben "nativen" Benachrichtigungen
self.addEventListener('push', event => {
event.waitUntil(
self.registration.showNotification('Neuer Stadtführer verfügbar', {
body: 'Entdecken Sie die Schönheiten der Baustellenstadt Karlsruhe.',
icon: 'assets/launcher-icon-3x.png',
tag: 'notification'
});
);
});

Die Entwicklung eines Service Worker, insbesondere der unterschiedlichen Caching-Strategien, kann kompliziert sein. Aus diesem Grund gibt es verschiedene Tools, die eine Standardimplementierung eines Service Worker anbieten oder einen solchen aus den Build-Artefakten generieren können. Das Single-Page-Web-Application-Framework Angular bietet beispielsweise das Paket @angular/service-worker an. Es unterstützt das Cachen statischer Dateien oder Push-Benachrichtigungen.

Das Tool sw-precache kann verwendet werden, um einen Service Worker zu generieren, der Ressourcen lädt und cacht, sodass diese auch offline zur Verfügung stehen. Das bietet sich etwa für die sogenannte App-Shell an, also den statischen Bereich der Anwendung, wie Navigationsleisten oder Menüs, der auch ohne Internetverbindung funktioniert und bei fehlender oder schwacher Verbindung einen Hinweis anzeigen kann. Möchte man den Service Worker komplett selbst schreiben, bietet Google mit der sw-toolbox eine Bibliothek zur vereinfachten Implementierung des Service Worker an.

So viel zum Service Worker, einem der mächtigsten Features des modernen Web. Service Worker können derzeit unter Google Chrome und Mozilla Firefox verwendet werden. In Microsoft Edge sollen sie in Kürze Einzug erhalten. Von Apple hörte man bisher nur ein verhaltenes "wir sollten es machen" im inoffiziellen Fünfjahresplan der Webkit-Engine. Doch selbst wenn Webkit Service Worker eines Tages unterstützen sollte, ist damit nicht gesagt, dass dies auch unter iOS der Fall sein wird. In der nächsten Woche sehen wir uns das Web App Manifest an. Dieses spezifiziert den Titel und das Logo der App: Mit einem Fingertippen wird aus der Web-App eine App-App.