PWA und manifest.json in Symfony - RevisionTen/cms GitHub Wiki

Dieser Artikel erklärt wie man eine manifest.json und einen Progressive-Web-App Service-Worker zum Symfony Projekt hinzufügt.

Als zusätzliche Dependency wird lediglich das Webpack Offline-Plugin benötigt. Die Dokumentation ist hier zu finden: https://github.com/NekR/offline-plugin Auf diese Dependency kann auch verzichtet werden wenn nur eine manifest.json ergänzt werden soll.

Schritt für Schritt Anleitung

Als erstes muss die Dev-Dependency für das offline-plugin installiert werden:

yarn add offline-plugin --dev

Das Plugin muss in der webpack.config.js required werden:

var OfflinePlugin = require('offline-plugin');

Danach sollte ein zufälliger String generiert werden den wir als Cache-Buster verwenden (anstelle des automatisch generierten).

var imageCacheBuster = (Math.random() + 1).toString(36).substring(7);

Als nächstes legen wir den Inhalt unseres Manifest fest:

var manifestOptions = {
    name: 'Zingst – Mehr als Meer!', // Erscheint unter anderem im App-Startbildschirm auf Android.
    short_name: 'Zingst', // App-Titel z.b. im Android App-Drawer.
    description: 'Zingst – Urlaub an der Ostsee! Zingst ist Mehr als Meer!',

    // Einfärbungen der Browserleiste.
    theme_color: '#ffffff',
    background_color : '#ffffff' ,

    display: 'minimal-ui', // Browser stellt im App-Modus nur einen Reload und Vor/Zurückbutton bereit.
    prefer_related_applications: false, // Legt fest das keine alternativ Apps zur Installation angeboten werden sollen.
    start_url: '/', // Diese URL wird beim öffnen der App geladen. Könnte z.b. auch /?pwa=true sein falls man es tracken möchte.

    icons: [{
        src: '/build/images/icon-192.'+imageCacheBuster+'.png',
        sizes: '192x192',
        type: 'image/png'
    }, {
        src: '/build/images/icon-512.'+imageCacheBuster+'.png',
        sizes: '512x512',
        type: 'image/png'
    }, {
        src: '/build/images/maskable_icon.'+imageCacheBuster+'.png',
        sizes: '682x682', // Gibt keine Vorgabewert, sollte aber nicht zu klein sein vermutlich.
        type: 'image/png',
        purpose: 'any maskable' // Leg fest das das Icon maskierbar ist, also von Android in den diversen Stilen zugeschnitten werden darf. @see https://maskable.app/editor
    }]
};

Das Manifest fügen wir wie folgt zur Encore Konfiguration hinzu:

Encore
    [...]

    // Ausgabedateinamen konfigurieren.
    // Dieser Schritt ist notwendig weil wir für die Bilder unseren eigenen Cachebuster verwenden müssen.
    .configureFilenames({
        js: '[name].[contenthash].js',
        css: '[name].[contenthash].css',
        images: 'images/[name].'+imageCacheBuster+'.[ext]',
        fonts: 'fonts/[name].[hash:8].[ext]'
    })

    // Manifest hinzufügen.
    .configureManifestPlugin((options) => {
        options.seed = manifestOptions;
    })

    [...]
;

const webpackConfig = Encore.getWebpackConfig();

[...]

webpackConfig.plugins.push(new OfflinePlugin({
    strategy: 'changed',
    responseStrategy: 'cache-first', // Bedeutet das der Nutzer die Daten erst aus den Cache laden soll und als Fallback aus dem Web.
    caches: {
        main: [
            '*.css',
            '*.js',
            'fonts/*',
            'images/*'
        ]
    },
    ServiceWorker: {
        output: '../sw.js' // Ausgabe-Pfad des Service-Worker, der Service Worker muss direkt im "/public"-Verzeichnis liegen, er darf sich nicht in einem Unterordner befinden.
    }
}));

module.exports = webpackConfig;

In unserer app.js müssen wir noch die verwendeten Icons requiren und den Service-Worker einbinden:

assets/js/app.js bzw. assets/ts/app.ts

// Service Worker.
require('offline-plugin/runtime').install();

// Icons für das Manifest.
require('../favicon/icon-192.png');
require('../favicon/icon-512.png');
require('../favicon/maskable_icon.png');

Da unsere Service-Worker Datei im Webroot liegt müssen wir sie noch in den Deployment-Vorgang aufnehmen:

config/prod/deploy.php

public function beforeFinishingDeploy()
{
    [...]

    // Copy asset build to server.
    $this->runLocal(sprintf('scp -r ./public/build %s:%s/current/public/build', $ssh, $deployDir));
    // Copy service worker.
    $this->runLocal(sprintf('scp -r ./public/sw.js %s:%s/current/public/sw.js', $ssh, $deployDir));

    [...]
}

Schließlich müssen wir in unserem Template die Manifest-Datei noch im <head> einbinden:

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

Ein vollständiges Beispiel der umgesetzten Maßnahmen ist Zingst (in Github auffindbar).

Was bewirkt der Service-Worker und das Manifest?

Das Manifest schreibt vor wie die Web-App dargestellt werden soll. Der Service-Worker bestimmt welche Dateien die App vorhalten soll. In unserem Fall speichert der Benutzer die Javascript und CSS Dateien dadurch im App-Cache.

Ob alle PWA-Vorgaben korrekt umgesetzt wurden kann im Google Lighthouse-Test überprüft werden. Die App sollte dort als "Installable" angezeigt werden. Offline-Versionen müssen nicht zwingend angeboten werden um "installable" zu sein.

Wenn die App als "installable" gilt wird sie im mobile Chrome zur Installation angeboten und kann dann als App abgelegt werden. Im Desktop-Chrome ist dies auch möglich. In der Adressleiste erscheint dann ein Icon zum installieren.

⚠️ **GitHub.com Fallback** ⚠️