Mentalità dei service worker

Come pensare ai service worker.

I Service worker sono potenti e vale la pena impararli. che ti consentono di offrire agli utenti un livello di esperienza completamente nuovo. Il tuo sito può caricarsi immediatamente. Può funzionare offline. Può essere installato come app specifica per la piattaforma e ha un aspetto professionale, ma senza rinunciare alla portata e alla libertà del web.

Ma i service worker sono diversi da qualsiasi cosa a cui la maggior parte di noi sviluppatori web è abituata. Hanno una curva di apprendimento ripida e alcune insidie a cui devi fare attenzione.

Di recente, Google Developers e io abbiamo collaborato a un progetto, Service Workies, un gioco senza costi per comprendere i service worker. Mentre la costruivo e lavoravo con i trucchi del mestiere dei servizi, ho riscontrato alcuni problemi. Quello che mi ha aiutato di più è stato ideare una manciata di metafore rappresentative. In questo post esploreremo questi modelli mentali e avvolgeremo il nostro cervello intorno ai tratti paradossali che rendono i service worker sia difficili che interessanti.

Uguali, ma diversi

Durante la programmazione del tuo service worker, molte cose ti sembreranno familiari. Puoi utilizzare le tue nuove funzionalità preferite del linguaggio JavaScript. Puoi ascoltare gli eventi del ciclo di vita proprio come per gli eventi della UI. Gestisci il flusso di controllo con le promesse come di consueto.

Tuttavia, altri comportamenti dei service worker ti fanno grattare la testa per la confusione. Soprattutto quando aggiorni la pagina e non vedi le modifiche al codice applicate.

Un nuovo livello

Solitamente, quando si crea un sito, bisogna pensare a due livelli: il client e il server. Il service worker è un nuovo livello al centro.

Un service worker agisce da livello intermedio tra il client e il server

Pensa al service worker come a una sorta di estensione del browser, che il tuo sito può installare nel browser dell'utente. Dopo l'installazione, il service worker extends il browser per il tuo sito con un potente livello intermedio. Questo livello del service worker può intercettare e gestire tutte le richieste effettuate dal tuo sito.

Il livello del service worker ha un proprio ciclo di vita indipendente dalla scheda del browser. Un semplice aggiornamento di pagina non è sufficiente per aggiornare un service worker, proprio come non ti aspetteresti che un aggiornamento di pagina aggiorni il codice implementato su un server. Ogni livello ha le proprie regole di aggiornamento specifiche.

Nel gioco Service Workies trattiamo molti dettagli del ciclo di vita dei service worker e ti facciamo un sacco di pratica per lavorarci.

Potente, ma limitata

Avere un service worker sul tuo sito ti offre vantaggi incredibili. Il tuo sito può:

Con tutto quello che possono fare i service worker, sono limitati per definizione. Non possono eseguire operazioni sincrone o nello stesso thread del tuo sito. Ciò significa che non puoi accedere a:

  • localStorage
  • il DOM
  • la finestra

La buona notizia è che ci sono diversi modi in cui la tua pagina può comunicare con il suo service worker, tra cui i postMessage diretti, i canali di messaggi individuali e i canali di trasmissione one-to-many.

Lunga durata, ma di breve durata

Un service worker attivo continua a vivere anche dopo che un utente esce dal sito o chiude la scheda. Il browser mantiene a portata di mano questo service worker in modo che sia pronto la volta successiva che l'utente torna sul tuo sito. Prima che venga effettuata la prima richiesta, il service worker ha la possibilità di intercettarlo e assumere il controllo della pagina. È questo 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 mostriamo questo concetto con Kolohe (un service worker cordiale) che intercetta e gestisce le richieste.

Arrestata

Nonostante i Service worker sembrino essere immortali, possono essere fermati quasi in qualsiasi momento. Il browser non vuole sprecare risorse su un service worker che al momento non sta facendo nulla. L'arresto non è la stessa cosa che essere terminato: il service worker rimane installato e attivato. Viene solo messo a dormire. La volta successiva che è necessaria (ad esempio per gestire una richiesta), il browser lo riattiva.

waitUntil

A causa della costante possibilità di andare a dormire, il tuo service worker ha bisogno di un modo per comunicare al browser quando sta facendo qualcosa di importante e non ha la sensazione di fare un pisolino. È qui che entra in gioco event.waitUntil(). Questo metodo estende il ciclo di vita in cui viene utilizzato, evitando di essere arrestato e di passare alla fase successiva del suo ciclo di vita finché non siamo pronti. In questo modo abbiamo il tempo di configurare le cache, recuperare le risorse dalla rete e così via.

In questo esempio, il browser indica che il service worker non viene installato finché la cache di assets non viene 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"]);
    })
  );
});

Attenzione allo stato globale

In questo caso di avvio/arresto, l'ambito globale del service worker viene reimpostato. Quindi, fai attenzione a non utilizzare uno stato globale nel service worker o sarà triste la prossima volta che si riattiva e ha uno stato diverso da quello che ti 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;
});

A ogni richiesta questo service worker registrerà un numero, ad esempio 0.13981866382421893. Anche la variabile hasHandledARequest diventa true. Ora il service worker rimane inattivo per un po' di tempo, quindi il browser lo interrompe. Alla successiva richiesta, il service worker è di nuovo necessario, quindi il browser lo riattiva. Lo script viene rivalutato. Ora hasHandledARequest è stato reimpostato su false e favoriteNumber è 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: otterrai una nuova istanza ogni volta che il service worker si arresta o si avvia.

Nel Capitolo 3 di Service Workies mostriamo il nostro service worker interrotto mentre perde tutti i colori mentre attende di essere svegliato.

visualizzazione di un service worker interrotto

Insieme, ma separati

La tua pagina può essere controllata da un solo service worker alla volta. Tuttavia, può installare due service worker contemporaneamente. Quando apporti una modifica al codice del service worker e aggiorni la pagina, in realtà non stai modificando affatto il service worker. I service worker sono immutabili. Stai invece creandone uno nuovo. Il nuovo service worker (che chiameremo SW2) verrà installato, ma non sarà ancora attivato. Deve attendere l'arresto del service worker (SW1) corrente (quando l'utente lascia il sito).

Passione con le cache di un altro service worker

Durante l'installazione, SW2 può completare la configurazione, di solito la creazione e il completamento delle cache. Ma attenzione: questo nuovo service worker ha accesso a tutto ciò a cui ha accesso il service worker attuale. Se non fai attenzione, il nuovo service worker in attesa può rovinare le cose per il tuo attuale service worker. Ecco alcuni esempi che potrebbero causare problemi:

  • SW2 potrebbe eliminare una cache utilizzata attivamente 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 e saltaIn attesa

Un service worker può anche utilizzare il rischioso metodo skipWaiting() per assumere il controllo della pagina al termine dell'installazione. Generalmente questa è una cattiva idea, a meno che tu non stia intenzionalmente cercando di sostituire un service worker che presenta bug. Il nuovo service worker potrebbe utilizzare risorse aggiornate che la pagina corrente non è prevista, con conseguenti errori e bug.

Inizia pulizia

Per evitare che i service worker si sovrappongano, devi assicurarti che utilizzino cache diverse. Il modo più semplice per farlo è creare la versione dei nomi di cache che utilizzano.

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, metti il version in modo che possa fare ciò che serve con una cache completamente separata da quella del service worker precedente.

visualizzazione di una cache

Fine pulizia

Quando il 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 precedente service worker. Non solo rispetta i limiti di archiviazione della cache degli utenti, ma può anche prevenire bug involontari.

Il metodo caches.match() è una scorciatoia usata spesso per recuperare un elemento da qualsiasi cache in cui esiste una corrispondenza. Tuttavia, l'iterazione nelle cache avviene nell'ordine in cui sono state create. Supponiamo che tu abbia due versioni di un file di script app.js in due cache diverse: assets-1 e assets-2. Per la tua pagina è previsto il più recente script archiviato in assets-2. Tuttavia, se non hai eliminato la vecchia cache, caches.match('app.js') restituirà quella vecchia da assets-1 e molto probabilmente danneggerà il sito.

Per la pulizia dopo i service worker precedenti è sufficiente eliminare le cache non necessarie 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 service worker di chiamarsi a vicenda richiede un po' di lavoro e disciplina, ma ne vale la pena.

Mentalità dei Service worker

Entrare nella giusta mentalità mentre pensi ai service worker ti aiuterà a costruire il tuo con fiducia. Una volta che avrai imparato a usarli, potrai creare esperienze incredibili per i tuoi utenti.

Se vuoi capire tutto questo giocando, sei fortunato. Gioca a Service Workies, dove scoprirai i trucchi del service worker per uccidere le bestie offline.