Il precaricamento della navigazione ti consente di superare il tempo di avvio del service worker inviando richieste in parallelo.
Riepilogo
- In alcuni casi, il tempo di avvio del service worker può ritardare una risposta di rete.
- Disponibile nei tre principali motori del browser, il precaricamento della navigazione risolve il problema consentendo di effettuare la richiesta in parallelo con l'avvio del service worker.
- Puoi distinguere le richieste di precaricamento dalle normali navigazioni utilizzando un'intestazione e pubblicare contenuti diversi.
Il problema
Quando visiti un sito che utilizza un worker di servizio per gestire gli eventi di recupero, il browser chiede una risposta al worker di servizio. Ciò comporta l'avvio del service worker (se non è già in esecuzione) e l'invio dell'evento di recupero.
Il tempo di avvio dipende dal dispositivo e dalle condizioni. Di solito è di circa 50 ms. Sui dispositivi mobili, invece, si parla di 250 ms. In casi estremi (dispositivi lenti, CPU in difficoltà) può superare i 500 ms. Tuttavia, poiché il service worker rimane attivo per un periodo di tempo determinato dal browser tra un evento e l'altro, questo ritardo si verifica solo occasionalmente, ad esempio quando l'utente accede al tuo sito da una scheda nuova o da un altro sito.
Il tempo di avvio non è un problema se rispondi dalla cache, poiché il vantaggio di saltare la rete è maggiore del ritardo di avvio. Tuttavia, se rispondi utilizzando la rete…
La richiesta di rete è ritardata dall'avvio del service worker.
Stiamo continuando a ridurre il tempo di avvio utilizzando la memorizzazione nella cache del codice in V8, saltando i worker di servizio che non hanno un evento di recupero, avviando i worker di servizio in modo speculativo e con altre ottimizzazioni. Tuttavia, il tempo di avvio sarà sempre maggiore di zero.
Facebook ci ha segnalato l'impatto di questo problema e ci ha chiesto un modo per eseguire richieste di navigazione in parallelo:
Precaricamento della navigazione accorre in soccorso
Il precaricamento della navigazione è una funzionalità che ti consente di dire: "Quando l'utente effettua una richiesta di navigazione GET, avvia la richiesta di rete durante l'avvio del service worker".
Il ritardo di avvio è ancora presente, ma non blocca la richiesta di rete, quindi l'utente riceve i contenuti prima.
Ecco un video in cui il servizio viene eseguito con un ritardo di avvio deliberato di 500 ms utilizzando un ciclo while:
Ecco la demo stessa. Per usufruire dei vantaggi del precaricamento della navigazione, è necessario un browser che lo supporti.
Attivare il precaricamento della navigazione
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Puoi chiamare navigationPreload.enable()
quando vuoi o disattivarlo con navigationPreload.disable()
. Tuttavia, poiché l'evento fetch
deve utilizzarlo, è meglio attivarlo e disattivarlo nell'evento fetch
del tuo service worker.activate
Utilizzare la risposta precaricata
Ora il browser eseguirà i precarichi per le navigazioni, ma devi comunque utilizzare la risposta:
addEventListener('fetch', event => {
event.respondWith(async function() {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response;
// Else try the network.
return fetch(event.request);
}());
});
event.preloadResponse
è una promessa che si risolve con una risposta se:
- Il precaricamento della navigazione è attivo.
- La richiesta è una richiesta
GET
. - La richiesta è una richiesta di navigazione (generata dai browser quando caricano pagine, inclusi gli iframe).
In caso contrario, event.preloadResponse
è ancora presente, ma viene risolto con undefined
.
Risposte personalizzate per i precaricamenti
Se la tua pagina ha bisogno di dati della rete, il modo più rapido è richiederli nel service worker e creare una singola risposta in streaming contenente parti della cache e parti della rete.
Supponiamo di voler visualizzare un articolo:
addEventListener('fetch', event => {
const url = new URL(event.request.url);
const includeURL = new URL(url);
includeURL.pathname += 'include';
if (isArticleURL(url)) {
event.respondWith(async function() {
// We're going to build a single request from multiple parts.
const parts = [
// The top of the page.
caches.match('/article-top.include'),
// The primary content
fetch(includeURL)
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include')),
// The bottom of the page
caches.match('/article-bottom.include')
];
// Merge them all together.
const {done, response} = await mergeResponses(parts);
// Wait until the stream is complete.
event.waitUntil(done);
// Return the merged response.
return response;
}());
}
});
In questo caso, mergeResponses
è una piccola funzione che unisce gli stream di ogni richiesta. Ciò significa che possiamo visualizzare l'intestazione memorizzata nella cache mentre i contenuti della rete vengono trasmessi in streaming.
Questo è più veloce del modello "app shell" perché la richiesta di rete viene effettuata insieme alla richiesta di pagina e i contenuti possono essere trasmessi in streaming senza grossi problemi.
Tuttavia, la richiesta di includeURL
verrà ritardata dal tempo di avvio del service worker. Possiamo utilizzare il precaricamento della navigazione anche per risolvere questo problema, ma in questo caso non vogliamo precaricare la pagina completa, ma un'inclusione.
Per supportare questa funzionalità, viene inviata un'intestazione con ogni richiesta di precaricamento:
Service-Worker-Navigation-Preload: true
Il server può utilizzare questa informazione per inviare contenuti diversi per le richieste di precaricamento della navigazione rispetto a una normale richiesta di navigazione. Ricorda solo di aggiungere un'intestazione Vary: Service-Worker-Navigation-Preload
, in modo che le cache sappiano che le tue risposte sono diverse.
Ora possiamo utilizzare la richiesta di precaricamento:
// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
// Else do a normal fetch
.then(r => r || fetch(includeURL))
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include'));
const parts = [
caches.match('/article-top.include'),
networkContent,
caches.match('/article-bottom')
];
Modificare l'intestazione
Per impostazione predefinita, il valore dell'intestazione Service-Worker-Navigation-Preload
è true
, ma puoi impostarlo su qualsiasi valore:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Ad esempio, puoi impostarlo sull'ID dell'ultimo post memorizzato nella cache localmente, in modo che il server restituisca solo i dati più recenti.
Acquisizione dello stato
Puoi controllare lo stato del precaricamento della navigazione utilizzando getState
:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Un grande ringraziamento a Matt Falkenhagen e Tsuyoshi Horo per il loro lavoro su questa funzionalità e per l'aiuto fornito per la stesura di questo articolo. Un grande grazie a tutti coloro che hanno contribuito allo sforzo di standardizzazione