Web

Tutorial: Erste Schritte mit Progressive Web Apps

10.06.2024
Von 
Matthew Tyson ist Java-Entwickler und schreibt unter anderem für unsere US-Schwesterpublikation Infoworld.com.
Progressive Web Apps sind komplexer zu entwickeln als traditionelle Webanwendungen. Lesen Sie, warum sich dieser Zusatzaufwand lohnt.
Progressive Web Apps sind ein innovativer Ansatz der modernen Webentwicklung.
Progressive Web Apps sind ein innovativer Ansatz der modernen Webentwicklung.
Foto: KatePilko | shutterstock.com

Progressive Web Apps (PWA) verbinden die Allgegenwärtigkeit von Webbrowsern mit der Funktionsvielfalt nativer Applikationen. Sie sind dabei zwar aufgrund spezialisierter Features schwieriger zu entwickeln, bieten im Gegenzug jedoch einen enormen Vorteil: Geräteübergreifende, Native-ähnliche Funktionen - serviert in einem Browser. Auch langfristig betrachtet lohnt es sich, die zusätzliche Entwicklungskomplexität auf sich zu nehmen: PWA erfordern lediglich eine singuläre Codebasis und ermöglichen parallel, mit vertrauten Browser-Standards zu arbeiten.

In diesem Tutorial lesen Sie, welche Features Progressive Web Apps auszeichnen, wie Sie sie installieren - und bereitstellen.

Das können Progressive Web Apps

Ein charakteristisches Merkmal von nativen Apps ist, dass sie auch laufen, wenn keine Netzwerkverbindung existiert. Progressive Web Apps unterstützen ähnliche Offline-Funktionalitäten innerhalb eines Browsers. Im Gegensatz zu Browser-basierten Webanwendungen sind PWA allerdings stark vom Anwendungstyp abhängig: Die Funktionen der App bestimmen maßgeblich darüber, wie Progressive Web Apps implementiert werden.

Zu den allgemeinen Features von Progressive Web Apps zählen:

  • Offline-Funktionalität,

  • Hintergrundaktivität (einschließlich Synchronisierung),

  • Homepage-"Installation",

  • Push-Benachrichtigungen und Alerts (auch wenn die Anwendung nicht läuft),

  • aggressives Caching (eine Strategie, um intermittierende Netzwerkprobleme zu minimieren),

  • geräteübergreifende, responsive Mobile-First-Layouts,

  • Link-Share- und Bookmark-Funktionalitäten.

Ein guter Anwendungsfall für eine Progressive Web App ist Google Docs. Die Browser-basierte Anwendung unterstützt einen Offline-Modus, wenn kein Netzwerk verfügbar ist. Im Wesentlichen kann die App dabei sämtliche Inhalte lokal im Browser speichern und mit dem Backend synchronisieren, sobald der Browser wieder online ist.

Was uns zur Komplexität von PWA führt, die sich im Wesentlichen darin begründet, dass distribuierte Teile einer Anwendung synchronisiert werden müssen. Google Docs stellt ein entsprechend massives, architektonisches Unterfangen dar. Es geht aber auch ein bisschen simpler: Wie schon erwähnt, gilt der Grundsatz, dass die Anforderungen der Anwendung darüber bestimmen, wie umfangreich Progressive Web Apps implementiert werden müssen.

Progressive Web Apps installieren

Eine Besonderheit von Progressive Web Apps: Sie können "installiert" werden, obwohl sie im Browser laufen. Auf dem Frontend wird dabei ein Link auf der Startseite des Devices platziert, der die Website im Browser aufruft. Die PWA-Installation selbst erfolgt über eine manifest.json-Datei, die dem Browser die App-Funktionen und ihr Homepage-Icon beschreibt. Im Folgenden ein Beispiel für ein einfaches Manifest:

{

"name": "My PWA App",

"short_name": "My PWA",

"icons": [

{

"src": "icons/icon-192x192.png",

"sizes": "192x192",

"type": "image/png"

}

],

"start_url": "/",

"display": "standalone",

"theme_color": "#ffffff"

}

Findet der Browser eine solche Datei im Root-Verzeichnis, bietet er an, einen Link zur Homepage hinzuzufügen (vorausgesetzt, die Datei ist validiert).

PWA-Funktionen über Service Worker bereitstellen

Bereitgestellt werden PWA-Funktionen hauptsächlich über sogenannte Service Worker. Zugriff auf die Service Worker API ermöglicht Ihnen das navigator.serviceWorker-Objekt, das ausschließlich in einem sicheren (HTTPS) Kontext verfügbar ist. Ein Service Worker ist so etwas Ähnliches wie ein Worker Thread, weist aber einen längerfristigen Lebenszyklus auf und greift weniger auf DOM und Browser-APIs zu.

Stellen Sie sich einen Service Worker als einen isolierten Kontext vor, der mit Ihrem Haupt-Thread (und anderen Workern) über Messages und Events interagieren kann: Er reagiert auf Events, fährt Netzwerk-Requests, antwortet auf Push-Calls und speichert Informationen über die Cache API oder über IndexedDB. Ein Service Worker kann dabei nur über Nachrichten an den Haupt-Thread auf das User Interface einwirken. Man könnte die Auffassung vertreten, dass ein Service Worker eine Proxy-Middleware darstellt, die im Browser läuft.

In Sachen Lebenszyklus weisen Service Worker Besonderheiten auf. Spezifische Bedingungen bestimmen darüber, wann sie beendet werden. Sie können Benutzern auf Push-Basis auch dann noch Benachrichtigungen übermitteln, wenn die Seite, die sie hervorgebracht hat, bereits geschlossen wurde. Zudem gibt es auch Browser-spezifische Informationen darüber, wie Service Worker zu beenden sind - zum Beispiel für Chrome.

Service Worker Events

Im Wesentlichen sind Service Worker asynchrone Event Handler: Sie reagieren auf UI- oder Backend-Events. Wenn Sie Service Worker erstellen, sollten Sie dabei im Hinterkopf behalten, dass der Kontext zwischen den Events gelöscht wird. Es ist nicht möglich, den State in lokalen Variablen zu speichern. Stattdessen greifen Sie dazu auf den Cache oder eine Datenbank zurück.

Ein Service Worker kann auf folgende Events reagieren:

  • install wird einmalig ausgelöst, sobald der Service Worker zum ersten Mal installiert wird. Dieses Ereignis wird häufig verwendet, um Assets wie HTML-, CSS- und JavaScript-Dateien zu Gunsten von Offline-Funktionalität zwischenzuspeichern.

  • activate wird ausgelöst, wenn ein Service Worker aktiv wird. Hiermit können auch Caches früherer Service-Worker-Versionen bereinigt werden und Tasks bearbeitet werden, die anfallen, wenn der Service Worker die Kontrolle über Clients (kontrollierte Webseiten) übernimmt.

  • fetch wird immer dann getriggert, wenn eine kontrollierte Seite einen Request an das Netzwerk stellt. Das befähigt den Service Worker, als unsichtbarer Vermittler für die Hauptseite zu fungieren, Requests abzufangen und diese möglicherweise zu modifizieren.

  • sync wird bei stabiler Netzwerkverbindung in Intervallen ausgelöst, die vom Browser definiert werden. Dieses Event wird häufig verwendet, um offline vorgenommene Änderungen an Daten mit dem Server zu synchronisieren. Wenn ein Netzwerk verfügbar ist, unterstützt die Sync API dabei, Requests zu automatisieren.

  • push wird ausgelöst, wenn der Service Worker eine Push-Benachrichtigung von einem Server erhält. Diese kann er verarbeiten und dem Benutzer anzeigen, auch wenn die Webseite geschlossen ist.

  • notificationclick wird getriggert, wenn der Benutzer auf eine Push-Benachrichtigung klickt. Dieses Ereignis kann genutzt werden, um die Interaktion mit der Benachrichtigung zu verarbeiten und den User zu einer bestimmten Seite innerhalb der PWA zu leiten.

  • error kann in verschiedenen Situationen ausgelöst werden, wenn der Service Worker während seines Betriebs auf einen Fehler stößt. Dieser Event ist nützlich für Protokollierungs- oder Debugging-Zwecke.

  • broadcast und post messages sind Ereignisse, die speziell von JavaScript-Code im Haupt-Thread ausgelöst werden. Sie werden verwendet, um Daten an Service Worker zu übermitteln.

Darüber hinaus können Service Worker auch auf verschiedene APIs zugreifen:

  • IndexedDB ist eine robuste Object-Store-Datenbank, die Querying unterstützt. Sie "lebt" zwischen den Service-Worker-Instanzen und wird mit dem Haupt-Thread geteilt.

  • Die Cache API erleichtert des, Request Objects abzurufen und deren Antworten zu speichern. In Kombination mit dem fetch-Eevent erleichtert die Cache API, Antworten für den Offline-Modus auf transparente Art und Weise zwischenzuspeichern. Einen Überblick über potenzielle Cache-Strategien finden Sie hier.

  • Fetch- und WebSocket-APIs gewährleisten vollumfänglichen Netzwerkzugriff für Service Worker, trotz fehlenden DOM-Zugriffs.

  • Geolokalisierung ist im Bereich Service Worker ein heiß diskutiertes Thema - sowohl bezüglich Exposure als auch Support.

Ein Service-Worker-Beispiel

Ein Service Worker wird initial stets mit dem navigator.serviceWorker-Objekt in ein JavaScript-File geladen - etwa so:

const subscription = await navigator.serviceWorker.register('service-worker.js');

Event Subscrptions erfolgen in service-worker.js. Um beispielsweise ein fetch-Event mit der Cache API in Augenschein zu nehmen, könnten Sie wie folgt vorgehen:

self.addEventListener('fetch', (event) => {

const request = event.request;

const url = new URL(request.url);

// Try serving assets from cache first

event.respondWith(

caches.match(request)

.then((cachedResponse) => {

// If found in cache, return the cached response

if (cachedResponse) {

return cachedResponse;

}

// If not in cache, fetch from network

return fetch(request)

.then((response) => {

// Clone the response for potential caching

const responseClone = response.clone();

// Cache the new response for future requests

caches.open('my-cache')

.then((cache) => {

cache.put(request, responseClone);

});

return response;

});

})

);

});

Wenn der Service Worker geladen ist, verweist self auf ihn. Mit der addEventListener-Methode können Sie nach verschiedenen Ereignissen Ausschau halten. Innerhalb des fetch-Events können Sie mit Hilfe der Cache API prüfen, ob die angegebene Anfrage-URL bereits im Cache vorhanden ist. Falls ja, wird sie zurückgesendet. Handelt es sich um eine neue URL, stellen Sie Ihren Request an den Server und speichern die Antwort zwischen. Die Cache-API kann die Komplexität deutlich reduzieren, wenn es darum geht, herauszufinden, ob es sich um denselben Request handelt oder nicht. Service Worker machen das für den Haupt-Thread transparent. (fm)