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

Algunos elementos de tu aplicación web pueden usarse con poca frecuencia, ser muy grandes o variar según el dispositivo del usuario (como las imágenes responsivas) o el idioma. Estas son instancias en las que el almacenamiento previo en caché puede ser un antipatrón y, en su lugar, debes confiar en el almacenamiento en caché del entorno de ejecución.

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

Estrategias de almacenamiento en caché

Puedes controlar la mayoría de las rutas para los recursos con una de las estrategias de almacenamiento en caché integradas. Se tratarán en detalle anteriormente en esta documentación, pero aquí hay algunos que vale la pena resumir:

  • 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 está almacenado en caché, esperará la respuesta de la red y la usará. Es una estrategia bastante segura, ya que actualiza periódicamente 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 la caché. Si la solicitud de red falla, se usará la última respuesta almacenada en caché, que habilitará el acceso sin conexión al recurso.
  • Cache First busca primero una respuesta en la caché y la usa si está disponible. Si la solicitud no está en la caché, se usa la red y cualquier respuesta válida se agrega a la caché antes de pasarla al navegador.
  • Solo red fuerza la respuesta para que provenga de la red.
  • Solo caché fuerza la respuesta para que provenga de la caché.

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

Aplica estrategias de almacenamiento en caché con coincidencia de ruta

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

  1. Una string, una expresión regular o una devolución de llamada de coincidencia para especificar los criterios de coincidencia de 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 hacer coincidir las rutas, ya que proporcionan un objeto de contexto que incluye el objeto Request, la string de URL de la solicitud, el evento de recuperación y un valor booleano que indica si la solicitud es del mismo origen.

Luego, el controlador maneja la ruta coincidente. En el siguiente ejemplo, se crea una ruta nueva que coincide con las solicitudes de imagen del mismo origen que provienen, 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);

Usa varias cachés

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

En el siguiente ejemplo, las imágenes usan una estrategia de inactividad durante la revalidación, mientras que los elementos 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 agregando 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 caché en la pestaña de la aplicación de Herramientas para desarrolladores de Chrome. Se muestran tres cachés diferentes: una llamada 'scripts', otra llamada 'styles' y la última llamada 'images'.
Visualizador de almacenamiento en caché en el panel Application de las Herramientas para desarrolladores de Chrome. Las respuestas para los diferentes tipos de elementos se almacenan en cachés independientes.

Configura un vencimiento para las entradas de caché

Ten en cuenta las cuotas de almacenamiento cuando administres las cachés del 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);

Satisfacer las cuotas de almacenamiento puede ser complicado. Es recomendable considerar a los usuarios que podrían estar experimentando presión de almacenamiento o que quieran usar su almacenamiento de la forma más eficiente. Los pares ExpirationPlugin de Workbox pueden ayudar a alcanzar ese objetivo.

Consideraciones de orígenes cruzados

La interacción entre tu service worker y los recursos de origen cruzado es considerablemente diferente a la de los recursos del mismo origen. El uso compartido de recursos multiorigen (CORS) es complicado y esa complejidad se extiende a la forma en que se manejan los recursos entre dominios 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é del service worker y hasta 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 e incluso almacenarlos en caché. No podrás leer el cuerpo de la respuesta ni leer su código de estado.

Recuerda habilitar el modo CORS

Incluso si cargas elementos de origen cruzado que establecen encabezados de CORS permisivos que permiten leer respuestas, el cuerpo de la respuesta de origen cruzado puede seguir siendo opaco. Por ejemplo, el siguiente HTML activará las solicitudes no-cors que generarán respuestas opacas independientemente de los encabezados de CORS que se configuren:

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

Para activar explícitamente una solicitud cors que generará una respuesta no opaca, debes habilitar el modo CORS de forma explícita. Para ello, agrega el atributo crossorigin a tu 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 la caché del service worker se cargan durante el tiempo de ejecución.

Es posible que Workbox no almacene en caché las respuestas opacas

De forma predeterminada, Workbox adopta un enfoque cauteloso para almacenar en caché las respuestas opacas. Como es imposible examinar el código de respuesta en busca de respuestas opacas, el almacenamiento en caché de una respuesta de error puede provocar una experiencia persistentemente dañada si se usa una estrategia en la que se prioriza la caché o solo en caché.

Si necesitas almacenar en caché una respuesta opaca en Workbox, debes usar una estrategia que prioriza la red o que se activa durante la validación para manejarla. Sí, esto significa que el recurso se seguirá solicitando desde la red cada vez, pero garantiza que las respuestas con errores no se mantengan y, con el tiempo, se reemplazarán 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 las respuestas opacas

Si tienes la certeza absoluta de que deseas almacenar en caché una respuesta opaca con una estrategia en la que se prioriza la caché o solo en caché, puedes forzar a 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);

Respuestas opacas y la API de navigator.storage

Para evitar la filtración de información multidominio, 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 forma 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 en caché contribuye al almacenamiento total utilizado es de aproximadamente 7 megabytes. Debes tener esto en cuenta a la hora de determinar cuántas respuestas opacas deseas almacenar en caché, ya que podrías exceder fácilmente las cuotas de almacenamiento mucho antes de lo esperado.