Jag bygger en offline-first WordPress-PWA, del 3

Service worker med WorkBox

Det är nu som den här serien av blogginlägg börjar bli intressant på riktigt! Vi har värmt upp genom att ordna HTTPS lokalt och att skapa en manifest.json-fil. Men nu är mjukstarten över och det har blivit dags att kavla upp ärmarna och sätta tänderna i service workers, den viktigaste komponenten i en Progressive Web App.


Men vad är egentligen en service worker?

Precis som alltid i den här världen så verkar det inledningsvis vara något skrämmande och en totalt obegriplig term, men så visar det sig vara inte fullt så komplicerat.

En service worker är en JavaScript-fil som körs i bakgrunden och kan fortsätta göra saker även när besökaren lämnat sajten.

Det låter ju förvisso lite skrämmande, särskilt när vi redan har Google och Facebook som stalkers dygnet runt. Men ett användningsområde av mer välvillig natur är möjligheten att hålla webbappen uppdaterad.
Om din PWA är en att göra-lista så kan service workern hålla den i synk med databasen utan att användaren öppnar den i webbläsaren.

Ett annat exempel på vad en service worker möjliggör är att din PWA kan skicka push-notiser till användaren.

Men vad som kanske är viktigast av allt är att alla nätverksanrop- och svar går genom service workern.

Detta gör att du med JavaScript kan manipulera dessa. Om du exempelvis vill att varje gång webbläsaren skickar en förfrågan om att ladda ned en fil med filändelsen .jpg så kan service workern svara med funny.gif istället. I detta fall skickas inte ens denna förfrågan till servern utan den stannar vid service workern.

Ett mer praktiskt exempel, som jag snart kommer att gå in mer på, är att en service worker kan hantera en cache som är unik för din PWA. I denna kan exempelvis sajtens style.css lagras. 
Eftersom alla nätverksanrop går genom service workern så kan den först kontrollera om style.css finns lagrad. I så fall levererar service workern filen direkt från sajtens cache istället för att ladda ner den från servern.

Med andra ord kan en service worker förkorta laddningstiderna markant genom att reducera antalet nätverksanrop.


Detta om detta. Nästa steg i mitt lilla projekt är alltså att skapa en service worker som cache:ar (swengelska går inte att undvika) alla assets.

I ett tidigare blogginlägg har jag skrivit om när jag testade WorkBox. Det är ett bibliotek från Google som tar hand om bland annat cache:ningen åt dig på ett enkelt sätt.

Men i utbildningssyfte var min första tanke att skriva min service worker från grunden. serviceworke.rs är en bra resurs som tillhandahåller många av de vanligaste “recepten” på en service worker.

Funktionen för att spara style.css i cache:n skulle i så fall ha sett ut ungefär så här:

Som synes behöver man ange den exakta sökvägen till style.css , vilket förvisso inte är några problem än så länge. Dock brukar jag använda mig av det utmärkta WordPress-pluginet Autoptimize för att minifiera mina CSS- och JavaScript-filer. Detta innebär att jag inte kommer veta exakt vad den optimerade CSS-filen kommer att heta.

Exempel:

./wp-content/cache/autoptimize/css/autoptimize_537ce15b533c7419959db13d757c910e.css

Istället för exakta filnamn hade ett regular expression löst detta problem, och det kan man faktiskt använda med WorkBox.

workbox.routing.registerRoute(
new RegExp('.*\.js'),
workbox.strategies.networkFirst()
);

Av denna anledning valde jag alltså att använda WorkBox även för detta projekt. Med det beslutet taget så är det bara att börja jobba!

Skapa och installera service workern

Första steget är att registrera service workern och detta görs i den “vanliga” JavaScript-filen. app.js i det här fallet.

serviceworker.js skapade jag i projektets root, alltså tillsammans med alla WordPress-filer och manifest.json som jag skrev om förra veckan.

Cache:a CSS- och JavaScript-filer med WorkBox

WorkBox gör livet väldigt lätt att leva, åtminstone i det här fallet. Dessa enkla rader är allt som krävs för att uppfylla mitt mål:

Först importeras själva WorkBox-scriptet och sedan registreras en “route.” Det första argumentet är ett regular expression som alltså kommer fånga alla .js och .css filer. Eventuellt kommer jag justera det så att bara filer från /cache/autoptimize/ fångas upp, men det tar vi vid behov.

Andra argumentet är strategin som man väljer att applicera på dessa filer. Vad är en strategi i det här fallet? Det är på vilket sätt man väljer att hantera nätverksanropet.
I det här fallet valde jag strategin cacheFirst() vilket innebär att i första hand ska service workern svara på nätverksanropet med att leverera filen från cachen. Detta förutsätter ju givetvis att filen finns där, annars kommer service workern att ladda ner den från servern som vanligt.

Det finns även strategier som networkOnly() som gör det motsatta. Cachen ignoreras och filen laddas ner över nätverket från servern. Men i det här fallet vill jag minimera laddningstiderna och göra bloggen så pålitlig som möjligt, även på ostabila uppkopplingar, så därför satsar jag på cacheFirst()

Strategin tar emot ett objekt med inställningar. Den enda inställningen som är relevant för tillfället är namnet på cachen som skapas. Bloggen ska föreställa en dagbok från min Dubai-resa, så därav namnet dubai-travel-diary

Funkar det?

Vad man bör komma ihåg är första gången man besöker en PWA så är den som vilken sajt som helst. Det är då service workern installeras, men det är först vid andra besöket som den ger effekt.

Öppnar man upp Developer tools och klickar på fliken Application och sedan Service Workers i vänstermenyn så kan man mycket riktigt se att den är aktiv.

Men klickar man på Cache Storage så är det tomt! Hade man inte förväntat sig att se dubai-travel-diary cachen här?

Nej, inte vid första besöket, men andra gången så dyker filerna upp där. (Notera att det inte räcker med att ladda om sidan utan man måste öppna sajten i en ny flik.)

Växlar man över till fliken Network så ser man att filerna inte längre laddas över nätverket. Istället hämtas de från den lokala cachen tack vare service workern.

Utan service workern ser det ut så här:

Lighthouse-rapporten

I och med detta har PWA-poängen i Lighthouse-rapporten ökat från 73 till 82!

Då återstår följande punkter:

  • Does not respond with a 200 when offline
  • User will not be prompted to install the web app

Båda dessa kan faktiskt åtgärdas i ett enda slag.

Cache:a startsidan

Genom att registrera ytterligare en “route” som gäller startsidan, alltså sökvägen / , så sparas också den i cachen.

Åtminstone till att börja med så använder jag cacheFirst() strategin, men eftersom det är en blogg så är det kanske rimligare att använda exempelvis networkFirst(). Om det har kommit ett nytt blogginlägg så vill man förstås att besökaren ser det direkt.

Men det är nu jag ska börja experimentera på riktigt. Kanske kan backgroundSync användas på något vis? Kanske ska jag bygga om temat så att det följer App Shell-modellen och blogginläggen laddas in i en statisk mall med JavaScript?

100% PWA

Möjligheterna är många och dessa ska utforskas i nästa del i serien! Glädjande är att bloggen redan nu har 100 av 100 möjliga PWA-poäng!