Progressive Web Apps in der Praxis


14.05.2020 von 2020-07-13

https://www.iteratec.de/fileadmin/Bilder/News/iteratec_logo.png
iteratec GmbH
Progressive Web Apps in der Praxis

Zuerst erschienen in Java Aktuell 03/2020 (ijug.eu)

Oft macht das Aufrufen von Webseiten und die Benutzung von komplexeren Webanwendungen unterwegs oder auf mobilen Geräten wenig Spaß. Native Anwendungen sind hier klar im Vorteil, denn diese sind offline und vor allem bequemer aufzurufen als Webseiten. Der Begriff “Progressive Web App” steht für eine Sammlung von Konzepten und Techniken, welche Webseiten und Webanwendungen wieder attraktiv werden lassen. Sie helfen dabei, Webanwendungen schneller und bei fehlender Internetverbindung auch offline laden zu lassen. Außerdem sollen sie imstande sein, auf unterschiedliche Endgeräte flexibel zu reagieren. In diesem Artikel wird die Philosophie der Progressive Web Apps erklärt und gezeigt, wie das Ganze technisch funktioniert.

Als Apple im Jahre 2007 das erste iPhone der Weltöffentlichkeit vorstellte, war es eigentlich angedacht, Anwendungen auf dem Gerät standardmäßig mit HTML, CSS, JavaScript und Ajax, damals auch unter Web 2.0 bekannt, zu erstellen. Der Safari Browser sollte hierfür Schnittstellen zu tieferen Schichten des Telefons anbieten. Diese Idee war für damalige Verhältnisse extrem innovativ und vorausschauend, da es die Hürde, eine Anwendung zu erstellen im Vergleich zu anderen Herstellern bedeutend niedriger machte. Apple erkannte schon sehr früh, dass ein Browser nicht nur für das Rendern von Webseiten geeignet ist, sondern vielmehr als Plattform für komplexe Anwendungen dienen kann. Leider waren die Möglichkeiten von HTML zu der Zeit begrenzt und HTML5 ließ noch eine Weile auf sich warten. Daher und auch aus marketingtechnischen Gründen änderte Apple diese Strategie zugunsten von nativ entwickelten Anwendungen, die über einen extra Marktplatz, den App Store, vertrieben wurden.

Die Jahre vergingen und die verschiedenen Browser wurden nicht zuletzt auch durch die mächtigen HTML5-Schnittstellen, die es erlauben auf Hardware und Dateien zuzugreifen, tatsächlich immer mehr zu “App-Engines”. Diese sind in der Lage, komplexe Anwendungen unabhängig vom Betriebssystem oder Gerät auszuführen. CSS3 tat sein Übriges, denn aufwändige Animationen und grafische Spielereien sind damit relativ einfach umsetzbar. Responsive Layouts (“Mobile First”) werden zum Standard. Immer mehr sogenannte Cross-Plattform Frameworks erobern den Markt (z.B. Xamarin, Electron.io, React-Native, usw.). Die Grenzen zwischen Web und Desktop, Geräten und Betriebssystemen verschwimmen zusehends.

Mit der Einführung der Web Worker und Service Worker war auch der Grundstein für Progressive Web Apps (PWAs) gelegt. Als treibende Kraft hinter den PWAs hat Google hier viel Pionierarbeit geleistet und gewisse Standards definiert. Hinter diesem Begriff steckt letztendlich eine Vielzahl von Ideen und Konzepten, die alle darauf abzielen, eine Webanwendung mehr wie eine native Anwendung anfühlen zu lassen. Eine der wichtigsten Überlegungen ist: Wie kann der Zugang zu einer Anwendung für möglichst viele Benutzer und damit also für unterschiedlichste Umgebungen (Browser, Betriebssystem, Gerät) so einfach wie möglich gestaltet werden? Die Anwendung selbst soll

  •     so schnell wie möglich erste sinnvolle Ergebnisse liefern  
  •     auf Umgebungen, die wenig Features anbieten sinnvolle Ergebnisse liefern
  •     auf Umgebungen, die vollen Funktionsumfang liefern, diesen auch nutzen
  •     offline sinnvolle Ergebnisse liefern
  •     so einfach wie möglich installierbar sein
  •     sicher sein
  •     aus Marketinggründen dafür sorgen, dass die Benutzer so wenig wie möglich gleich wieder abspringen

Es gibt einige Beispiele von Webseiten, die nach der Einführung von PWAs ungewöhnlich signifikante positive Änderungen von KPIs (wie Umsatz oder Benutzerzahlen) vorweisen können. Unter www.pwastats.com gibt es sogar eine Webseite, die solche Erfolgsgeschichten sammelt – und selbst eine PWA ist.

Die Bausteine für eine PWA

Die Bausteine, die eine gewöhnliche Webseite zu einer Progressive Web App machen, sind zunächst übersichtlich. Es steckt aber allerhand Überlegungsarbeit in der konzeptionellen Implementierung. Wir benötigen:

  •     HTML, CSS, etwas JavaScript
  •     eine Service Worker Datei
  •     eine Web App Manifest Datei
  •     etwas Markup und JavaScript zum Zusammenstecken
  •     HTTPS: PWAs dürfen nur über HTTPS aufgerufen werden

Das in Listing 1 dargestellte HTML Markup ist das zunächst notwendige Gerüst. Hier ist vorerst noch nichts Spannendes zu sehen, wir werden dieses Gerüst aber nun Schritt für Schritt erweitern.

<LISTING 1> 

<!DOCTYPE html> 

<html> 

<head> 

  <title>My super PWA</title> 

  <link rel="stylesheet" href="./style.css"> 

</head> 

<body> 

  <div>Hier kommt später der eigentliche Inhalt</div> 

  <script src="./app.js"></script> 

</body> 

</html> 
Listing 1: index.html - Grundgerüst der Webanwendung

Der eigentliche Trick im Hintergrund

Der Service Worker ist ein kleiner Helfer im Hintergrund. Er ermöglicht es, gewisse Dinge unabhängig von der Seite selbst zu erledigen (beispielsweise eine PWA offline zu laden). Es handelt sich um eine JavaScript Datei, die Zugriff auf Cache und Netzwerkfunktionen des Browsers hat. Sie wird nicht im Kontext der Webseite, sondern in einem separaten Thread ausgeführt. Für vom Browser oder programmatisch ausgelösten Web-Anfragen, kann der Entwickler anders als in dem veralteten AppCache Mechanismus, feingranular entscheiden, bei welcher Anfrage wie verfahren wird. So können zum Beispiel bestimmte HTML-, JavaScript-Dateien oder REST-Anfragen in den Browser Cache geladen werden. Diese Daten liegen beim nächsten Aufruf der Anwendung sofort vor und es muss nicht erst auf eine Antwort des Servers gewartet werden. Dennoch kann aber, falls das Gerät online ist, die Anfrage zusätzlich an den Server gesendet werden. Wenn die Antwort angekommen ist, wird der Status der Anwendung entsprechend aktualisiert. Der Benutzer bekommt die Anwendung präsentiert und kann damit arbeiten, während im Hintergrund noch Inhalte nachgeladen werden. Der Service Worker kann auch Daten synchronisieren, ohne die Anwendung selbst zu blockieren und das auch ohne, dass die Anwendung überhaupt in einem Tab geöffnet ist.

<LISTING 2> 

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

  event.waitUntil( 

    caches.open('v1').then((cache) => { 

      return cache.addAll([ 

        '/', 

        '/index.html', 

        '/images/logo.png' 

      ]); 

    }) 

  ); 

}); 

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

  event.respondWith( 

    caches.match(event.request).then((response) => { 

      return response || fetch(event.request); 

    }) 

  ); 

}); 
Listing 2: service-worker.js - Service-Worker-Datei, die es ermöglicht, dass die index.html auch offline lädt

In Listing 2 ist der Inhalt der Service-Worker-Datei dargestellt, über den eine Webanwendung prinzipiell offlinefähig wird. Hier wird auch das Prinzip deutlich, wie sich ein Service Worker in die Anwendung einklinkt. So kann auf die verschiedenen Events des Lebenszyklus (“install” und “activate”) und auf Netzwerkanfragen (“fetch”) gehorcht werden. In unserem Beispiel wird bei dem “install”-Ereignis, das auftritt, wenn der Service Worker installiert wird, gesagt, dass die Antworten auf die Anfragen “/”, “/index.html”, und “/images/logo.png” im Cache mit dem von uns ausgedachten Namen “v1” gespeichert werden sollen. Beim nächsten Start der Seite, greift das “fetch”-Ereignis. Dieses ist so implementiert, dass bei einer Anfrage mit passendem Match im Cache, dieser Match zurückgeliefert wird. Andernfalls wird die Anfrage einfach “durchgeschleift”.

Um einen Service Worker zu verwenden, genügen wenige Zeilen Code in unserer Webanwendung, siehe hierzu Listing 3. Gemäß dem Motto, Funktionalität nur dann anzubieten, wenn der Browser das auch unterstützt (also Progressive Enhancement), wird der Service Worker nur bei Vorhandensein der entsprechenden Schnittstellen geladen.

<LISTING 3> 

if ('serviceWorker' in navigator) { 

  window.addEventListener('load', function() { 

    navigator.serviceWorker.register('/service-worker.js'); 

  }); 

} 
Listing 3: app.js - Einbinden des Service Workers in unsere Anwendung

Was sich zunächst als offensichtlich darstellt, ist hier ein sehr wichtiger Punkt: Während der Entwicklung der Anwendung muss stets an den “Progressive”-Ansatz gedacht werden. Die Webanwendung soll auf möglichst vielen Geräten zumindest die Grundfunktionalität bieten. Also sollte bei Verwendung jeder moderneren Schnittstelle geprüft werden, ob diese überhaupt vorhanden ist. Sinnvolle Alternativen sollten gut überlegt werden. Meistens ist es auch zielführend, mit sogenannten Polyfills zu arbeiten, die bestimmte Funktionalitäten nachbilden können. Ziel ist es jedoch, möglichst viele Browser und Geräte zumindest für die Grundfunktionalitäten zu unterstützen.

Web-App-Manifest

Die nächste Zutat für eine PWA ist ein Web-App-Manifest. Dies ist eine Datei im JSON Format, die deklarativ einige Metainformationen der Anwendung beschreibt.

In Listing 4 ist ein Ausschnitt der Manifest-Datei für eine PWA dargestellt.

<LISTING 4> 

{ 

  "name": "PWA Notes Example", 

  "short_name": "PWA-Notes", 

  "theme_color": "#2196f3", 

  "background_color": "#2196f3", 

  "display": "standalone", 

  "scope": "/", 

  "icons": [ 

    { 

      "src": "/android-chrome-192x192.png", 

      "sizes": "192x192", 

      "type": "image/png" 

    }, 

    { 

      "src": "/android-chrome-512x512.png", 

      "sizes": "512x512", 

      "type": "image/png" 

    } 

  ] 

} 
Listing 4: manifest.json - Die Web-App-Manifest-Datei beschreibt Metainformationen der Webanwendung

In dieser Datei wird ein Name für die Anwendung vergeben (“name” und “short_name”), der dann später auch als Text unter dem App-Icon im Homescreen angezeigt wird. Es besteht die Möglichkeit, Farben und Icons anzugeben ("theme_color”, “background_color”, “icons”), die u.a. beim Laden der Webanwendung angezeigt werden. Und es kann angegeben werden, wie die App gestartet werden soll (“display”), also z.B. im Vollbildmodus oder mit einem Browserrahmen. Mit “scope” wird angegeben, für welchen Pfad der Webseite, dieses Manifest gelten soll. Dies ist hilfreich, wenn sich die Inhalte der PWA nicht auf “/”, sondern in einem Unterordner befinden.

Eingebunden wird die Manifest-Datei über die index.html im Header Bereich, siehe Listing 5. 

<LISTING 5> 

<!DOCTYPE html> 

<html> 

<head> 

  <title>PWA Notes Example</title> 

  <link rel="stylesheet" href="./stylesheets/style.css"> 

  <link rel="manifest" href="/manifest.json"> 

</head> 

<body> 

  <div>Hier kommt der Inhalt</div> 

  <script src="./javascripts/app.js"></script> 

</body> 

</html> 
Listing 5: index.html - Grundgerüst der Webanwendung um die Einbindung der Web-App-Manifest-Datei erweitert

Wenn diese Datei vorhanden und eingebunden ist, Icons bereitgestellt werden, ein Service Worker registriert ist und die Seite mit HTTPS geladen wird, bietet der Browser (manchmal erst bei mehrfacher Verwendung der Anwendung) an, die Webseite zum “Homescreen” hinzuzufügen. Die Webseite erscheint dann als Link neben den anderen installierten Anwendungen und kann von dort direkt aufgerufen werden. Momentan wird das allerdings nur von Chrome auf mobilen Geräten und Desktop angeboten.

Der Schritt, dass die Anwendung “installiert” werden kann und direkt über das gewohnte App-Menü aufgerufen werden kann, ist ein kleiner Schritt mit großer Wirkung. Es erleichtert den Zugang zur Anwendung sowohl beim Herunterladen (Link genügt) als auch beim Aufrufen (One-Click). Allein diese Tatsache beschert den Betreibern einer solchen Anwendung ungleich bessere Statistiken bei Zugriff und Verweildauer der Anwender auf der Seite. Das spiegelt sich nicht zuletzt auch in besseren Umsatzzahlen wider.

Die richtige Balance finden

Mit den vorherigen Maßnahmen ist die Webanwendung schon ein gutes Stück an die PWA-Philosophie rangekommen. Sie sind auch die einfachsten, um eine bestehende Webanwendung in eine PWA umzuwandeln. Es fehlt aber ein wichtiger Kern: PWAs sollen schnell sein. Und zwar nicht nur schnell während der Benutzung, sondern auch schnell beim Laden. Nachgewiesenermaßen verlieren Benutzer schon nach kurzer Zeit die Lust, wenn sie auf eine Anwendung einige Sekunden warten müssen, bevor sie mit ihr arbeiten können. Genau das soll bei echten PWAs nicht passieren. Abhilfe schafft hier ein Architektur-Muster, das sich “Application Shell” nennt.

Der äußere Rahmen der Anwendung und falls erforderlich einige wenige Grundfunktionen werden unter Verwendung des Service Workers gecached. Dies führt dazu, dass sich die Anwendung quasi instant anzeigt, wenn sie beim nächsten Mal geladen wird, selbst wenn das Gerät eine schlechte Verbindung hat oder gar offline ist. Wichtig ist, dass diese “Hülle” fast ausschließlich statisch ausgeliefert und so wenig wie möglich dynamisch per JavaScript aufgebaut wird. Denn das dynamische Aufbauen des Seiteninhalts kostet Zeit. Dynamischer Inhalt wird dann erst sukzessive nachgeladen.

Zu entscheiden, was Teil der “Application Shell” ist und was wann in welcher Reihenfolge nachgeladen wird, muss gut überlegt sein und ist bei genauerem Hinsehen nicht trivial. Entscheidungen, die hier getroffen werden, haben enormen Einfluss auf die weitere Entwicklung. Was hier mit einfließt ist die Frage, welcher Teil der Anwendung serverseitig (“Server Side Rendering”, SSR) und welcher Teil dynamisch im Browser erstellt wird (“Client Side Rendering”, CSR).

Im Gegensatz zu konventionellen Webanwendungen, bei der eine Art zu Rendern vorherrscht, findet man in PWAs meist eine Mischform. Die hohe Kunst ist es, die perfekte Balance zwischen SSR und CSR zu erhalten. Das gelingt realistisch eigentlich nur schrittweise mit Messen und Verbessern. Die Entwickler von Twitter haben ihre Bemühungen, die Balance zu finden in einem Blog-Artikel festgehalten.

Das Rad nicht neu erfinden

Genauer betrachtet sind die Aufgaben, die ein Service Worker zu erledigen hat, oft sehr ähnlich, daher gibt es sowohl die Möglichkeit über verschiedene Frameworks einen Service Worker generieren zu lassen, oder bestehende Bibliotheken für die Implementierung zu verwenden. Eine nützliche Bibliothek für das Cachen von Ressourcen ist Workbox, siehe Listing 6.

<LISTING 6> 

// Import der Workbox Bibliothek 

importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js'); 

 

// Route registrieren, die js und css Dateien cached 

workbox.routing.registerRoute( 
  /\.(?:js|css)$/, 
  new workbox.strategies.StaleWhileRevalidate({ 
    cacheName: 'static-resources', 
  }) 
); 

 

// Route registrieren, die Bilder cached (Mit Verfallsdatum) 

workbox.routing.registerRoute( 
  /\.(?:png|gif|jpg|jpeg|webp|svg)$/, 
  new workbox.strategies.CacheFirst({ 
    cacheName: 'images', 
    plugins: [ 
      new workbox.expiration.Plugin({ 
        maxEntries: 60, // Maximal 60 Bilder 
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Tage 
      }), 
    ], 
  }) 
); 
Listing 6: service-worker.js - Service Worker mit Workbox-Beispielen. Quelle: https://developers.google.com/web/tools/workbox/guides/common-recipes

Mit Workbox können verschiedene sogenannte Routen (das sind letztendlich reguläre Ausdrücke, die auf aufgerufene URLs matchen) registriert und einem Handler übergeben werden. Workbox bringt dabei schon einige eigene Handler mit, wie in unserem Beispiel StaleWhileRevalidate und CacheFirst. 

StaleWhileRevalidate fragt zuerst beim Cache, ob eine passende Ressource enthalten ist und liefert diese zurück. Gleichzeitig wird aber die Ressource im Hintergrund beim Server angefragt und der Cache aktualisiert. Beim nächsten Aufruf wird dann die aktualisierte Ressource verwendet.

CacheFirst fragt ebenfalls zunächst beim Cache an. Falls die Ressource nicht gefunden wird, wird sie über das Netzwerk geladen. Es findet hier keine Aktualisierung der Caches im Hintergrund statt. Diese Strategie bietet sich also nur an, wenn sich eine Ressource nicht mehr verändert, oder, wie hier im Beispiel, ein Verfallsdatum angegeben wird.

Dieses Beispiel soll hier nur als Einstieg dienen, denn Workbox bietet noch einige Funktionen mehr an, die in der Dokumentation nachgelesen werden können.

Von Checklisten und Leuchttürmen

Weitere Hilfen für die Entwicklung von PWAs sind Checklisten und Lighthouse. Die Checklisten enthalten einige Punkte, die während der Entwicklung und Bereitstellung einer PWA zu beachten sind. Lighthouse ist eine in Google Chrome integrierte Anwendung, die eine Webanwendung auf unterschiedliche Kategorien wie Performance, Accessibility, Best Practices, SEO und eben auch PWA testet. Lighthouse kann auch per Webseite oder Kommandozeile (z.B. auch in einer Continuous Integration Pipeline) aufgerufen werden. Diese Listen sollten nicht dogmatisch, aber wo sinnvoll durchaus in die Entwicklung einfließen.

Fazit

Die Konzepte der Progressive Web Apps sind sicherlich äußerst sinnvolle Erweiterungen, die das Verwenden und Erleben von Web-Anwendungen für den Benutzer deutlich verbessern werden. Insbesondere unter Verwendung moderner Web APIs und Push Notification und der “Add-To-Homescreen"-Funktionalität, verschmelzen die Grenzen zu einer nativen Anwendung immer mehr. Aber wie immer gilt hier, dass gut überlegt sein sollte, wann welches Werkzeug am sinnvollsten einzusetzen ist. So sind PWAs nicht als Ersatz für native Anwendungen gedacht, die nach wie vor ihre Daseinsberechtigung haben, sondern eben als nächster Evolutionsschritt von Web-Anwendungen. Die Basisschritte zur PWA sind relativ einfach und die Browserunterstützung wird immer breiter. Daher lohnt es sich, auf diesen Zug aufzuspringen, denn allein schon die Überlegungen, die für eine PWA getätigt werden müssen, helfen, jede Web-Anwendung besser zu machen.

Diesen Artikel bewerten
 
 
 
 
 
 
 
3 Bewertungen (100 %)
Bewerten
 
 
 
 
 
 
1
5
5