PWA - Progressive WebApps

Unsere Konfiguratoren sind ja hauptsächlich JavaScript-Anwendungen, die sich aus einem Backend Daten und Assets besorgen. Da liegt es nahe, sich mit einer Technik zu befassen, die mit JavaScript-Frontends die nativen Apps ersetzen will.

Service Worker

PWAs?

Progressive Webapps sind Bündel aus HTML-Seiten, CSS, Assets und JavaScript-Programmen, die auf dem Gerät gecached werden und Eigenschaften haben, die sie in die Nähe von nativen Apps rücken:

Zukünftig sollen weitere APIs hinzukommen. Die perspektive ist, dass PWAs irgendwann native Apps ersetzen können.

Wie funktionieren PWAs im Prinzip?

Damit PWAa gecached und sinnvoll auf dem Homescreen installiert werden können, werden sie durch eine Manifest-Datei beschrieben. Der Browser ruft das Manifest ab und nutzt die Informationen, um die PWA komplett zu laden und zu installieren.

Der Schlüssel zu den Offline-Funktionen und den Push-Notifications sind Service-Worker. Dabei handelt es sich um JavaScript-Code, der asynchron ausgeführt wird ohne Verbindung zu der Webseite, die ihn geladen hat. Er hat dementsprechend keinen Zugriff zum DOM. Er wird auch dann noch ausgeführt, wenn die landende Seite verlassen oder Tab geschlossen wurde.

Alle Aufrufe über xhr und fetch werden durch einen dedizierten ServiceWorker geschleust. Er kann Requests und Ergebnisse cachen und so die offline-Fähigkeit herstellen. Da er auch dann läuft, wenn die Seite nicht mehr angezeigt wird, kann er auch Push-Notifications vom Server empfangen sie vom System darstellen lassen.

Wie funktionieren PWAs in der Praxis?

Das Manifest

In der Praxis wird die Manifest-Datei automatisch erzeugt. Zum Beispiel, indem man das Modul webpack-pwa-manifest als dev dependency nutzt und in der webpack.config.js:

        new WebpackPwaManifest({
            name: 'DevCafe-PWA',
            short_name: 'OCPWA',
            description: 'A example Progressive Web App!',
            background_color: '#ffffff',
            icons: [
              {
                src: path.resolve('src/assets/android-chrome-192x192.png'),
                sizes: [96, 128, 192, 256, 384, 512], // multiple sizes
                purpose: "any maskable"
              },
            ],
            ios: {
                "apple-touch-icon": path.resolve('src/assets/apple-touch-icon.png'),
            },
            theme_color: '#ffffff'
          }),

Im Großen und Ganzen kommt das übergebene JSON dann als Manifest heraus und wird im Head der HTML-Datei so eingebunden:

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

Ansonsten sieht die HTML-Seite genauso aus wie immer.

Der Service Worker

Der Service-Worker wird in der index.js eingebunden:

if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(registration => {
            console.log('SW registered: ', registration);
        }).catch(registrationError => {
            console.log('SW registration failed: ', registrationError);
        });
    });
}

Den Serice-Worker kann man natürlich selbst schreiben. Aber seine Arbeit (Anfragen cachen) ist eigentlich ziemlich generisch. Daher kann man ihn auch automatisch erzeugen lassen, z.B. mit workbox.

Das wird in der webpack.config einfach als Plugin eingetragen:

const WorkboxPlugin = require('workbox-webpack-plugin');

...
  plugins: [
    ...
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
    })
  ] 

Es erzeugt dann die service-worker.js, die oben im Beispiel oben eingebunden wird.

Die App

Die App selbst kann dann natürlich mit jedem beliebigen Framework geschrieben sein, zum Beispiel React.