Come pensare ai service worker.
I Service worker sono potenti e meritano assolutamente l'apprendimento. Ti consentono di offrire agli utenti un'esperienza di livello completamente nuovo. Il tuo sito può caricarsi immediatamente. Può funzionare offline. Può essere installata come app specifica per la piattaforma e ha un aspetto elegante, ma con la portata e la libertà del web.
ma i service worker sono diversi da qualsiasi cosa a cui siamo abituati la maggior parte degli sviluppatori web. Hanno una ripida curva di apprendimento e una manciata di intoppi a cui devi fare attenzione.
Di recente io e Google Developers abbiamo collaborato a un progetto, Service Workies, un gioco senza costi per comprendere i service worker. Durante la sua costruzione e la gestione dei complessi dettagli degli addetti all'assistenza, ho riscontrato alcuni problemi. Quello che mi ha aiutato di più è stata 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.
Uguale, ma diverso
Molte cose ti sembreranno familiari durante la programmazione del service worker. Puoi usare le tue nuove funzionalità preferite del linguaggio JavaScript. Ascolti gli eventi del ciclo di vita proprio come per gli eventi della UI. Gestisci il flusso di controllo con promesse come sei abituato.
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
Di solito, quando crei un sito devi pensare solo a due livelli: il client e il server. Il service worker è un livello completamente nuovo che si trova al centro.
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 estende il browser del tuo sito con un potente livello centrale. Questo livello del service worker può intercettare e gestire tutte le richieste effettuate dal tuo sito.
Il livello del service worker ha il 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 esaminiamo i molti dettagli del ciclo di vita dei service worker e ti forniamo un sacco di pratica per utilizzarlo.
Potente, ma limitato
Avere un service worker sul tuo sito ti offre vantaggi incredibili. Il tuo sito può:
- funzionano perfettamente anche quando l'utente è offline
- ottenere enormi miglioramenti delle prestazioni grazie alla memorizzazione nella cache
- Utilizzare le notifiche push
- essere installata come PWA
Per quanto i Service worker possano fare, la loro progettazione è limitata. Non può fare nulla in modo sincrono o nello stesso thread del tuo sito. Ciò significa che non è possibile accedere a:
- localStorage
- il DOM
- la finestra
La buona notizia è che ci sono diversi modi in cui la tua pagina può comunicare con il proprio service worker, tra cui postMessage
diretti, canali di messaggistica one-to-one e canali di trasmissione one-to-many.
Lunga vita, ma di breve durata
Un service worker attivo continua a vivere anche dopo che un utente lascia il sito o chiude la scheda. Il browser mantiene questo service worker in modo che sia pronto la prossima volta che l'utente tornerà sul tuo sito. Prima che venga fatta la primissima 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 è connesso a internet.
In Service Workies, visualizziamo questo concetto con Kolohe (un service worker amichevole) che intercetta e gestisce le richieste.
Interrotta
Nonostante gli addetti ai servizi appaiano immortali, possono essere fermati in qualsiasi momento. Il browser non vuole sprecare risorse su un service worker che al momento non sta facendo nulla. Arrestare non è la stessa cosa che terminare: il service worker rimane installato e attivato. Viene soltanto messo in sospensione. La volta successiva che è necessario (ad esempio, per gestire una richiesta), il browser lo riattiva.
waitUntil
A causa della costante possibilità di addormentarsi, il Service worker ha bisogno di un modo per far sapere 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 passare alla fase successiva del suo ciclo di vita fino a quando non è tutto pronto. 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 Service worker non è terminata finché la cache di 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 in cui viene utilizzato uno stato globale:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
Questo service worker registra un numero per ogni richiesta, ad esempio 0.13981866382421893
. Anche la variabile hasHandledARequest
diventa true
. Ora il service worker rimane inattivo per un po', quindi il browser lo arresta. La prossima volta che viene inviata una richiesta, il service worker è di nuovo necessario e il browser lo riattiva. Il relativo script viene valutato di nuovo. Ora hasHandledARequest
è stato reimpostato su false
, e favoriteNumber
è qualcosa di completamente diverso: 0.5907281835659033
.
Non puoi fare affidamento sullo stato archiviato in un service worker. Inoltre, la creazione di istanze di elementi come i canali di messaggi può causare bug, in quanto ottieni una nuova istanza ogni volta che il service worker si arresta/avvia.
Nel Capitolo 3 di Service Workies, il Service worker arrestato viene visualizzato mentre perde tutto il colore mentre attende di essere svegliato.
Insieme, ma separati
La tua pagina può essere controllata da un solo service worker alla volta. Può però avere due service worker installati contemporaneamente. Quando modifichi il codice del service worker e aggiorni la pagina, non stai affatto modificando il service worker. I Service worker sono immutabili. ma ne stai creando uno nuovo. Questo nuovo service worker (che chiameremo SW2) si installa, ma non si attiva ancora. Deve attendere che l'attuale service worker (SW1) termini (quando l'utente lascia il sito).
Scambiarsi le cache di un altro service worker
Durante l'installazione, SW2 può eseguire la configurazione, in genere la creazione e il completamento delle cache. Ma attenzione: questo nuovo service worker ha accesso a tutto ciò a cui ha accesso l'attuale service worker. Se non fai attenzione, il tuo nuovo service worker in attesa può davvero rovinare le cose per quello attuale. Alcuni esempi che potrebbero causare problemi:
- SW2 potrebbe eliminare una cache che sta utilizzando attivamente SW1.
- SW2 potrebbe modificare i contenuti di una cache utilizzata da SW1, facendo in modo che SW1 risponda con asset non previsti dalla pagina.
Salta SaltaIn attesa
Un service worker può anche utilizzare il metodo skipWaiting()
rischioso per assumere il controllo della pagina al termine dell'installazione. Generalmente questa è una cattiva idea, a meno che tu non stia cercando di sostituire intenzionalmente un service worker con bug. Il nuovo service worker potrebbe utilizzare risorse aggiornate inaspettate nella pagina corrente, causando errori e bug.
Avvia pulizia
Per evitare che i Service worker si chiudano a vicenda, assicurati che utilizzino cache diverse. Il modo più semplice per ottenere questo risultato è modificare i nomi della cache utilizzati.
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, fai passare version
in modo che faccia ciò di cui ha bisogno, utilizzando una cache completamente separata rispetto al service worker precedente.
Termina pulizia
Quando il Service worker raggiunge lo stato activated
, sai che ha preso il controllo e il Service worker precedente è ridondante. A questo punto è importante eseguire la pulizia dopo il vecchio service worker. Non solo rispetta le esigenze degli utenti limiti di spazio di archiviazione della cache, ma può anche evitare 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 tua pagina prevede 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 comporterà l'interruzione del tuo sito.
Dopo aver eseguito la pulizia dopo i precedenti service worker, è sufficiente eliminare le cache non necessarie al 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 Service worker di clobarsi a vicenda richiede un po' di lavoro e disciplina, ma ne 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 dimestichezza, potrai creare esperienze incredibili per i tuoi utenti.
Se vuoi capire tutto questo giocando a un gioco, sei fortunato! Vai a Service Workies: scoprirai i modi in cui il service worker può uccidere le bestie offline.