Almacena recursos en caché durante el tiempo de ejecución

Es posible que algunos elementos de tu aplicación web se usen con poca frecuencia, sean muy grandes o varíen según el idioma o el dispositivo del usuario (como imágenes responsivas). Estas son instancias en las que el almacenamiento previo en caché puede ser un antipatrón, por lo que deberías utilizar el almacenamiento en caché del entorno de ejecución.

En Workbox, puedes administrar el almacenamiento en caché del entorno de ejecución de los recursos con el módulo workbox-routing para hacer coincidir las rutas y controlar las estrategias de almacenamiento en caché con el módulo workbox-strategies.

Estrategias de almacenamiento en caché

Puedes controlar la mayoría de las rutas de los recursos con una de las estrategias de almacenamiento en caché integradas. Ya se trataron en detalle en esta documentación, pero estos son algunos que vale la pena resumir:

  • La opción Stale while Revalidate usa una respuesta almacenada en caché para una solicitud si está disponible y actualiza la caché en segundo plano con una respuesta de la red. Por lo tanto, si el recurso no se almacena en caché, esperará la respuesta de la red y la usará. Es una estrategia bastante segura, ya que actualiza con regularidad las entradas de caché que dependen de ella. La desventaja es que siempre solicita un recurso de la red en segundo plano.
  • Network First intenta obtener una respuesta de la red primero. Si se recibe una respuesta, la pasa al navegador y la guarda en una caché. Si la solicitud de red falla, se usará la última respuesta almacenada en caché, lo que habilitará el acceso sin conexión al recurso.
  • Cache First verifica primero la caché en busca de una respuesta y la usa si está disponible. Si la solicitud no está en la caché, se utiliza la red y se agrega cualquier respuesta válida a la caché antes de pasarla al navegador.
  • Solo red obliga a que la respuesta provenga de la red.
  • Solo caché obliga a que la respuesta provenga de la caché.

Puedes aplicar estas estrategias para seleccionar solicitudes con los métodos que ofrece workbox-routing.

Aplicación de estrategias de almacenamiento en caché con coincidencia de ruta

workbox-routing expone un método registerRoute para hacer coincidir las rutas y controlarlas con una estrategia de almacenamiento en caché. registerRoute acepta un objeto Route que, a su vez, acepta dos argumentos:

  1. Es una cadena, una expresión regular o una devolución de llamada de coincidencia para especificar los criterios de coincidencia de la ruta.
  2. Un controlador para la ruta, por lo general, una estrategia proporcionada por workbox-strategies

Se prefieren las devoluciones de llamada de coincidencia para que coincidan las rutas, ya que proporcionan un objeto de contexto que incluye el objeto Request, la cadena de URL de la solicitud, el evento de recuperación y un valor booleano que indica si la solicitud es una solicitud del mismo origen.

Luego, el controlador controla la ruta coincidente. En el siguiente ejemplo, se crea una ruta nueva que coincide con las solicitudes de imagen del mismo origen que llegan, aplicando primero la caché y recurriendo a la estrategia de red.

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';

// A new route that matches same-origin image requests and handles
// them with the cache-first, falling back to network strategy:
const imageRoute = new Route(({ request, sameOrigin }) => {
  return sameOrigin && request.destination === 'image'
}, new CacheFirst());

// Register the new route
registerRoute(imageRoute);

Uso de varias cachés

Workbox te permite agrupar respuestas almacenadas en caché en instancias de Cache separadas usando la opción cacheName disponible en las estrategias agrupadas.

En el siguiente ejemplo, las imágenes usan una estrategia inactiva durante la revalidación, mientras que los recursos de CSS y JavaScript usan una estrategia de red que prioriza la caché. La ruta de cada recurso coloca las respuestas en cachés independientes; para ello, agrega la propiedad cacheName.

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';

// Handle images:
const imageRoute = new Route(({ request }) => {
  return request.destination === 'image'
}, new StaleWhileRevalidate({
  cacheName: 'images'
}));

// Handle scripts:
const scriptsRoute = new Route(({ request }) => {
  return request.destination === 'script';
}, new CacheFirst({
  cacheName: 'scripts'
}));

// Handle styles:
const stylesRoute = new Route(({ request }) => {
  return request.destination === 'style';
}, new CacheFirst({
  cacheName: 'styles'
}));

// Register routes
registerRoute(imageRoute);
registerRoute(scriptsRoute);
registerRoute(stylesRoute);
Captura de pantalla de una lista de instancias de Cache en la pestaña Application de las Herramientas para desarrolladores de Chrome. Se muestran tres cachés diferentes: una llamada “scripts”, otra llamada “styles” y la última llamada “images”.
El visualizador de almacenamiento en caché en el panel Application de las Herramientas para desarrolladores de Chrome. Las respuestas para diferentes tipos de recursos se almacenan en cachés separadas.

Configura un vencimiento para las entradas de caché

Ten en cuenta las cuotas de almacenamiento cuando administres caché(s) de service worker. ExpirationPlugin simplifica el mantenimiento de la caché y workbox-expiration lo expone. Para usarla, especifícala en la configuración de una estrategia de almacenamiento en caché:

// sw.js
import { registerRoute, Route } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';

// Evict image cache entries older thirty days:
const imageRoute = new Route(({ request }) => {
  return request.destination === 'image';
}, new CacheFirst({
  cacheName: 'images',
  plugins: [
    new ExpirationPlugin({
      maxAgeSeconds: 60 * 60 * 24 * 30,
    })
  ]
}));

// Evict the least-used script cache entries when
// the cache has more than 50 entries:
const scriptsRoute = new Route(({ request }) => {
  return request.destination === 'script';
}, new CacheFirst({
  cacheName: 'scripts',
  plugins: [
    new ExpirationPlugin({
      maxEntries: 50,
    })
  ]
}));

// Register routes
registerRoute(imageRoute);
registerRoute(scriptsRoute);

Cumplir con las cuotas de almacenamiento puede ser complicado. Te recomendamos considerar a los usuarios que pueden estar experimentando presión de almacenamiento o que quieren hacer un uso de su almacenamiento de la manera más eficiente. Los pares ExpirationPlugin de Workbox pueden ayudarte a lograr ese objetivo.

Consideraciones de origen cruzado

La interacción entre tu service worker y los recursos de origen cruzado es considerablemente diferente de la de los recursos del mismo origen. El uso compartido de recursos entre dominios (CORS) es complicado, lo que se extiende a la forma en que se manejan los recursos multiorigen en un service worker.

Respuestas opacas

Cuando se realiza una solicitud de origen cruzado en el modo no-cors, la respuesta se puede almacenar en la caché de un service worker, o incluso el navegador puede usarla directamente. Sin embargo, el cuerpo de la respuesta en sí mismo no se puede leer a través de JavaScript. Esto se conoce como respuesta opaca.

Las respuestas opacas son una medida de seguridad destinada a evitar la inspección de un recurso de origen cruzado. Aún puedes hacer solicitudes de recursos de origen cruzado y almacenar en caché. Sin embargo, no puedes leer el cuerpo de la respuesta ni leer su código de estado.

Recuerda habilitar el modo CORS

Incluso si cargas recursos de origen cruzado que establecen encabezados de CORS permisivos que te permiten leer respuestas, el cuerpo de la respuesta de origen cruzado puede seguir siendo opaco. Por ejemplo, el siguiente código HTML activará solicitudes no-cors que generarán respuestas opacas sin importar qué encabezados de CORS estén configurados:

<link rel="stylesheet" href="https://example.com/path/to/style.css">
<img src="https://example.com/path/to/image.png">

Para activar de forma explícita una solicitud cors que genere una respuesta no opaca, debes habilitar de forma explícita el modo CORS agregando el atributo crossorigin a tu código HTML:

<link crossorigin="anonymous" rel="stylesheet" href="https://example.com/path/to/style.css">
<img crossorigin="anonymous" src="https://example.com/path/to/image.png">

Es importante recordar esto cuando las rutas de los subrecursos de caché del service worker se cargan durante el tiempo de ejecución.

Es posible que la caja de trabajo no almacene en caché las respuestas opacas

De forma predeterminada, Workbox adopta un enfoque cauteloso para almacenar en caché respuestas opacas. Como es imposible examinar el código de respuesta para respuestas opacas, almacenar en caché una respuesta de error puede dar como resultado una experiencia dañada de forma persistente si se utiliza una estrategia de almacenamiento en caché primero o solo caché.

Si necesitas almacenar en caché una respuesta opaca en Workbox, debes usar una estrategia centrada en la red o inactiva durante la validación para controlarla. Sí, esto significa que el recurso se seguirá solicitando a la red cada vez, pero garantiza que las respuestas fallidas no persistan y, con el tiempo, se reemplace por respuestas utilizables.

Si usas otra estrategia de almacenamiento en caché y se muestra una respuesta opaca, Workbox te advertirá que la respuesta no se almacenó en caché en el modo de desarrollo.

Forzar el almacenamiento en caché de respuestas opacas

Si estás con certeza absoluta de que deseas almacenar en caché una respuesta opaca con una estrategia centrada en la caché o solo en caché, puedes forzar la opción de Workbox para que lo haga con el módulo workbox-cacheable-response:

import {Route, registerRoute} from 'workbox-routing';
import {NetworkFirst, StaleWhileRevalidate} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';

const cdnRoute = new Route(({url}) => {
  return url === 'https://cdn.google.com/example-script.min.js';
}, new CacheFirst({
  plugins: [
    new CacheableResponsePlugin({
      statuses: [0, 200]
    })
  ]
}))

registerRoute(cdnRoute);

Opaque Responses y la API de navigator.storage

Para evitar la filtración de información entre dominios, se agrega un relleno significativo al tamaño de una respuesta opaca que se usa para calcular los límites de cuota de almacenamiento. Esto afecta la manera en que la API de navigator.storage informa las cuotas de almacenamiento.

Este relleno varía según el navegador, pero, para Chrome, el tamaño mínimo que cualquier respuesta opaca almacenada en caché contribuye al almacenamiento total utilizado es de aproximadamente 7 megabytes. Debes tener esto en cuenta cuando determines cuántas respuestas opacas deseas almacenar en caché, ya que podrías exceder fácilmente las cuotas de almacenamiento mucho antes de lo esperado.