Progresivní webové aplikace, díl II. — Jak zlepšit performance metriky a fungovat i offline?
I s elementárními znalostmi JavaScriptu můžete během chvíle vylepšit svůj web a poskytnout uživatelům prožitek, který znají z nativních aplikací. V pokračování minisérie o PWA se dozvíte, jak umožnit instalaci aplikace na domovskou obrazovku nebo jak začít web vykreslovat výrazně rychleji.
V minulém dílu jste se mohli dozvědět, co jsou progresivní webové aplikace vůbec zač a kdy je vhodné o nich začít uvažovat. Pokud je teď třípísmenná zkratka PWA ve vašem hledáčku a chcete se dozvědět něco k samotné implementaci, tento a následující díl minisérie o PWA vám poskytnou detailnější vhled do problematiky.
V následujících odstavcích si ukážeme, jak docílit:
- stažení webové stránky na zařízení uživatele,
- rychlejšího zobrazení webu po prvním načtení,
- nezávislosti webové stránky na internetovém připojení.
Co musím udělat pro to, abych sestavil PWA?
Na stránkách od Google Developers můžete najít velmi podrobný checklist se všemi požadavky na plnohodnotnou PWA. U každého bodu dostanete odpověď na klíčové otázky — co, proč a jak. Neboli: Co to je? Proč by mě to mělo zajímat? Jak na implementaci?
Protože není cílem a především ani možné detailně sepsat splnění všech kritérií na PWA v tomto článku, budeme předpokládat, že váš web splňuje následující:
- Web servírujete ze zabezpečené domény přes HTTPS.
V případě, že tomu tak není, doporučuji si přečíst článek o nasazování webu na HTTPS. - Stránky jsou plně responzivní a fungují i napříč prohlížeči.
Pokud vyvíjíte s přístupem mobile first, nemusíte se bát, že byste kritérium responzivity nesplňovali. Pokud si nejste jistí, použijte Lighthouse audit nebo Mobile Friendly Test, abyste zjistili, zda se vaše stránka adekvátně vykresluje i na mobilních zařízeních nebo tabletech. Nebo můžete váš web proklikat prostřednictvím Chrome pluginu Responsive viewer. - Každá stránka je odkazovatelná přes URL a obsahuje metadata pro sociální sítě.
Ujistěte se, že každá stránka má svůj unikátní odkaz a v hlavičce jsou dostupné informace pro sociální sítě jako Twitter, Facebook nebo Instagram. Facebook i Twitter poskytují zdarma debugovací rozhraní na otestování vzhledu odkazu stránky na své sociální síti. Díky tomu bude aplikace snáze dohledatelná v prohlížečích.
Pojďme se nyní podívat, jak umožnit uživatelům stažení webové aplikace na zařízení (zatím nehledě na specifika některých OS).
Instalace webové stránky na zařízení
Pokud chceme učinit naši aplikaci instalovatelnou z webového prohlížeče či nativní platformy Google Play, musí mít definovaný web app manifest. Chrome navíc vyžaduje zaregistrovaný service worker, ale o něm se budeme bavit až v další kapitole.
Web App Manifest je JSON soubor, který poskytuje všechny potřebné informace o webové aplikaci, aby bylo možné ji stáhnout a chovala se tak jako všechny ostatní nativní aplikace v telefonu. Měl by vždy obsahovat minimálně název aplikace, ikony, které se mohou používat, a počáteční URL. Pokud chceme, aby správně fungovalo přidání na domovskou stránku (někdy označované jako A2HS – Add to Home Screen), musíme navíc definovat barvu pozadí a režim zobrazení aplikace.
Pro lepší představu, zde je ukázka jednoho takového manifestu a napojení na web.
// manifest.webmanifest nebo manifest.json
{
"background_color": "#fff",
"description": "První český online magazín o webovém frontendu",
"display": "standalone",
"icons": [
{
"src": "/images/icons-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/images/icons-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"name": "Frontend Garden",
"short_name": "Frontend Garden",
"start_url": "/",
"theme_color": "#40bf4f"
}
<!-- index.html -->
<link rel="manifest" href="/manifest.webmanifest" />
👇🏻 Je zapotřebí mít na paměti následující.
Chrome doporučuje, aby byly vždy definovány minimálně 2 velikosti ikon, a to o rozměrech 192 𐄂 192 px a 512 𐄂 512 px. Ostatní náležitosti doporučuju ověřit na webu konsorcia pro standardy WWW.
Pokud chcete podporovat i iOS 🍎:
- Aby se aplikace otevírala v samostatném okně, musíme nastavit vlastnost
display
nastandalone
nebo vložit do hlavičky vindex.html
meta tag<meta name="apple-mobile-web-app-capable" content="yes">
. - Pokud se název aplikace nevejde do 12 znaků a chcete se vyhnout jeho zkrácení, nebo tomu, že se nezobrazí vůbec, musíme opět vložit do hlavičky tag s názvem aplikace, který chceme, aby se zobrazoval na domovské stránce pod ikonou:
<meta name="apple-mobile-web-app-title" content="Frontend Mag">
. - iOS nepoužívá ikony definované v manifestu. Proto nesmíte zapomenout na vygenerování favicon pro iOS, na které se taktéž musíte odkázat v hlavičce kořenového HTML souboru.
- Další užitečné triky pro iOS zařízení se můžete dočíst v článku Designing Native-Like Progressive Web Apps For iOS.
Chcete-li předejít špatnému ořezu ikon vlivem různých tvarů ikon mezi platformami, určitě stojí za zvážení maskovatelné ikony. Stačí pouze umístit do tzv. safe zone důležité oblasti ikony, které musí být zachovány. Velmi pěkný návod je například na stránkách web.dev.
🎉 Voilà! Aplikaci by nyní mělo být možné nainstalovat na domovskou obrazovku a pracovat s ní (téměř) jako s nativní aplikací. Stačí jen při načtení webu potvrdit v banneru přidání stránky na domovskou obrazovku a pro příště už netřeba zadávat URL stránky do prohlížeče, nebo hledat v záložkách.
Podpora Web App Manifestu
Pokud nahlédneme do Can I Use, tak bohužel zjistíme, že s podporou na desktopu to vyjma prohlížečů Edge a Chrome není žádná sláva (ale to v mnohých případech není klíčové). Až na Operu, tak mobilní prohlížeče A2HS podporují.
⚠️ S iOS Safari je to poněkud komplikovanější, jelikož nepodporuje událost beforeinstallprompt
, která nám zobrazí banner na instalaci/přidání aplikace, proto je třeba se proklikat přes Share Sheet. Apple v současnou chvíli neumožňuje A2HS ve webviews, kterými je například Chrome nebo Firefox.
Rychlejší zobrazení webu po prvním načtení
Aplikace nyní splňuje čtyři z výše zmíněných kritérií: je responzivní a zabezpečená (plynoucí z předpokladu), instalovatelná a částečně dohledatelná díky manifest souboru. Nyní bychom chtěli dosáhnout toho, aby byla plně dohledatelná, rychleji se načetl (téměř všechen) její obsah a aby fungovala i offline. K tomu nám dopomůže service worker.
Pro ty z vás, kteří se s tímto pojmem setkávají poprvé a nebo jim svět JavaScriptu není blízký, přiblížím význam a způsob fungování této technologie.
Service worker je speciálním typem web workeru, což je označení pro skript, který je spuštěn odděleně od hlavního vlákna pro vykreslování webové stránky. Stránku jako takovou neomezuje. Samotný service worker proto může odesílat push notifikace a synchronizovat data na pozadí, jelikož se jedná o programovatelnou proxy mezi stránkou a sítí, která umožňuje zachytit a ukládat síťové požadavky.
Je založený na promisách, což jsou programovací konstrukce, které představují hodnotu proměnné, která ještě není známá v době vykonání kódu. Vzhledem k tomu, že běží v odděleném vlákně, není možné z jeho instance přistupovat na prvky DOMu a pracovat tak s některými API jako XHR či cookies.
Jak tedy funguje instance service workeru? Jeho zapojení do chodu aplikace sestává ze tří fází:
- Registrace – do globálního objektu
navigator
se zaregistruje skript se service workerem (⚠️ tento skript musí být vždy samostatný soubor). - Instalace – inicializuje se chování offline režimu.
- Aktivace – service worker plně přebírá kontrolu nad webovou aplikací podle definovaného chování. Pokud dochází k aktualizaci service workeru, zbavujeme se předchozích verzí mezipaměti.
Při prvotním spuštění webové stránky se service worker zaregistruje do webového prohlížeče (za předpokladu, že prohlížeč podporuje tuto technologii, více v kapitole Podpora service workeru). Pokud registrace byla úspěšná, přichází proces instalace.
Instalace proběhne v případě, že service worker nebyl doposud pro web registrován, nebo byl registrován, ale došlo ke změně skriptu.
Poslední fází je aktivace. Ta nastane, pokud není žádný service worker aktivní, uživatel obnoví stránku, nebo se zavolá self.skipWaiting()
v instalační fázi.
Jakmile máme service worker aktivní a neprovádí žádné operace, může nabývat dvou stavů: terminated
, nebo fetching/messaging
. Doporučuji bedlivě pracovat s DevTools, kde se pod záložkou Application skrývá sekce Service Workers, ve které můžete sledovat stav service workeru, a nejen to.
Stručná rekapitulace k service workeru:
- Jedná se o programovatelnou proxy, pomocí níž můžeme řídit zpracování síťových požadavků na stránce.
- Umožňuje cachování dat a pomocí Cache API data zprostředkuje aplikaci v momentě, kdy prohlížeč pracuje bez připojení k internetové síti.
- Přijímá a zobrazuje push notifikace díky Push API a Nofications API.
Podpora Service Workeru
Opět platí, že – až na Operu – je podpora napříč prohlížeči poměrně slušná.
A nyní k samotné implementaci...
Krok č. 1 – Registrace service workeru
Nejprve ze všeho musíme zaregistrovat service worker do globálního objektu navigator
. Service worker registrujeme pouze v případě, že jej daný prohlížeč podporuje.
// register-sw.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('Service worker registered.');
});
.catch((error) => {
console.error('Oh, no. Service worker registration failed: ', error);
});
}
Pokud máme i vytvořený soubor sw.js
v kořenovém adresáři, měli bychom po načtení stránky vidět v konzoli zprávu, že se service worker podařilo zaregistrovat.
Pakliže se nám podařilo úspěšně zaregistrovat service worker, můžeme přejít k jeho instalaci a aktivaci.
Krok č. 2 – Instalace a aktivace service workeru
Ze všeho nejdřív chceme nadefinovat název cache a statické soubory, které tvoří tzv. application shell, a vše následně uložit do mezipaměti.
// sw.js
// 0. Údaje o mezipaměti a relativních cestách k app shell souborům.
const CACHE_VERSION = 'v1';
const CACHE_NAME = 'frontend-garden-' + CACHE_VERSION;
const assetsToCache = [
'/',
'/built/css/main.css',
'/built/js/main.js',
];
// 1. Inicializace mezipaměti.
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
console.log('Opened cache.');
return cache.addAll(assetsToCache);
})
);
});
// 2. Úklid po předchozích verzí service workeru.
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
cacheNames.map(function (cacheName) {
if (cacheName.indexOf(CACHE_VERSION) < 0) {
console.log(`Deleted cache ${cacheName}.`);
return caches.delete(cacheName);
}
});
})
);
});
Mnozí z vás si všimli, že události navěšujeme na objekt self
. Jedná se o klíčové slovo, které se používá k přístupu k web workeru.
Krok č. 3 – Zpracování požadavků a jejich ukládání do mezipaměti
Jak už bylo zmíněno, service worker umí zachytávat a ukládat jednotlivé požadavky mezi stránkou a sítí. Událost fetch
se spustí pokaždé, pokud se dotazujeme na nějaký zdroj, který spravuje service worker. Během této události řešíme, zdali chceme data ukládat a jak je zprostředkujeme stránce.
Jak zpracovávat požadavky na stránce
Existuje několik přístupů, jak můžeme zpracovat requesty na stránce:
Cache only/Network only
- Cache only používáme v momentě, kdy vracíme (téměř) neměnná data.
- Network only potřebujeme, pokud pracuje s non-GET requesty a potřebujeme pracovat s "živými daty".
- Většinou tento přístup nepotřebujeme používat. Lepší je používat variantu Network falling back to cache (viz dále).
Cache, falling back to network
- Vhodné, pokud usilujeme o přístup offline-first.
- Uložená data vracíme z cache, ostatní požadavky vracíme ze sítě.
Network falling back to cache
- Pro často se měnící data, jako jsou články, avatary, nebo verzované assety (CSS a JS).
- Nespornou výhodou je, že uživatel pracuje s nejaktuálnějšími daty. Pokud však není k dispozici stabilní připojení k síti, může to vést k frustrujícímu uživatelskému zážitku.
- Vhodnějším řešením může být přístup Cache then network.
Cache then network
- Vhodné pro často se měnící obsah stránky (seznam článků, časové osy na sociálních sítích, žebříčky), kde jsme schopni oželet poněkud rušivý moment při nahrazování části stránky aktuálními daty.
- Stránka pošle 2 dotazy – jeden do cache, druhý do sítě. Cílem je co nejrychleji zobrazit data z cache a jakmile se vrátí aktuální data ze sítě, stránka nahradí obsah právě získanými daty.
Existují i další přístupy, jako Cache & network race, Generic fallback, ServiceWorker-side templating. Perfektně zpracovaný článek včetně ukázek kódu je k dispozici na stránkách Google Developers od Jake Archibalda.
Nyní se vraťme k našemu kódu. Následující ukázka kódu za nás řeší následující:
- Víme, že naše stránka pracuje s daty třetích stran. Tato data chceme nejprve vracet z cache.
- Pokud nenajdeme shodu s dotazem v mezipaměti a jsme připojeni k síti, vracíme odpověď ze sítě a data si následně naklonujeme do cache (odpovídá přístupu Cache then network).
- Pokud nejsme připojeni k síti a chceme, aby naši uživatelé viděli něco lepšího než je skákající 🦖, vrátíme z cache vlastní offline stránku (viz níže).
// sw.js
...
// 0. URL a styly offline stránky
const OFFLINE_URL = '/offline/';
const assetsToCache = [
...
'/offline/,
'/built/css/offline.css'
];
...
// 1. Zpracování fetch požadavků na stránce
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE_NAME)
.then((cache) => {
return cache.match(event.request)
.then((response) => {
return response || fetch(event.request)
.then((response) => {
cache.put(event.request, response.clone());
});
})
.catch((error) => {
console.error(`${error}. Returning offline page.`);
return caches.match(OFFLINE_URL);
});
})
);
});
Pokud není uživatel připojen k internetu, s největší pravděpodobností bude zbytečné mít na offline stránce interní odkazy (pokud nepředpokládáte, že obsah bude načten v mezipaměti). Externí odkazy lze pro jistotu úplně vynechat.
Méně práce díky Workboxu
Naše stránka však nezpracovává jen pár požadavků na vlastní styly a skripty. Často potřebujeme získávat data ze třetích stran, jakou jsou písma z Google Fonts nebo Adobe Typekit, obrázky z CDN a další. Z jednotek dotazů na stránce se rázem může stát několik desítek. Velikost cache je omezená, i když přesné číslo se ve specifikaci horko těžko dozvíme (často se však kalkuluje s horní hranicí 50 MB). Kapacita totiž záleží na daném prohlížeči a zařízení.
Svědomitý a zodpovědný přístup při práci s mezipamětí je gró celé funkcionality. Neměli bychom zapomínat ani na maximální životnost dat a aplikovat různé cachovací přístupy podle jejich typu.
Tato a jiná další trápení při jejich implementaci za nás může vyřešit Workbox. Workbox je kolekce javascriptových knihoven pro vývoj PWA. Vývojářům ulehčuje práce se service workerem a Cache Storage APIs, čímž následně uživatelům aplikace zpříjemňuje prožitek při používání aplikace offline.
Pinterest, Tinder a i další organizace, které učinily ze svých webových aplikací PWA a zrealizovaly i řadu dalších vylepšení, se dočkaly ještě závratnějších změn. Pinterestu se podařilo markantně snížit časy pro první vykreslení stránky a interakci s ní. First Paint se jim podařilo zredukovat z 4,2 s na 1,8 s, tj. zlepšení o zhruba 60 %. Time to Interactive dokonce stáhli z neuvěřitelných 23 s na 5,6 s – tady se bavíme dokonce o 75% zlepšení.
Další případové studie na téma PWA a performance se můžete dočíst například na stránkách Google Developers nebo na Mediu, kde se tomuto tématu hojně věnuje Addy Osmani.
Související odkazy
- Progresivní webové aplikace — co to je a kdy má smysl se o to zajímat? (Frontend Garden)
- Web App Manifest (Google Developers)
- Service Workers (Google Developers)
- Designing Native-Like Progressive Web Apps For iOS (Medium)
- Caching Files with Service Worker (Google Developers)
- iPhone 11, iPadOS and iOS 13 for PWAs and web development (Medium)
Další články od autora:
Sdílejte
Líbí se vám článek? Podpořte jej sdílením!
Komentujte
Chcete k článku něco doplnit? Našli jste chybu? Napište e-mail.