La precarga de Navigation te permite superar el tiempo de inicio del service worker mediante solicitudes en paralelo.
Resumen
- En algunas situaciones, el tiempo de inicio del service worker puede retrasar la respuesta de la red.
- La precarga de navegación, que está disponible en los tres motores de navegador principales, soluciona este problema, ya que te permite realizar la solicitud en paralelo con el inicio del service worker.
- Puedes distinguir las solicitudes de precarga de las navegaciones normales mediante un encabezado y publicar contenido diferente.
El problema
Cuando navegas a un sitio que usa un service worker para controlar los eventos de recuperación, el navegador le solicita una respuesta al service worker. Esto implica iniciar el service worker (si aún no se está ejecutando) y despachar el evento de recuperación.
El tiempo de inicio depende del dispositivo y de las condiciones. Por lo general, es de alrededor de 50 ms. En dispositivos móviles, la velocidad máxima es de 250 ms. En casos extremos (dispositivos lentos, CPU en peligro), pueden superar los 500 ms. Sin embargo, dado que el service worker permanece activo durante un tiempo determinado por el navegador entre eventos, solo se produce esta demora de vez en cuando, por ejemplo, cuando el usuario navega a tu sitio desde una pestaña nueva o desde otro sitio.
El tiempo de inicio no representa un problema si respondes desde la caché, ya que el beneficio de omitir la red es mayor que la demora en el inicio. Pero si respondes usando la red...
El service worker retrasa la solicitud de red.
Para seguir reduciendo el tiempo de inicio, usaremos el almacenamiento en caché de código en V8, se omitirán los service workers que no tengan un evento de recuperación, el inicio de service worker de manera especulativa y otras optimizaciones. Sin embargo, el tiempo de inicio siempre será mayor que cero.
Facebook nos informó sobre el impacto de este problema y nos pidió una forma de realizar solicitudes de navegación en paralelo:
Carga previa de Navigation al rescate
La precarga de navegación es una función que te permite decir lo siguiente: "Cuando el usuario realiza una solicitud de navegación GET, se inicia la solicitud de red mientras se inicia el service worker".
El retraso en el inicio sigue ahí, pero no bloquea la solicitud de red, por lo que el usuario obtiene el contenido antes.
Este es un video de cómo funciona, en el que el service worker recibe un retraso del inicio deliberado de 500 ms con un bucle while:
Esta es la demostración. Para obtener los beneficios de la precarga de navegación, necesitarás un navegador que la admita.
Activa la precarga de navegación
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Puedes llamar a navigationPreload.enable()
cuando quieras o inhabilitarla con navigationPreload.disable()
. Sin embargo, dado que es necesario que tu evento fetch
la use, se recomienda habilitarlo e inhabilitarlo en el evento activate
de tu service worker.
Usa la respuesta precargada
Ahora el navegador realizará las precargas para las navegaciones, pero deberás usar la respuesta de todos modos:
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
es una promesa que se resuelve con una respuesta en los siguientes casos:
- La precarga de navegación está habilitada.
- Es una solicitud
GET
. - La solicitud es una solicitud de navegación (que los navegadores generan cuando cargan páginas, incluidos los iframes).
Por lo demás, event.preloadResponse
sigue allí, pero se resuelve con undefined
.
Respuestas personalizadas para las precargas
Si tu página necesita datos de la red, la forma más rápida es solicitarlos al service worker y crear una única respuesta transmitida que contenga partes de la caché y partes de la red.
Supongamos que queremos mostrar un artículo:
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;
}());
}
});
En el ejemplo anterior, mergeResponses
es una función pequeña que combina las transmisiones de cada solicitud. Esto significa que podemos mostrar el encabezado almacenado en caché mientras se transmite el contenido de la red.
Este proceso es más rápido que el modelo de "shell de app", ya que la solicitud de red se realiza junto con la solicitud de la página y el contenido se puede transmitir sin hackeos importantes.
Sin embargo, el tiempo de inicio del service worker retrasará la solicitud de includeURL
. Podemos usar la precarga de navegación para corregir esto también, pero, en este caso, no queremos precargar la página completa, queremos precargar una inclusión.
Para ello, se envía un encabezado con cada solicitud de precarga:
Service-Worker-Navigation-Preload: true
El servidor puede usar esto para enviar contenido diferente en las solicitudes de precarga de navegación del que recibiría en una solicitud de navegación normal. Solo recuerda agregar un encabezado Vary: Service-Worker-Navigation-Preload
, para que las cachés sepan que tus respuestas difieren.
Ahora, podemos usar la solicitud de precarga:
// 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')
];
Cambia el encabezado
De forma predeterminada, el valor del encabezado Service-Worker-Navigation-Preload
es true
, pero puedes configurarlo como desees:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Por ejemplo, podrías configurarla con el ID de la última publicación que almacenaste en caché de forma local, de modo que el servidor solo devuelva datos más recientes.
Obtén el estado
Puedes buscar el estado de la precarga de navegación con getState
:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Muchas gracias a Matt Falkenhagen y Tsuyoshi Horo por su trabajo en esta función y a ayudar con este artículo. Agradecemos sinceramente a todas las personas involucradas en la iniciativa de estandarización