È difficile sapere cosa fanno i worker di servizio senza comprendere il loro ciclo di vita. Il loro funzionamento interno sembrerà opaco, persino arbitrario. È utile ricordare che, come qualsiasi altra API del browser, i comportamenti dei worker di servizio sono ben definiti, specificati e rendono possibili le applicazioni offline, facilitando al contempo gli aggiornamenti senza interrompere l'esperienza utente.
Prima di iniziare a utilizzare Workbox, è importante comprendere il ciclo di vita del servizio worker in modo da capire cosa fa Workbox.
Definizione dei termini
Prima di esaminare il ciclo di vita del worker di servizio, vale la pena definire alcuni termini relativi al suo funzionamento.
Controllo e ambito
L'idea di controllo è fondamentale per comprendere il funzionamento dei service worker. Una pagina descritta come controllata da un service worker è una pagina che consente a un service worker di intercettare le richieste di rete per suo conto. Il service worker è presente e in grado di eseguire operazioni per la pagina all'interno di un determinato ambito.
Ambito
L'ambito di un worker di servizio è determinato dalla sua posizione su un server web.
Se un service worker viene eseguito in una pagina all'indirizzo /subdir/index.html
e si trova all'indirizzo /subdir/sw.js
,
l'ambito del service worker è /subdir/
.
Per vedere il concetto di ambito in azione, dai un'occhiata a questo esempio:
- Vai all'indirizzo
https://service-worker-scope-viewer.glitch.me/subdir/index.html.
Viene visualizzato un messaggio che indica che nessun service worker controlla la pagina.
Tuttavia, questa pagina registra un service worker da
https://service-worker-scope-viewer.glitch.me/subdir/sw.js
. - Ricarica la pagina. Poiché il service worker è stato registrato ed è ora attivo, controlla la pagina. Verrà visualizzato un modulo contenente l'ambito, lo stato corrente e l'URL del worker del servizio. Nota: la necessità di ricaricare la pagina non ha nulla a che fare con l'ambito, ma con il ciclo di vita del worker di servizio, che verrà spiegato in seguito.
- Ora vai all'indirizzo https://service-worker-scope-viewer.glitch.me/index.html. Anche se è stato registrato un service worker per questa origine, viene visualizzato un messaggio che indica che non è presente alcun service worker corrente. Questo perché questa pagina non rientra nell'ambito del service worker registrato.
L'ambito limita le pagine controllate dal service worker.
In questo esempio, significa che il service worker caricato da /subdir/sw.js
può controllare solo le pagine situate in /subdir/
o nel relativo sottoalbero.
Questo è il funzionamento dell'ambito per impostazione predefinita, ma l'ambito massimo consentito può essere ignorato impostando l'intestazione di risposta Service-Worker-Allowed
e passando un'opzione scope
al metodo register
.
A meno che non ci sia un motivo molto valido per limitare l'ambito del service worker a un sottoinsieme di un'origine, carica un service worker dalla directory principale del server web in modo che il suo ambito sia il più ampio possibile e non preoccuparti dell'intestazione Service-Worker-Allowed
. In questo modo è molto più semplice per tutti.
Cliente
Quando si dice che un service worker controlla una pagina, in realtà controlla un client.
Un client è qualsiasi pagina aperta il cui URL rientra nell'ambito del service worker.
Nello specifico, si tratta di istanze di un WindowClient
.
Il ciclo di vita di un nuovo worker di servizio
Affinché un worker di servizio possa controllare una pagina, deve prima essere creato. Iniziamo con quello che succede quando viene implementato un nuovo service worker per un sito web senza service worker attivi.
Registrazione
La registrazione è il primo passaggio del ciclo di vita del service worker:
<!-- In index.html, for example: -->
<script>
// Don't register the service worker
// until the page has fully loaded
window.addEventListener('load', () => {
// Is service worker available?
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(() => {
console.log('Service worker registered!');
}).catch((error) => {
console.warn('Error registering service worker:');
console.warn(error);
});
}
});
</script>
Questo codice viene eseguito nel thread principale ed esegue le seguenti operazioni:
- Poiché la prima visita dell'utente a un sito web avviene senza un servizio worker registrato, attendi che la pagina sia completamente caricata prima di registrarne uno. In questo modo si evitano conflitti di larghezza di banda se il worker di servizio esegue la precache di elementi.
- Sebbene i service worker siano ben supportati, un controllo rapido aiuta a evitare errori nei browser in cui non sono supportati.
- Quando la pagina è completamente caricata e se il service worker è supportato, registra
/sw.js
.
Ecco alcuni aspetti chiave da comprendere:
- I worker di servizio sono disponibili solo tramite HTTPS o localhost.
- Se i contenuti di un service worker contengono errori di sintassi, la registrazione non va a buon fine e il service worker viene ignorato.
- Promemoria: i service worker operano in un ambito. In questo caso, l'ambito è l'intera origine, così come è stata caricata dalla directory principale.
- Quando inizia la registrazione, lo stato del worker del servizio viene impostato su
'installing'
.
Al termine della registrazione, inizia l'installazione.
Installazione
Un worker del servizio attiva il suo
install
evento dopo la registrazione.
install
viene chiamato una sola volta per service worker e non verrà attivato di nuovo finché non verrà aggiornato.
Un callback per l'evento install
può essere registrato nell'ambito del worker con addEventListener
:
// /sw.js
self.addEventListener('install', (event) => {
const cacheKey = 'MyFancyCacheName_v1';
event.waitUntil(caches.open(cacheKey).then((cache) => {
// Add all the assets in the array to the 'MyFancyCacheName_v1'
// `Cache` instance for later use.
return cache.addAll([
'/css/global.bc7b80b7.css',
'/css/home.fe5d0b23.css',
'/js/home.d3cc4ba4.js',
'/js/jquery.43ca4933.js'
]);
}));
});
Viene creata una nuova istanza Cache
e gli asset vengono precommessi.
Avremo molte opportunità di parlare del precaching in seguito, quindi concentriamoci sul ruolo di event.waitUntil
. event.waitUntil
accetta una promessa
e attende che venga risolta.
In questo esempio, la promessa esegue due operazioni asincrone:
- Crea una nuova istanza
Cache
denominata'MyFancyCache_v1'
. - Dopo aver creato la cache,
viene eseguito il pre-caching di un array di URL di asset utilizzando il relativo metodo asincrono
addAll
.
L'installazione non va a buon fine se le promesse passate a event.waitUntil
vengono
rifiutate.
In questo caso, il worker del servizio viene ignorato.
Se le promesse vengono risolte,
l'installazione va a buon fine e lo stato del servizio worker diventa 'installed'
, quindi si attiva.
Attivazione
Se la registrazione e l'installazione vanno a buon fine, il servizio worker si attiva e il suo stato diventa 'activating'
. È possibile eseguire operazioni durante l'attivazione nell'evento activate
del servizio worker.
Un'attività tipica in questo evento è la potatura delle vecchie cache, ma per un nuovo servizio worker questo non è rilevante al momento e verrà approfondito quando parleremo degli aggiornamenti dei service worker.
Per i nuovi worker di servizio, activate
viene attivato immediatamente dopo il completamento di install
.
Al termine dell'attivazione,
lo stato del service worker diventa 'activated'
.
Tieni presente che, per impostazione predefinita, il nuovo service worker non inizierà a controllare la pagina fino alla navigazione successiva o all'aggiornamento della pagina.
Gestione degli aggiornamenti dei worker di servizio
Una volta disegnato il primo service worker, probabilmente dovrà essere aggiornato in un secondo momento. Ad esempio, potrebbe essere necessario un aggiornamento se si verificano modifiche nella gestione delle richieste o nella logica di pre-caching.
Quando vengono eseguiti gli aggiornamenti
I browser controllano la presenza di aggiornamenti di un worker di servizio quando:
- L'utente accede a una pagina nell'ambito del service worker.
navigator.serviceWorker.register()
viene chiamato con un URL diverso da quello del servizio worker attualmente installato, ma non modificare l'URL di un servizio worker.navigator.serviceWorker.register()
viene chiamato con lo stesso URL del servizio worker installato, ma con un ambito diverso. Anche in questo caso, evita questo problema mantenendo l'ambito alla radice di un'origine, se possibile.- Quando eventi come
'push'
o'sync'
sono stati attivati nelle ultime 24 ore, ma non preoccuparti ancora di questi eventi.
Come vengono eseguiti gli aggiornamenti
È importante sapere quando il browser aggiorna un service worker, ma lo è anche il "come". Supponendo che l'URL o l'ambito di un worker del servizio non sia modificato, un worker del servizio attualmente installato viene aggiornato a una nuova versione solo se i relativi contenuti sono stati modificati.
I browser rilevano le modifiche in due modi:
- Eventuali modifiche byte per byte agli script richieste da
importScripts
, se applicabili. - Eventuali modifiche al codice di primo livello del servizio worker, che influiscono sull'impronta generata dal browser.
Il browser esegue la maggior parte del lavoro. Per assicurarti che il browser abbia tutto ciò che serve per rilevare in modo affidabile le modifiche ai contenuti di un worker di servizio, non chiedere alla cache HTTP di conservarlo e non modificarne il nome file. Il browser esegue automaticamente i controlli degli aggiornamenti quando si passa a una nuova pagina nell'ambito di un worker di servizio.
Attivazione manuale dei controlli degli aggiornamenti
Per quanto riguarda gli aggiornamenti, in genere la logica di registrazione non dovrebbe cambiare. Tuttavia, un'eccezione potrebbe essere rappresentata dalle sessioni su un sito web di lunga durata. Questo può accadere nelle applicazioni a pagina singola in cui le richieste di navigazione sono rare, poiché in genere l'applicazione rileva una richiesta di navigazione all'inizio del ciclo di vita dell'applicazione. In questi casi, è possibile attivare un aggiornamento manuale sul thread principale:
navigator.serviceWorker.ready.then((registration) => {
registration.update();
});
Per i siti web tradizionali o in qualsiasi caso in cui le sessioni utente non siano durature, probabilmente non è necessario attivare gli aggiornamenti manuali.
Installazione
Quando utilizzi un bundler per generare asset statici,
questi asset conterranno hash nel nome,
ad esempio framework.3defa9d2.js
.
Supponiamo che alcuni di questi asset siano pre-memorizzati nella cache per l'accesso offline in un secondo momento.
Per farlo, è necessario aggiornare il servizio worker per eseguire la memorizzazione nella cache predefinita degli asset aggiornati:
self.addEventListener('install', (event) => {
const cacheKey = 'MyFancyCacheName_v2';
event.waitUntil(caches.open(cacheKey).then((cache) => {
// Add all the assets in the array to the 'MyFancyCacheName_v2'
// `Cache` instance for later use.
return cache.addAll([
'/css/global.ced4aef2.css',
'/css/home.cbe409ad.css',
'/js/home.109defa4.js',
'/js/jquery.38caf32d.js'
]);
}));
});
Esistono due differenze rispetto al primo esempio di evento install
riportato sopra:
- Viene creata una nuova istanza
Cache
con una chiave'MyFancyCacheName_v2'
. - I nomi delle risorse memorizzate nella cache sono cambiati.
Tieni presente che un service worker aggiornato viene installato insieme a quello precedente. Ciò significa che il vecchio service worker è ancora in controllo di tutte le pagine aperte e, dopo l'installazione, il nuovo entra in uno stato di attesa fino all'attivazione.
Per impostazione predefinita, un nuovo worker di servizio si attiva quando nessun client è controllato dal vecchio. Questo accade quando tutte le schede aperte per il sito web pertinente sono chiuse.
Attivazione
Quando viene installato un service worker aggiornato e termina la fase di attesa,
viene attivato e il vecchio service worker viene ignorato.
Un'attività comune da eseguire nell'evento activate
di un service worker aggiornato è eliminare le vecchie cache.
Rimuovi le vecchie cache ottenendo le chiavi per tutte le istanze Cache
aperte con
caches.keys
ed eliminando le cache che non sono in una lista consentita definita con
caches.delete
:
self.addEventListener('activate', (event) => {
// Specify allowed cache keys
const cacheAllowList = ['MyFancyCacheName_v2'];
// Get all the currently active `Cache` instances.
event.waitUntil(caches.keys().then((keys) => {
// Delete all caches that aren't in the allow list:
return Promise.all(keys.map((key) => {
if (!cacheAllowList.includes(key)) {
return caches.delete(key);
}
}));
}));
});
Le vecchie cache non vengono eliminate automaticamente.
Dobbiamo farlo noi stessi o rischiamo di superare le
quote di archiviazione.
Poiché 'MyFancyCacheName_v1'
del primo service worker non è aggiornato,
l'elenco consentiti della cache viene aggiornato per specificare 'MyFancyCacheName_v2'
,
che elimina le cache con un nome diverso.
L'evento activate
verrà completato dopo la rimozione della vecchia cache.
A questo punto, il nuovo service worker assumerà il controllo della pagina,
sostituendo finalmente quello precedente.
Il ciclo di vita continua
Indipendentemente dal fatto che Workbox venga utilizzato per gestire il deployment e gli aggiornamenti dei worker di servizio o che venga utilizzata direttamente l'API Service Worker, conviene comprendere il ciclo di vita dei worker di servizio. In questo modo, i comportamenti dei worker di servizio dovrebbero sembrare più logici che misteriosi.
Per chi vuole approfondire l'argomento, consigliamo di leggere questo articolo di Jake Archibald. Esistono moltissime sfumature nel modo in cui funziona l'intero ciclo di vita del servizio, ma è possibile conoscerle e queste conoscenze saranno molto utili quando utilizzi Workbox.