Come pensare ai service worker.
I service worker sono potenti e vale la pena impararli. Ti consentono di offrire agli utenti un'esperienza di livello completamente nuovo. Il tuo sito può caricarsi istantaneamente. Può funzionare offline. Può essere installata come app specifica per piattaforma e offre la stessa esperienza, ma con la copertura e la libertà del web.
Tuttavia, i worker di servizio sono diversi da tutto ciò a cui la maggior parte degli sviluppatori web è abituata. Hanno una curva di apprendimento ripida e alcuni problemi da tenere d'occhio.
Di recente, Google Developers e io abbiamo collaborato a un progetto, Service Workies, un gioco gratuito per comprendere i worker di servizio. Durante la creazione e la gestione delle complesse ins e out dei worker di servizio, ho riscontrato alcuni problemi. Ciò che mi ha aiutato di più è stato trovare alcune metafore descrittive. In questo post esploreremo questi modelli mentali e analizzeremo il nostro cervello sui tratti paradossali che rendono i lavoratori dei servizi allo stesso tempo difficili e impressionanti.
Lo stesso, ma diverso
Durante la codifica del tuo worker di servizio, molte cose ti sembreranno familiari. Puoi utilizzare le nuove funzionalità del linguaggio JavaScript che preferisci. Ascolti gli eventi del ciclo di vita proprio come per gli eventi della UI. Gestisci il flusso di controllo con le promesse come sempre.
Tuttavia, altri comportamenti dei service worker ti fanno grattare la testa per confusione. Soprattutto quando aggiorni la pagina e non vedi le modifiche al codice applicate.
Un nuovo livello
Normalmente, quando crei un sito, devi considerare solo due livelli: il client e il server. Il service worker è un nuovo livello intermedio.
Pensa al tuo service worker come a una sorta di estensione del browser, che il tuo sito può installare nel browser degli utenti. Una volta installato, il service worker espande il browser per il tuo sito con un potente livello intermedio. Questo livello di worker di servizio può intercettare e gestire tutte le richieste effettuate dal tuo sito.
Il livello del worker del servizio ha un proprio ciclo di vita indipendente dalla scheda del browser. Un semplice aggiornamento di una pagina non è sufficiente per aggiornare un service worker, proprio come non ci si aspetterebbe che un aggiornamento di pagina aggiorni il codice di cui è stato eseguito il deployment su un server. Ogni livello ha le proprie regole di aggiornamento.
Nel gioco Service Workies vengono trattati i molti dettagli del ciclo di vita dei worker di servizio e ti offriamo un'ampia possibilità di esercitarti con questo tipo di worker.
Potente, ma con limitazioni
Avere un service worker sul tuo sito offre vantaggi incredibili. Il tuo sito può:
- Funzionare perfettamente anche quando l'utente è offline
- ottenere enormi miglioramenti delle prestazioni grazie alla memorizzazione nella cache
- Utilizzare le notifiche push
- essere installata come PWA
I worker di servizio possono fare molto, ma sono limitati per progettazione. Non possono eseguire operazioni sincrone o nello stesso thread del tuo sito. Ciò significa che non è possibile accedere a:
- localStorage
- il DOM
- la finestra
La buona notizia è che esistono diversi modi in cui la tua pagina può comunicare con il proprio worker di servizio, tra cui postMessage
diretti, canali di messaggistica uno a uno e canali di trasmissione uno a molti.
Lunga esecuzione, ma di breve durata
Un worker di servizio attivo continua a funzionare anche dopo che un utente esce dal tuo sito o chiude la scheda. Il browser mantiene questo worker di servizio in modo che sia pronto la volta successiva che l'utente torna sul tuo sito. Prima che venga fatta la prima richiesta, il service worker ha la possibilità di intercettarla e assumere il controllo della pagina. Questo è ciò che consente a un sito di funzionare offline: il service worker può pubblicare una versione memorizzata nella cache della pagina stessa, anche se l'utente non ha una connessione a internet.
In Service Workies visualizziamo questo concetto con Kolohe (un simpatico worker) che intercetta e gestisce le richieste.
Interrotta
Sebbene i worker di servizio appaiano immortali, possono essere interrotti quasi in qualsiasi momento. Il browser non vuole sprecare risorse su un service worker che al momento non sta facendo nulla. L'interruzione non è la stessa cosa dell'interruzione: il worker del servizio rimane installato e attivato. È solo in sospensione. La volta successiva che è necessario (ad esempio per gestire una richiesta), il browser lo riattiva.
waitUntil
A causa della costante possibilità di essere messo in sospensione, il tuo service worker ha bisogno di un modo per comunicare al browser quando sta facendo qualcosa di importante e non ha voglia di fare un pisolino. È qui che entra in gioco event.waitUntil()
. Questo metodo estende il ciclo di vita in cui viene utilizzato, impedendo che venga interrotto e che passi alla fase successiva del ciclo di vita fino a quando non saremo pronti. In questo modo abbiamo il tempo di configurare le cache, recuperare risorse dalla rete e così via.
Questo esempio indica al browser che l'installazione del nostro worker dei servizi non è completata finché la cache assets
non è stata creata e compilata con l'immagine di una spada:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
Fai attenzione allo stato globale
Quando si verifica questo avvio/arresto, l'ambito globale del service worker viene reimpostato. Quindi fai attenzione a non utilizzare alcuno stato globale nel tuo service worker, altrimenti ti rassicurerai la prossima volta che si riattiva e ha uno stato diverso da quello che si aspettava.
Considera questo esempio che utilizza uno stato globale:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
Per ogni richiesta, questo worker di servizio registrerà un numero, diciamo 0.13981866382421893
. Anche la variabile hasHandledARequest
diventa true
. Ora il service worker rimane inattivo per un po', quindi il browser lo arresta. La volta successiva che viene inviata una richiesta, il service worker è di nuovo necessario, quindi il browser lo riattiva. Lo script viene valutato di nuovo. Ora hasHandledARequest
viene reimpostato su false
e favoriteNumber
è qualcosa di completamente diverso: 0.5907281835659033
.
Non puoi fare affidamento sullo stato memorizzato in un worker di servizio. Inoltre, la creazione di istanze di elementi come i canali di messaggi può causare bug: ogni volta che il service worker si arresta/avvia, viene creata una nuova istanza.
Nel capitolo 3 di Service Workers, visualizziamo il nostro service worker fermo che perde tutto il colore mentre attende di essere riattivato.
Insieme, ma separati
La tua pagina può essere controllata da un solo service worker alla volta. Tuttavia, può avere due service worker installati contemporaneamente. Quando apporti una modifica al codice del tuo service worker e aggiorni la pagina, non stai effettivamente modificando il service worker. I worker di servizio sono immutati. ma ne stai creando una nuova. Questo nuovo service worker (che chiameremo SW2) si installa, ma non si attiva ancora. Deve attendere la terminazione del service worker (SW1) attuale (quando l'utente lascia il sito).
Interferire con le cache di un altro service worker
Durante l'installazione, SW2 può configurare le impostazioni, in genere creando e compilando le cache. Tieni presente che questo nuovo worker di servizio ha accesso a tutto ciò a cui ha accesso l'attuale worker di servizio. Se non fai attenzione, il nuovo service worker in attesa può davvero rovinare tutto per il tuo attuale service worker. Ecco alcuni esempi che potrebbero causare problemi:
- SW2 potrebbe eliminare una cache in uso da SW1.
- SW2 potrebbe modificare i contenuti di una cache utilizzata da SW1, facendo sì che SW1 risponda con asset che la pagina non si aspetta.
Salta skipWaiting
Un service worker può anche utilizzare il metodo rischioso skipWaiting()
per assumere il controllo della pagina non appena è terminata l'installazione. Generalmente questa è una cattiva idea, a meno che tu non stia cercando di sostituire intenzionalmente un service worker con bug. Il nuovo worker del servizio potrebbe utilizzare risorse aggiornate che la pagina corrente non prevede, causando errori e bug.
Inizia con una base pulita
Per evitare che i service worker si chiudano a vicenda, assicurati che utilizzino cache diverse. Il modo più semplice per farlo è creare una versione dei nomi delle cache utilizzate.
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
Quando esegui il deployment di un nuovo service worker, esegui l'upgrade di version
in modo che esegua le operazioni necessarie con una cache completamente separata dal service worker precedente.
Pulizia finale
Quando il tuo service worker raggiunge lo stato activated
, sai che ha preso il controllo e che il service worker precedente è ridondante. A questo punto è importante eseguire la pulizia dopo il vecchio service worker. Non solo rispetta i limiti di spazio di archiviazione della cache degli utenti, ma può anche prevenire bug involontari.
Il metodo caches.match()
è una scorciatoia utilizzata spesso per recuperare un elemento da qualsiasi cache in cui è presente una corrispondenza. ma esegue l'iterazione nelle cache nell'ordine in cui sono state create. Supponiamo, quindi, di avere due versioni di un file di script app.js
in due cache diverse: assets-1
e assets-2
. La pagina si aspetta lo script più recente memorizzato in assets-2
. Tuttavia, se non hai eliminato la vecchia cache, caches.match('app.js')
restituirà quella precedente da assets-1
e molto probabilmente non farà funzionare il tuo sito.
Per eseguire la pulizia dopo i service worker precedenti, è sufficiente eliminare la cache non necessaria per il nuovo service worker:
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
Impedire ai tuoi service worker di sovrapporsi richiede un po' di lavoro e disciplina, ma vale la pena.
Mentalità dei service worker
Adottare la mentalità giusta quando pensi ai service worker ti aiuterà a sviluppare la tua sicurezza con fiducia. Una volta acquisita la necessaria dimestichezza, potrai creare esperienze incredibili per i tuoi utenti.
Se vuoi capire tutto questo giocando, hai trovato quello che fa per te. Vai a Service Workies: scoprirai i modi in cui il service worker può uccidere le bestie offline.