Vorresti sviluppare una Progressive Web App (PWA) che migliori l’esperienza utente? Un Service Worker è ciò che fa per te!
In questo breve articolo vi parlerò del Service Worker, delle sue funzionalità principali e del suo ciclo di vita. Vi fornirò inoltre un semplice codice di esempio per l’installazione e l’attivazione.
Che cos’è un Service Worker
Un Service Worker è essenzialmente un file JavaScript che viene eseguito su un thread separato rispetto a quello principale del browser. Questo componente risulta essere molto utile in quanto è in grado d’intercettare richieste dalla rete, salvare/ritrovare dati nella cache e inviare notifiche push all’utente. È quindi un componente fondamentale da implementare qualora si volesse sviluppare una PWA.
Possiamo considerare un Service Worker come un miglioramento dell’esperienza utente durante la navigazione. Qualora un utente dovesse utilizzare un browser obsoleto che non supporta i Service Worker, tutte le funzionalità base della PWA sarebbero comunque garantite.
Funzionalità principali di un Service Worker
Un Service Worker possiede una serie di funzionalità, di seguito vi elenco quelle principali.
- Agisce come proxy tra il browser Web e il server Web permettendo di controllare e gestire le richieste a una pagina Web.
- Funziona solamente con il protocollo HTTPS.
- Passa in stato idle quando non è utilizzato e si riattiva quando è necessario, non esiste quindi uno stato globale persistente tra differenti eventi. Per salvare le informazioni da riutilizzare successivamente, si fa uso degli IndexedDB.
- Offre due API per permettere a un’applicazione di lavorare offline:
- fetch: il modo standard per ritrovare informazioni dalla rete;
- cache: per memorizzare precedenti richieste e velocizzare quelle future.
- È un oggetto event driven, ovvero ciascun task è lanciato in risposta a uno specifico evento.
Ciclo di vita di un Service Worker
Il lifecycle è molto simile a quella di un’applicazione per smartphone. Ogni volta che installiamo una app da uno store:
- viene fatta una richiesta per scaricare l’applicazione;
- l’applicazione viene quindi installata al termine del download;
- l’applicazione è pronta all’uso e può essere eseguita;
- ogni volta che vengono pubblicati nuovi rilasci l’applicazione si aggiorna.
Analogamente il ciclo di vita di un Service Worker si articola in tre fasi.
- Registrazione
- Installazione
- Attivazione
Registrazione
Prima d’installare un Service Worker, occorre registrarlo all’interno del file JavaScript del main
dell’applicazione così che il browser possa essere informato della sua presenza. Questa fase ritorna una promise
che viene risolta nel momento in cui il componente termina la registrazione con successo.
Lo scope determina quali file avrà sotto il suo controllo, in altre parole da quale percorso (quale route
) intercetterà le richieste. Lo scope di default è la location del file, e si estende a tutte le sue sottocartelle. Per esempio, se il file service-worker.js
fosse situato nella cartella root
del progetto, esso controllerebbe tutte le richieste a tutti i file del dominio.
È possibile cambiare lo scope di default del Service Worker passandogli un parametro addizionale alla chiamata di registrazione.
Installazione
Terminata la fase di registrazione si può procedere con l’installazione. Questa avviene quando il Service Worker risulta essere nuovo al browser oppure anche quando risulta esserci una differenza tra il precedente installato e quello nuovo, quindi quando è necessario un aggiornamento.
Questa fase genera un evento install
che è possibile intercettare mediante un listener
per eseguire alcuni task durante l’installazione. Per esempio è utile inserire nella cache parti della Web app che risultano essere statiche e che verranno richieste frequentemente.
Di seguito vi riporto un codice di esempio di semplice installazione:
const staticCacheName = 'pages-cache-v1'; const filesToCache = [ './images/1.jpg', './images/2.jpg', './images/3.jpg' ]; self.addEventListener('install', event => { console.log('Attempting to install service worker and cache static assets'); event.waitUntil( caches.open(staticCacheName) .then(cache => { console.log('Adding static files to cache') return cache.addAll(filesToCache); }) ); });
Attivazione
In questa fase, se ci sono delle pagine dell’app controllate da una versione precedente del Service Worker, la nuova entra in uno stato waiting
e si attiverà solamente quando non ci saranno più pagine gestite dalla precedente versione. In questo modo è garantito che ci sia solamente una sola versione attiva contemporaneamente.
Quando il componente si attiva, scatena un evento activate
che può essere utile intercettare per pulire dati vecchi presenti nella cache.
Una volta attivo, è possibile esaminare l’oggetto navigando nell’apposita sezione tra i Web development tools del browser usato per l’app.
Di seguito vi riporto il codice di esempio che gestisce il salvataggio di dati in cache:
self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => { if (response) { console.log('found ', event.request.url, ' in cache'); return response; } console.log('Resource not found in cache'); console.log('Network request for ',event.request.url); return fetch(event.request) .then(response => { // we need to save clone to put one copy in cache and serve second one let responseClone = response.clone(); caches.open('v1').then(function (cache) { caches.out(event.request, responseClone); }); return response; }); }).catch(error => { return new Response("Network error happened", {"status" : 408, "headers" : {"Content-Type" : "text/plain"}}); } }; });
Conclusioni
Spero che con questo articolo possa avervi incuriosito sull’importanza e l’utilizzo dei Service Worker. Qualora voleste approfondire, vi lascio due link inerenti alla documentazione ufficiale.
Questo invece è l’articolo “The Offline Cookbook” che tratta dei pattern più comuni per salvare risorse nella cache del browser.