Il pacchetto workbox-window
è un insieme di moduli progettati per essere eseguiti nel
contesto window
, ovvero
all'interno delle tue pagine web. Si integrano con gli altri pacchetti di
workbox eseguiti nel service worker.
Le funzionalità/gli obiettivi principali di workbox-window
sono:
- Per semplificare il processo di registrazione e aggiornamenti dei service worker, aiutando gli sviluppatori a identificare i momenti più critici nel ciclo di vita dei Service worker e a rispondere più facilmente a questi momenti.
- Per impedire agli sviluppatori di commettere gli errori più comuni.
- Per abilitare una comunicazione più semplice tra il codice in esecuzione nel service worker e il codice in esecuzione nella finestra.
Importazione e utilizzo di workbox-window
Il punto di ingresso principale del pacchetto workbox-window
è la classe Workbox
. Puoi importarlo nel tuo codice dalla nostra rete CDN o utilizzando uno dei comuni strumenti di raggruppamento JavaScript.
Utilizzo della rete CDN
Il modo più semplice per importare la classe Workbox
sul tuo sito è dalla nostra rete CDN:
<script type="module">
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
</script>
Tieni presente che questo esempio utilizza <script type="module">
e l'istruzione import
per caricare la classe Workbox
. Sebbene tu possa pensare che sia necessario eseguire il transpile di questo codice
per farlo funzionare nei browser meno recenti, questo non è necessario.
Anche tutti i principali browser che supportano il service worker supportano i moduli JavaScript nativi, pertanto è perfettamente possibile fornire questo codice a qualsiasi browser (i browser meno recenti lo ignorano).
Caricamento di Workbox con bundle JavaScript
Sebbene non siano necessari assolutamente strumenti per utilizzare workbox-window
, se l'infrastruttura di sviluppo include già un bundler come webpack o Rollup che funziona con le dipendenze npm, puoi utilizzarlo per caricare workbox-window
.
Il primo passaggio consiste nell'installare
workbox-window
come dipendenza della tua applicazione:
npm install workbox-window
Quindi, in uno dei file JavaScript dell'applicazione, fai riferimento al nome del pacchetto workbox-window
nella casella di lavoro import
:
import {Workbox} from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
Se il tuo bundler supporta la suddivisione del codice tramite istruzioni di importazione dinamica, puoi anche caricare workbox-window
in modo condizionale, il che dovrebbe contribuire a ridurre le dimensioni del bundle principale della pagina.
Anche se workbox-window
è piuttosto piccolo, non c'è motivo per cui sia necessario caricarlo con la logica dell'applicazione di base del tuo sito perché i service worker, per loro natura, sono un miglioramento progressivo.
if ('serviceWorker' in navigator) {
const {Workbox} = await import('workbox-window');
const wb = new Workbox('/sw.js');
wb.register();
}
Concetti avanzati di raggruppamento
A differenza dei pacchetti Workbox eseguiti nel service worker, i file di build a cui fanno riferimento i campi main
e module
di workbox-window
in package.json
vengono traslati in ES5. Questo li rende compatibili con gli strumenti di
creazione odierni, alcuni dei quali non consentono agli sviluppatori di eseguire il trasferimento delle
dipendenze node_module
.
Se il tuo sistema di compilazione consente di eseguire il transpile delle dipendenze (o se non devi eseguire il transpile del codice), è meglio importare un file di origine specifico anziché il pacchetto stesso.
Ecco i vari modi in cui puoi importare Workbox
, oltre a una spiegazione di ciò che verrà restituito:
// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');
// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';
// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';
Esempi
Dopo aver importato la classe Workbox
, puoi utilizzarla per registrare e interagire con il service worker. Ecco alcuni esempi di modi in cui potresti utilizzare
Workbox
nella tua applicazione:
Registrare un service worker e inviare una notifica all'utente la prima volta che il service worker è attivo
Molti service worker di applicazioni web possono prememorizzare gli asset nella cache in modo che la loro app funzioni offline nei caricamenti pagina successivi. In alcuni casi potrebbe avere senso informare l'utente che l'app è ora disponibile offline.
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// `event.isUpdate` will be true if another version of the service
// worker was controlling the page when this version was registered.
if (!event.isUpdate) {
console.log('Service worker activated for the first time!');
// If your service worker is configured to precache assets, those
// assets should all be available now.
}
});
// Register the service worker after event listeners have been added.
wb.register();
Invia una notifica all'utente se un service worker è stato installato, ma è bloccato in attesa di attivazione
Quando una pagina controllata da un service worker esistente registra un nuovo service worker, per impostazione predefinita quel service worker non viene attivato finché tutti i client controllati dal service worker iniziale non hanno completato il caricamento.
Questa è una fonte di confusione comune per gli sviluppatori, soprattutto nei casi in cui il ricaricamento della pagina corrente non comporta l'attivazione del nuovo service worker.
Per ridurre al minimo la confusione e chiarire quando si verifica questa situazione,
il corso Workbox
offre un evento waiting
che puoi ascoltare:
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', event => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
});
// Register the service worker after event listeners have been added.
wb.register();
Avvisa l'utente in merito agli aggiornamenti della cache dal pacchetto workbox-broadcast-update
Il pacchetto workbox-broadcast-update
è un ottimo modo per pubblicare i contenuti dalla cache (per una pubblicazione rapida), comunicando al contempo all'utente gli aggiornamenti dei contenuti in questione (usando la
strategia "stale-while-revalidate).
Per ricevere questi aggiornamenti dalla finestra, puoi ascoltare gli eventi message
di
tipo CACHE_UPDATED
:
const wb = new Workbox('/sw.js');
wb.addEventListener('message', event => {
if (event.data.type === 'CACHE_UPDATED') {
const {updatedURL} = event.data.payload;
console.log(`A newer version of ${updatedURL} is available!`);
}
});
// Register the service worker after event listeners have been added.
wb.register();
Invia al service worker un elenco di URL da memorizzare nella cache
Per alcune applicazioni, è possibile conoscere tutte le risorse che devono essere pre-memorizzate nella cache al momento della creazione, ma alcune applicazioni pubblicano pagine completamente diverse, in base al primo URL a cui viene indirizzato l'utente per primo.
Per le app di quest'ultima categoria, potrebbe avere senso memorizzare nella cache solo gli asset necessari all'utente per la pagina specifica visitata. Quando utilizzi il pacchetto workbox-routing
, puoi inviare al router un elenco di URL da memorizzare nella cache in base alle regole definite sul router stesso.
Questo esempio invia al router un elenco di URL caricati dalla pagina ogni volta che viene attivato un nuovo service worker. Nota: puoi inviare tutti gli URL, perché solo gli URL che corrispondono a una route definita nel service worker verranno memorizzati nella cache:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// Get the current page URL + all resources the page loaded.
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map(r => r.name),
];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// Register the service worker after event listeners have been added.
wb.register();
Momenti importanti del ciclo di vita dei service worker
Il ciclo di vita dei service worker è complesso e può essere difficile da capire a fondo. Uno dei motivi per cui è così complesso è che deve gestire tutti i casi limite per tutti i possibili utilizzi dei service worker (ad esempio, la registrazione di più di un service worker, la registrazione di service worker diversi in frame diversi, la registrazione dei service worker con nomi diversi e così via).
Ma la maggior parte degli sviluppatori che implementano i service worker non dovrebbe preoccuparsi di tutti questi casi limite perché il loro utilizzo è piuttosto semplice. La maggior parte degli sviluppatori registra un solo service worker per caricamento pagina e non cambia il nome del file dei service worker di cui esegue il deployment sul server.
La classe Workbox
adotta questa visualizzazione più semplice per il ciclo di vita dei service worker, suddividendo tutte le registrazioni dei service worker in due categorie: il service worker registrato dell'istanza e un service worker esterno:
- Service worker registrato: un service worker che ha avviato l'installazione in seguito alla chiamata dell'istanza
Workbox
aregister()
o al service worker già attivo se la chiamata aregister()
non ha attivato un eventoupdatefound
nella registrazione. - Service worker esterno: un service worker che ha avviato l'installazione in modo indipendente dall'istanza
Workbox
che chiamaregister()
. In genere questo accade quando un utente ha una nuova versione del sito aperta in un'altra scheda. Quando un evento ha origine da un service worker esterno, la proprietàisExternal
dell'evento viene impostata sutrue
.
Tenendo a mente questi due tipi di service worker, ecco un'analisi di tutti i momenti più importanti del ciclo di vita dei Service worker, insieme a suggerimenti degli sviluppatori su come gestirli:
La prima volta che viene installato un service worker
Probabilmente vorrai trattare la prima volta che un service worker esegue l'installazione in modo diverso da come tratti tutti gli aggiornamenti futuri.
In workbox-window
, puoi distinguere tra la prima installazione della versione e gli aggiornamenti futuri controllando la proprietà isUpdate
in uno dei seguenti eventi. Per la prima installazione, isUpdate
sarà
false
.
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', event => {
if (!event.isUpdate) {
// First-installed code goes here...
}
});
wb.register();
Quando viene rilevata una versione aggiornata del service worker
Quando inizia l'installazione di un nuovo service worker, ma una versione esistente sta attualmente
controllando la pagina, la proprietà isUpdate
di tutti i seguenti eventi sarà true
.
Il modo in cui reagisci a questa situazione è in genere diverso dalla prima installazione, perché devi gestire quando e come l'utente riceve questo aggiornamento.
Quando viene rilevata una versione imprevista del service worker
A volte gli utenti mantengono il sito aperto in una scheda in background per molto tempo. Potrebbero persino aprire una nuova scheda e accedere al tuo sito senza rendersi conto di averlo già aperto in una scheda in background. In questi casi è possibile eseguire due versioni del sito contemporaneamente e questo potrebbe presentare alcuni problemi interessanti per gli sviluppatori.
Considera uno scenario in cui la scheda A è in esecuzione v1 del tuo sito e la scheda B con la versione v2. Quando viene caricata, la scheda B viene controllata dalla versione del worker di servizio fornita con la v1, ma la pagina restituita dal server (se utilizzi una strategia di memorizzazione nella cache network-first per le richieste di navigazione) conterrà tutti gli asset v2.
In genere questo non è un problema per la scheda B, in quanto quando hai scritto il codice v2 eri a conoscenza di come funzionava il codice v1. Tuttavia, potrebbe esserci un problema per la scheda A,dato che il codice v1 non avrebbe potuto prevedere quali modifiche potrebbero introdurre.
Per gestire queste situazioni, workbox-window
invia anche gli eventi del ciclo di vita quando rileva un aggiornamento da un service worker "esterno", dove per esterno si intende semplicemente qualsiasi versione diversa da quella registrata dall'istanza Workbox
attuale.
A partire da Workbox v6 e successive, questi eventi sono equivalenti a quelli documentati in precedenza, con l'aggiunta di una proprietà isExternal: true
impostata per ogni oggetto evento. Se la tua applicazione web deve implementare una logica specifica per gestire un service worker "esterno", puoi verificare la proprietà nei gestori di eventi.
Come evitare errori comuni
Una delle funzionalità più utili offerte da Workbox è la registrazione degli sviluppatori. E questo è particolarmente vero per workbox-window
.
Sappiamo che sviluppare con i service worker può spesso generare confusione e, quando accadono cose contrarie a ciò che ci si aspetta, può essere difficile capire il motivo.
Ad esempio, quando apporti una modifica al service worker e ricarichi la pagina, potresti non vedere la modifica nel browser. Il motivo più probabile è che il service worker è ancora in attesa di essere attivato.
Tuttavia, quando registri un service worker con la classe Workbox
, riceverai
una notifica relativa a tutte le modifiche dello stato del ciclo di vita nella Developer Console, il che dovrebbe
aiutarti a eseguire il debug del motivo per cui le cose non sono come ti aspetti.
Inoltre, un errore comune che gli sviluppatori commettono quando utilizzano per la prima volta il service worker è quello di registrare un service worker nell'ambito errato.
Per evitare che ciò accada, la classe Workbox
ti avvisa se la pagina di registrazione del service worker non rientra nell'ambito di quel service worker. Ti avvisa anche nei casi in cui il tuo service worker è attivo ma non controlla ancora la pagina:
Comunicazione tra il service worker
L'utilizzo più avanzato dei service worker comporta una notevole quantità di messaggi tra il service worker e la finestra. Anche la classe Workbox
può essere d'aiuto fornendo un metodo messageSW()
, che postMessage()
il service worker registrato dell'istanza e attenderà una risposta.
Sebbene sia possibile inviare dati al service worker in qualsiasi formato, il formato condiviso da tutti i pacchetti Workbox è un oggetto con tre proprietà (le ultime due sono facoltative):
I messaggi inviati con il metodo messageSW()
utilizzano MessageChannel
per consentire al destinatario
di rispondere. Per rispondere a un messaggio, puoi chiamare
event.ports[0].postMessage(response)
nel tuo listener di eventi dei messaggi. Il metodo messageSW()
restituisce una promessa che si risolverà in qualsiasi elemento response
con cui rispondi.
Ecco un esempio di invio di messaggi dalla finestra al service worker e di
ricezione di una risposta. Il primo blocco di codice è il listener di messaggi nel service worker, mentre il secondo utilizza la classe Workbox
per inviare il messaggio e attendere la risposta:
Codice in sw.js:
const SW_VERSION = '1.0.0';
addEventListener('message', event => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Codice in main.js (in esecuzione nella finestra):
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Gestione delle incompatibilità delle versioni
L'esempio riportato sopra mostra come implementare il controllo della versione del service worker dalla finestra. Questo esempio viene utilizzato perché quando invii messaggi tra la finestra e il service worker, è importante tenere presente che il service worker potrebbe non eseguire la stessa versione del sito su cui è in esecuzione il codice della pagina e che la soluzione per risolvere questo problema è diversa a seconda che tu stia pubblicando le pagine network-first o cache-first.
Prima rete
Quando pubblichi inizialmente la tua rete di pagine, gli utenti ricevono sempre la versione più recente del codice HTML dal server. Tuttavia, la prima volta che un utente visita nuovamente il tuo sito (dopo aver eseguito il deployment di un aggiornamento), l'HTML che riceverà si riferisce alla versione più recente, mentre il service worker in esecuzione nel browser sarà una versione già installata (possibilmente molte versioni precedenti).
È importante comprendere questa possibilità perché se il codice JavaScript caricato dalla versione corrente della pagina invia un messaggio a una versione precedente del service worker, tale versione potrebbe non sapere come rispondere (oppure rispondere con un formato incompatibile).
Di conseguenza, è consigliabile eseguire sempre una versione del service worker e verificare la presenza di versioni compatibili prima di eseguire qualsiasi operazione critica.
Ad esempio, nel codice riportato sopra, se la versione del service worker restituita da quella
chiamata messageSW()
è precedente alla versione prevista, sarebbe opportuno attendere fino a quando non viene trovato un aggiornamento (che dovrebbe verificarsi quando chiami register()
). A
questo punto, puoi inviare una notifica all'utente o un aggiornamento oppure puoi
saltare manualmente la fase di attesa
per attivare subito il nuovo service worker.
Prima cache
Diversamente da quanto accade quando pubblichi le pagine network-first, quando pubblichi le pagine nella cache, sappi che inizialmente la tua pagina avrà sempre la stessa versione del
service worker (perché è quella che la pubblica). Di conseguenza, puoi
utilizzare subito messageSW()
in sicurezza.
Tuttavia, se viene trovata una versione aggiornata del tuo service worker e si attiva quando la tua pagina chiama register()
(ovvero salta intenzionalmente la fase di attesa), potrebbe non essere più sicuro inviargli messaggi.
Una strategia per gestire questa possibilità è utilizzare uno schema di controllo delle versioni che ti consenta di distinguere tra aggiornamenti dell'ultima ora e aggiornamenti non più recenti e, in caso di aggiornamento che provoca un errore, non è sicuro inviare messaggi al service worker. Dovresti invece avvisare l'utente che sta eseguendo una versione precedente della pagina e suggerire di ricaricare per scaricare l'aggiornamento.
Nessun aiuto in attesa
Una convenzione di utilizzo comune per la messaggistica da window to service worker è l'invio di un messaggio {type: 'SKIP_WAITING'}
per istruire un service worker installato di saltare la fase di attesa e attivarlo.
A partire da Workbox v6, il metodo messageSkipWaiting()
può essere utilizzato per inviare un messaggio {type: 'SKIP_WAITING'}
al service worker in attesa associato alla registrazione attuale del service worker. In assenza di un service worker in attesa,
non fare nulla in silenzio.
Tipi
Workbox
Una classe che aiuta a gestire la registrazione, gli aggiornamenti e la reazione agli eventi del ciclo di vita dei service worker.
Proprietà
-
costruttore
void
Crea una nuova istanza Workbox con un URL di script e opzioni di service worker. L'URL e le opzioni dello script sono gli stessi utilizzati durante la chiamata a navigator.serviceWorker.register(scriptURL, options).
La funzione
constructor
ha il seguente aspetto:(scriptURL: string | TrustedScriptURL, registerOptions?: object) => {...}
-
scriptURL
stringa | TrustedScriptURL
Lo script del service worker associato all'istanza. È supportato l'uso di
TrustedScriptURL
. -
registerOptions
oggetto facoltativo
-
returns
-
-
attivo
Promise<ServiceWorker>
-
il controllo
Promise<ServiceWorker>
-
getSW
void
Restituisce un riferimento a un service worker che corrisponde all'URL dello script di questa istanza, non appena disponibile.
Se, al momento della registrazione, esiste già un service worker attivo o in attesa con un URL di script corrispondente, verrà utilizzato questo valore (con il service worker in attesa che ha la precedenza sul service worker attivo se entrambi corrispondono, dal momento che il service worker in attesa è stato registrato più di recente). Se al momento della registrazione non esiste un service worker attivo o in attesa corrispondente, la promessa non verrà risolta finché non viene trovato un aggiornamento e non viene avviata l'installazione. A quel punto viene utilizzato il service worker in fase di installazione.
La funzione
getSW
ha il seguente aspetto:() => {...}
-
returns
Promise<ServiceWorker>
-
-
messageSW
void
Invia l'oggetto dati passato al service worker registrato da questa istanza (tramite
workbox-window.Workbox#getSW
) e si risolve con una risposta (se presente).È possibile impostare una risposta in un gestore di messaggi nel service worker chiamando
event.ports[0].postMessage(...)
, che risolverà la promessa restituita damessageSW()
. Se non viene impostata alcuna risposta, la promessa non verrà mai risolta.La funzione
messageSW
ha il seguente aspetto:(data: object) => {...}
-
dati
oggetto
Un oggetto da inviare al service worker
-
returns
Promessa<qualsiasi>
-
-
messageSkipWaiting
void
Invia un messaggio
{type: 'SKIP_WAITING'}
al service worker che si trova attualmente nello statowaiting
associato alla registrazione corrente.Se non è presente alcuna registrazione corrente o se non è presente alcun service worker
waiting
, la chiamata non avrà alcun effetto.La funzione
messageSkipWaiting
ha il seguente aspetto:() => {...}
-
register
void
Registra un service worker per l'URL dello script dell'istanza e le opzioni del service worker. Per impostazione predefinita, questo metodo ritarda la registrazione fino a quando la finestra non è stata caricata.
La funzione
register
ha il seguente aspetto:(options?: object) => {...}
-
opzioni del modello.
oggetto facoltativo
-
nelle immediate vicinanze
booleano facoltativo
-
-
returns
Promise<ServiceWorkerRegistration>
-
-
update
void
Verifica la presenza di aggiornamenti del service worker registrato.
La funzione
update
ha il seguente aspetto:() => {...}
-
returns
Promise<void>
-
WorkboxEventMap
Proprietà
-
attivato
-
attivazione in corso...
-
il controllo
-
installata
-
installazione in corso...
-
messaggio
-
ridondante
-
in attesa
WorkboxLifecycleEvent
Proprietà
-
isExternal
booleano facoltativo
-
isUpdate
booleano facoltativo
-
originalEvent
Evento facoltativo
-
sw
ServiceWorker facoltativo
-
target
WorkboxEventTarget facoltativo
-
Tipo
typeOperator
WorkboxLifecycleEventMap
Proprietà
-
attivato
-
attivazione in corso...
-
il controllo
-
installata
-
installazione in corso...
-
ridondante
-
in attesa
WorkboxLifecycleWaitingEvent
Proprietà
-
isExternal
booleano facoltativo
-
isUpdate
booleano facoltativo
-
originalEvent
Evento facoltativo
-
sw
ServiceWorker facoltativo
-
target
WorkboxEventTarget facoltativo
-
Tipo
typeOperator
-
wasWaitingBeforeRegister
booleano facoltativo
WorkboxMessageEvent
Proprietà
-
dati
Qualsiasi
-
isExternal
booleano facoltativo
-
originalEvent
Evento
-
ports
typeOperator
-
sw
ServiceWorker facoltativo
-
target
WorkboxEventTarget facoltativo
-
Tipo
Metodi
messageSW()
workbox-window.messageSW(
sw: ServiceWorker,
data: object,
)
Invia un oggetto dati a un service worker tramite postMessage
e si risolve con
una risposta (se presente).
È possibile impostare una risposta in un gestore di messaggi nel service worker chiamando event.ports[0].postMessage(...)
, che risolverà la promessa
restituita da messageSW()
. Se non viene impostata alcuna risposta, la promessa non verrà risolta.
Parametri
-
sw
ServiceWorker
Il service worker a cui inviare il messaggio.
-
dati
oggetto
Un oggetto da inviare al service worker.
Ritorni
-
Promessa<qualsiasi>