La vita di un service worker

È difficile sapere cosa fanno i service worker senza comprenderne il ciclo di vita. Il loro funzionamento interno sembrerà opaco, persino arbitrario. È utile ricordare che, come per qualsiasi altra API del browser, i comportamenti dei service worker sono ben definiti, e rendere possibili le applicazioni offline, facilitando al contempo gli aggiornamenti senza compromettere l'esperienza utente.

Prima di iniziare a utilizzare Workbox, è importante comprendere il ciclo di vita dei service worker in modo da comprendere la logica di Workbox.

Definizione dei termini

Prima di entrare nel ciclo di vita del service worker, vale la pena definire alcuni termini relativi al funzionamento del ciclo di vita.

Controllo e ambito

L'idea di controllo è fondamentale per comprendere come operano i 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 ed è in grado di lavorare per la pagina in un determinato ambito.

Ambito

L'ambito di un service worker è determinato dalla sua posizione su un server web. Se un service worker viene eseguito su una pagina all'indirizzo /subdir/index.html e si trova in /subdir/sw.js, l'ambito del service worker è /subdir/. Per vedere il concetto di ambito in azione, guarda questo esempio:

  1. Passa a https://service-worker-scope-viewer.glitch.me/subdir/index.html. Viene visualizzato un messaggio che indica che nessun service worker sta controllando la pagina. Tuttavia, la pagina registra un service worker da https://service-worker-scope-viewer.glitch.me/subdir/sw.js.
  2. Ricarica la pagina. Poiché il service worker è stato registrato ed è ora attivo, controlla la pagina. Un modulo contenente l'ambito del service worker, stato corrente e il relativo URL sarà visibile. Nota: dover ricaricare la pagina non ha nulla a che fare con l'ambito, ma piuttosto il ciclo di vita del service worker, che verrà spiegato più avanti.
  3. Ora vai a https://service-worker-scope-viewer.glitch.me/index.html. Anche se un service worker era registrato su questa origine, c'è ancora un messaggio che indica che al momento non è presente alcun service worker. Il motivo è che 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 che si trovano in /subdir/ o nel relativo albero secondario.

Come funziona la definizione dell'ambito per impostazione predefinita, ma puoi ignorare l'ambito massimo consentito impostando il valore Service-Worker-Allowed intestazione della risposta, oltre a trasmettere un scope al metodo register.

A meno che non ci sia un'ottima ragione per limitare l'ambito del service worker a un sottoinsieme di un'origine, caricare un service worker dalla directory radice del server web in modo che l'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à sta controllando un client. Un client è qualsiasi pagina aperta il cui URL rientra nell'ambito del service worker. Nello specifico, si tratta di istanze di una WindowClient.

Il ciclo di vita di un nuovo service worker

Per fare in modo che un service worker controlli una pagina, deve prima essere messo in atto, per così dire. Iniziamo con cosa succede quando viene eseguito il deployment di un Service worker nuovo di zecca per un sito web senza un Service worker attivo.

Registrazione

La registrazione è la fase iniziale 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 sul thread principale e svolge le seguenti operazioni:

  1. Poiché la prima visita dell'utente a un sito web si verifica senza un service worker registrato, attendi che la pagina sia completamente caricata prima di registrarne una. In questo modo si evita il conflitto di larghezza di banda se il service worker prememorizza qualcosa.
  2. Sebbene il Service worker sia ben supportato, un controllo rapido consente di evitare errori nei browser in cui non è supportato.
  3. Quando la pagina è completamente caricata e se il service worker è supportato, registra /sw.js.

Alcuni aspetti fondamentali da comprendere sono:

  • I Service worker disponibile 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 eliminato.
  • Promemoria: i service worker operano all'interno di un ambito. Qui, l'ambito è l'intera origine, in quanto è stato caricato dalla directory radice.
  • All'inizio della registrazione, lo stato del service worker viene impostato su 'installing'.

Al termine della registrazione, inizierà l'installazione.

Installazione

Un service worker attiva la sua install dopo la registrazione. install viene chiamato una sola volta per service worker e non si attiverà di nuovo fino a quando non verrà aggiornato. È possibile registrare un callback per l'evento install 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 vengono prememorizzati gli asset. Parleremo della prememorizzazione nella cache più avanti, concentriamoci sul ruolo event.waitUntil event.waitUntil accetta una promessa, e attende che la promessa sia stata risolta. In questo esempio, la promessa fa due cose asincrone:

  1. Crea una nuova istanza Cache denominata 'MyFancyCache_v1'.
  2. Dopo aver creato la cache, un array di URL di asset viene prememorizzato nella cache usando Metodo addAll.

L'installazione non va a buon fine se le promesse passate a event.waitUntil sono rifiutato. In questo caso, il service worker viene eliminato.

Se le promesse si risolvono, l'installazione riesce e lo stato del service worker passerà a 'installed' e verrà attivato.

Attivazione

Se la registrazione e l'installazione hanno esito positivo, si attiva il service worker e il suo stato diventa 'activating' Il lavoro può essere svolto durante l'attivazione nell'account del Evento activate. Un'attività tipica in questo evento è l'eliminazione delle vecchie cache, ma per un service worker completamente nuovo, al momento non è pertinente, e verrà estesa quando parleremo degli aggiornamenti dei service worker.

Per i nuovi service worker, activate si attiva subito dopo l'esito positivo di install. Al termine dell'attivazione, lo stato del service worker diventa 'activated'. Per impostazione predefinita, il nuovo service worker non inizierà a controllare la pagina fino al successivo aggiornamento della pagina o della navigazione.

Gestione degli aggiornamenti dei service worker

Una volta eseguito il deployment del primo service worker, e probabilmente dovrà essere aggiornato in un secondo momento. Ad esempio, potrebbe essere necessario un aggiornamento se si verificano modifiche alla gestione delle richieste o alla logica di pre-memorizzazione nella cache.

Quando vengono eseguiti gli aggiornamenti

I browser controlleranno la presenza di aggiornamenti a un service worker quando:

  • L'utente passa a una pagina compresa nell'ambito del service worker.
  • navigator.serviceWorker.register() viene chiamato con un URL diverso da quello del service worker attualmente installato, ma non modificare l'URL del service worker.
  • navigator.serviceWorker.register() viene chiamato con lo stesso URL del service 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 avvengono gli aggiornamenti

Sapere quando il browser aggiorna un service worker è importante, ma anche il "come". Supponendo che l'URL o l'ambito di un service worker non venga modificato, un service worker attualmente installato si aggiorna a una nuova versione solo se i suoi contenuti sono stati modificati.

I browser rilevano i cambiamenti in due modi:

  • Eventuali modifiche byte per byte agli script richieste da importScripts, se applicabile.
  • Eventuali modifiche al codice di primo livello del service worker che incide sull'impronta creata dal browser.

Il browser esegue molte operazioni complicate. Per garantire che il browser abbia tutto ciò di cui ha bisogno per rilevare in modo affidabile le modifiche ai contenuti di un service worker, non indicare alla cache HTTP di bloccarla e non modificare il nome del file. Il browser esegue automaticamente i controlli degli aggiornamenti quando c'è una navigazione verso una nuova pagina nell'ambito di un service worker.

Attivazione manuale dei controlli degli aggiornamenti

Per quanto riguarda gli aggiornamenti, la logica di registrazione in genere non dovrebbe cambiare. Tuttavia, un'eccezione potrebbe essere la durata delle sessioni su un sito web. Ciò può accadere nelle applicazioni a pagina singola in cui richieste di navigazione sono rare poiché l'applicazione di solito riceve una richiesta di navigazione all'inizio del ciclo di vita dell'applicazione. In questi casi, è possibile attivare un aggiornamento manuale nel thread principale:

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

Per i siti web tradizionali, o, in tutti i casi in cui le sessioni utente non durano a lungo, probabilmente non è necessario attivare aggiornamenti manuali.

Installazione

Se utilizzi un bundler per generare asset statici, questi asset conterranno hash nel loro nome, ad esempio framework.3defa9d2.js. Supponiamo che alcuni di questi asset vengano prememorizzati nella cache per accedervi offline in un secondo momento. Ciò richiederebbe un aggiornamento del service worker per prememorizzare nella cache gli 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'
    ]);
  }));
});

Due aspetti sono diversi dal primo esempio di evento install rispetto a quello precedente:

  1. Viene creata una nuova istanza Cache con chiave 'MyFancyCacheName_v2'.
  2. I nomi degli asset prememorizzati nella cache sono cambiati.
di Gemini Advanced.

Una cosa da notare è che un service worker aggiornato viene installato insieme a quello precedente. Ciò significa che il Service worker precedente mantiene ancora il controllo delle pagine aperte e, in seguito all'installazione, quello nuovo passa in stato di attesa fino all'attivazione.

Per impostazione predefinita, un nuovo service worker si attiva quando nessun client è controllato da quello precedente. Questo accade quando vengono chiuse tutte le schede aperte del sito web pertinente.

Attivazione

Quando viene installato un service worker aggiornato e la fase di attesa termina, si attiva e il vecchio service worker viene eliminato. Un'attività comune da eseguire all'evento activate di un service worker aggiornato è l'eliminazione delle vecchie cache. Rimuovi le vecchie cache recuperando le chiavi per tutte le istanze Cache aperte con caches.keys ed eliminare le cache che non fanno parte di un elenco di elementi consentiti 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 si sistemano da sole. Dobbiamo farlo autonomamente, altrimenti rischiamo di superare quote di spazio di archiviazione. Poiché 'MyFancyCacheName_v1' del primo service worker è obsoleto, la lista consentita della cache viene aggiornata per specificare 'MyFancyCacheName_v2', eliminando così le cache con un nome diverso.

L'evento activate terminerà dopo la rimozione della vecchia cache. A questo punto, il nuovo service worker assumerà il controllo della pagina, finalmente sostituendo quello vecchio!

Il ciclo di vita non finisce mai

Se Workbox viene utilizzato per gestire il deployment e gli aggiornamenti dei service worker, o direttamente l'API Service Worker, per comprendere il ciclo di vita dei service worker. Con questa comprensione, i comportamenti dei service worker dovrebbero sembrare più logici che misteriosi.

Per chi fosse interessato ad approfondire questo argomento, vale la pena dare un'occhiata questo articolo di Jake Archibald. Il modo in cui si svolge l'intero ciclo di vita del servizio è molto variegato, ma è noto e questa conoscenza può essere molto utile quando si utilizza Workbox.