Vytvoření progresivní webové aplikace (PWA)

8.11.2020
Progresivní webové aplikace (PWA) je název pro technologii, která umožňuje uživateli používat webovou stránku v offline módu nebo si jí nainstalovat jako aplikaci do mobilu. Technologie má potenciál postupně nahrazovat nativní mobilní aplikace a UWP aplikace. Je aktivně vyvíjená společnostmi Google a Microsoft. Apple technologii také postupně implementuje. PWA významně zjednodušuje vývoj, distribuci a aktualizaci mobilních aplikací a pravděpodobně se s ní budeme setkávat čím dál častěji.

Slovníček

Nativní aplikace Aplikace naprogramováná pro konkrétní platformu, například Android nebo iOS. Nativní aplikace je rychlejší oproti webové a poskytuje lepší přístup k funkcím v telefonu. Nevýhodou je potřeba aplikaci registrovat do Google Play nebo App Store a poté aplikaci pravidelně aktualizovat.
UWP Universální Windows Platforma je technologie, kdy vývojář naprogramuje aplikaci pouze jednou a poté jí může spustit na libovolné Windows 10 platformě. Například Windows 10, Windows 10 Mobile nebo Xboxu One (Windows 10 Core).

Obsah

Minimální požadavky na PWA

Aby aplikace mohla být rozpoznána jako progresivní, musí implementovat následující

  • HTTPS
  • Manifest
  • Service worker

HTTPS

Podpora HTTPS je záležitostí především webového serveru, na localhost se pravidlo nevztahuje.

Manifest

Soubor s příponou .json nebo .webmanifest, který přilinkujeme na hlavní stránku html tagem. Jedná se o konfigurační soubor, který se použije v případě, kdy uživatel aplikaci nainstaluje do počítače nebo mobilního telefonu.

<link rel="manifest" href="/manifest.json">
{
    "name": "PWA Proof of concept",
    "short_name": "PWA PoC",
    "icons":[{
        "src": "/images/icons/web_hi_res_512.png",
        "sizes": "512x512",
        "type": "image/png",
        "purpose": "any maskable"
    }],
    "start_url": "/index.html",
    "display": "standalone",
    "orientation": "portrait",
    "theme_color": "#ffffff",
    "background_color":"#ffffff"
}

V konfiguraci je nastaveno několik důležitých věcí

  • Jméno aplikace, které se použije při spuštění.
  • Ikonku, kterou mobilní telefon použije při uložení aplikace do telefonu. iPhone má definici v apple-touch-icon html atributu.
    • 512x512px je největší možná velikost a zařízení si jí přizpůsobí dle potřeby.
    • Pro vygenerování ikonky je možné použít Launcher icon generator.
  • Start_url definuje adresu, která se použije pro otevření nainstalované aplikace.
Popis všech atributů je možné najít zde

Service worker

Alfou a omegou PWA je service worker, javascriptový soubor, který je součástí aplikace a běží na pozadí, nezávisle na webovém prohlížeči. Funguje na principu web workera a hlavním úkolem je synchronizovat offline a online verzi aplikace, jinými slovy spravovat cache úložiště, ale má i jiné funkce. Základní strategií (ale ne jedinou a různé strategie je možné kombinovat) jak nastavit service workera je takzvaná cache-first strategie, kdy se uživateli při první návštěvě nebo instalaci aplikace uloží do cache paměti všechny klíčové soubory, u kterých je předpoklad, že se nebudou často měnit. Jedná se o statické assety jako obrázky, css soubory, js scripty, ale i html stránka, která se má použít při offline režimu (start_url).

Cache-first strategie

  • Aplikace si požádá o nějaký soubor
  • Service worker se podívá do cache úložiště
    • Soubor nalezen
    • Soubor nenalezen, přepošle požadavek do sítě
  • Service worker předá aplikaci požadovaný soubor

Důležité eventy
Install Spustí se při první návštěvě stránky nebo její instalaci. Využijeme pro uložení klíčových souborů do cache úložiště. Poté už se nikdy nespustí.
Active Spustí se v momentě, kdy je service worker připravený. Využijeme pro aktualizaci cache úložiště, pokud existuje nová verze.
Fetch Událost se spustí v momentě, kdy aplikace pošle nějaký http požadavek. Service worker požadavek převezme a podívá se jestli požadovaný soubor není uložený v cache úložišti. Pokud ano, soubor se vezme z cache paměti, pokud ne tak se požadavek pošle dál do sítě.

Konfigurace service workera

Před tím, než se pustíme do konfigurace, musíme service workera zaregistrovat. Registraci nastavíme v souboru index.html. Starší verze prohlížečů service workera nepodporují, proto registraci obalíme podmínkou. Service worker je javascriptový soubor, v našem případě bude uložený v rootu webu pod jménem serviceworker.js.
<html>
  <head>
      <link rel="manifest" href="/manifest.json">
      <!-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color -->
      <meta name="theme-color" content="#FFFFFF">
      <!-- https://www.w3schools.com/css/css_rwd_viewport.asp -->
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <!-- https://web.dev/apple-touch-icon/ -->
      <link rel="apple-touch-icon" href="/images/icons/apple_touch_icon_192.png">
  </head>
  <body>
      PWA PoC v1
  </body>
  <script>
      if ('serviceWorker' in navigator){
          window.addEventListener('load', () => {
              navigator.serviceWorker.register('/serviceworker.js')
          })
      }
  </script>
</html>
serviceworker.js obsahuje implementaci eventů install, active a fetch.
const cacheName = 'cache-v1';

const resourcesToCache = [
    '/',
    'index.html',
    'styles/main.css',
    'images/space1.jpg',
    'images/space2.jpg',
    'images/space3.jpg'
]

// Install event triggers only once, 
// when the user first visit the page.
// We use the install event to pre-cache the core static assets 
// for a first-cache strategy.
// In the worker, self is the worker.
// Everywhere else it's the current global object.
self.addEventListener('install', event => {
    // Wait for the last promise to complete
    // https://stackoverflow.com/a/37906330/2333663
    event.waitUntil(
        caches.open(cacheName)
            // Cache returns a promise. When we call 'then'
            // on a promise, the code will be executed after
            // the promise is done.
            .then(cache => {
                // Load resources from the web server
                // and puts them into the cache.
                return cache.addAll(resourcesToCache);
            })
    );
});

// When serviceworker is ready, the activate event is called.
// The activate event is used to update pre-cashed resources (core cache), when there is a new version of it.
// https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Updating_your_service_worker
self.addEventListener('activate', (event) => {
    var cacheKeepList = [cacheName];
    event.waitUntil(
        caches
            .keys()
            .then(keys => Promise.all(keys.map((key) => {
                if (!cacheKeepList.includes(key)) {
                    return caches.delete(key);
                  }
            })))
    );
});

// For static assets pre-cached in the install event,
// we will use cache-first strategy.
// Strategy looks inside the cache first and network is a fallback.
// This improve performance and enable offline usage of the assets.
// Lighthouse 'Current page responds with a 200 when offline' will be green,
// if the current page is cached.
self.addEventListener('fetch', event => {
    // Look into cache for the requesting asset.
    event.respondWith(
        caches
          .match(event.request)
          // return cached asset if found,
          // otherwise load it from the web server.
          .then(cached => cached || fetch(event.request))
    );
});

Odinstalace

Pokud bychom chtěli service workera odebrat, nesmíme ho zapomenout odinstalovat. Stávajícímu service workeru dáme vědět, že si má stáhnout novou verzi souboru index.html, tedy souboru, kde probíhá registrace. To provedeme zvýšením verze cache úložiště, respektive přejmenováním cacheName konstanty v serviceworker.js souboru. Nový index.html soubor pak bude obsahovat odinstalaci místo registrace.

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.getRegistrations()
      .then(function(registrations) {
          for(let registration of registrations) {
              registration.unregister();
          }
      });
}

Spuštění aplikace

Pro nasazení aplikace na localhost využijeme rozšíření Web Server For Chrome, které nám umožní aplikaci spustit.

Struktura aplikace je následovná (ale libovolně měnitelná)

  • Pwa
    • images
      • space1.png
      • space2.png
      • space3.png
      • icons
        • apple_touch_icon_192.png
        • web_hi_res_512.png
    • styles
      • main.css
    • favicon.ico
    • index.html
    • manifest.json
    • serviceworker.json

Lighthouse audit

Prohlížeč Chrome má zabudovaný auditní nástroj pro PWA aplikace, který spustíme následovně Otevřeme Chrome > Otevřeme Developer tools (F12) > Záložka Lighthouse > Generate report Výstupem je přehledná obrazovka, která nám zvýrazní případné chyby

Řešení problémů

Pro řešení nejrůznějších problémů nebo jen vyzkoušení funkčnosti můžeme použít záložku Application v prohlížeči Chrome/Edge

Otevřeme Chrome/Edge > Otevřeme Developer tools (F12) > Záložka Application

Můžeme například překontrolovat s jakými hodnotami se nám načetl manifest, jestli běží service worker, jaká verze cache úložiště se používá a jaké obsahuje soubory a mnoho dalšího. Aplikaci také můžeme přepnout do offline režimu.

Výsledek

Výsledkem je webová aplikace, která funguje i v offline režimu (alespoň do té míry, do které má potřebné soubory v cache úložišti). Můžeme jí také nainstalovat (na desktopu do chrome://apps/ nebo edge://apps/, na mobilu jako aplikaci). Na mém Samsung Galaxy A40 v dark módu se Samsung Internet prohlížečem, který používá Chromium engine vypadá postup následovně.

Otevřeme webovou stránku a klikneme na ikonku instalace , která se nám zobrazí, protože prohlížeč rozpoznal PWA aplikaci
Potvrdíme instalaci
Ikonku aplikace najdeme v přehledu aplikací
Po spuštění aplikace nám problikne ikonka s názvem aplikace
Výsledkem je webová stránka bez adresního řádku prohlížeče, kterou je možné spustit offline. Veškeré odkazování ale musíme vyřešit v aplikaci.

Odkazy