Como armazenar recursos em cache durante o tempo de execução

Alguns recursos no seu aplicativo da Web podem não ser usados com frequência, muito grandes ou variam de acordo com o dispositivo (como imagens responsivas) ou o idioma do usuário. Nesses casos, o armazenamento em cache pode ser um antipadrão, e você pode usar o armazenamento em cache no ambiente de execução.

No Workbox, é possível processar o armazenamento em cache no momento da execução dos recursos usando o módulo workbox-routing para fazer a correspondência das rotas e processar as estratégias de armazenamento em cache com o módulo workbox-strategies.

Estratégias de armazenamento em cache

É possível processar a maioria das rotas para recursos com uma das estratégias de armazenamento em cache integradas. Eles foram abordados em detalhes anteriormente nesta documentação, mas estes são alguns que vale a pena recapitular:

  • A opção Stale embora revalidar usa uma resposta armazenada em cache para uma solicitação (se estiver disponível) e atualiza o cache em segundo plano com uma resposta da rede. Portanto, se o recurso não estiver armazenado em cache, ele aguardará a resposta da rede e a usará. É uma estratégia bastante segura, porque atualiza regularmente as entradas de cache que dependem dele. A desvantagem é que ela sempre solicita um recurso da rede em segundo plano.
  • Network First tenta primeiro receber uma resposta da rede. Se uma resposta for recebida, ele a transmite para o navegador e a salva em um cache. Se a solicitação de rede falhar, a última resposta armazenada em cache será usada, permitindo o acesso off-line ao recurso.
  • Cache First verifica primeiro o cache em busca de uma resposta e a usa, se disponível. Se a solicitação não estiver no cache, a rede será usada e qualquer resposta válida será adicionada ao cache antes de ser transmitida ao navegador.
  • Somente rede força a resposta a vir da rede.
  • Somente cache força a resposta a vir do cache.

Você pode aplicar essas estratégias para selecionar solicitações usando os métodos oferecidos pela workbox-routing.

Como aplicar estratégias de armazenamento em cache com correspondência de rota

workbox-routing expõe um método registerRoute para corresponder rotas e processá-las com uma estratégia de armazenamento em cache. registerRoute aceita um objeto Route que, por sua vez, aceita dois argumentos:

  1. Uma string, expressão regular ou um callback de correspondência para especificar critérios de correspondência de rota.
  2. Um gerenciador da rota, normalmente uma estratégia fornecida por workbox-strategies.

Callbacks de correspondência têm preferência para corresponder rotas porque fornecem um objeto de contexto que inclui o objeto Request, a string do URL de solicitação, o evento de busca e um booleano que indica se a solicitação é de mesma origem.

Em seguida, o gerenciador manipula a rota correspondente. No exemplo a seguir, é criada uma nova rota que corresponde às solicitações de imagem de mesma origem que chegam, aplicando o cache primeiro, voltando à estratégia de rede.

// 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);

Como usar vários caches

A caixa de trabalho permite agrupar as respostas em cache em instâncias Cache separadas usando a opção cacheName disponível nas estratégias agrupadas.

No exemplo a seguir, as imagens usam uma estratégia "desatualizado durante a revalidação", enquanto os recursos CSS e JavaScript usam uma estratégia de rede que prioriza o cache. A rota para cada recurso coloca as respostas em caches separados, adicionando a propriedade 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);
Uma captura de tela de uma lista de instâncias de Cache na guia do aplicativo do Chrome DevTools. São exibidos três caches distintos: um chamado "scripts", outro chamado "styles" e o último chamado "images".
O visualizador de armazenamento cache no painel Application do Chrome DevTools. As respostas para diferentes tipos de recursos são armazenadas em caches separados.

Como definir uma expiração para as entradas de cache

Esteja ciente das cotas de armazenamento ao gerenciar os caches do service worker. ExpirationPlugin simplifica a manutenção do cache e é exposto por workbox-expiration. Para usá-la, especifique-a na configuração de uma estratégia de armazenamento em cache:

// 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);

Obedecer às cotas de armazenamento pode ser complicado. Uma prática recomendada é considerar os usuários que estão sofrendo uma pressão de armazenamento ou querem fazer o uso mais eficiente desse espaço. Os pares de ExpirationPlugin da caixa de trabalho podem ajudar a alcançar esse objetivo.

Considerações sobre origens diferentes

A interação entre o service worker e os recursos de origem cruzada é consideravelmente diferente do que com recursos de mesma origem. O Compartilhamento de recursos entre origens (CORS) é complicado, e essa complexidade se estende à forma como você lida com recursos entre origens em um service worker.

Respostas opacas

Ao fazer uma solicitação entre origens no modo no-cors, a resposta pode ser armazenada em um cache de service worker e até ser usada diretamente pelo navegador. No entanto, o corpo da resposta em si não pode ser lido usando JavaScript. Isso é conhecido como resposta opaca.

Respostas opacas são uma medida de segurança destinada a evitar a inspeção de um recurso de origem cruzada. Você ainda pode fazer solicitações para recursos de origem cruzada e até armazená-los em cache, mas não é possível ler o corpo da resposta nem o código de status.

Não se esqueça de ativar o modo CORS

Mesmo que você carregue recursos de origem cruzada que definem cabeçalhos CORS permissivos que permitem a leitura de respostas, o corpo da resposta de origem cruzada ainda pode ser opaco. Por exemplo, o HTML a seguir acionará solicitações no-cors que levarão a respostas opacas, independentemente de quais cabeçalhos do CORS estão definidos:

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

Para acionar explicitamente uma solicitação cors que vai produzir uma resposta não opaca, ative explicitamente o modo CORS adicionando o atributo crossorigin ao 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">

É importante lembrar disso quando as rotas no service worker armazenam sub-recursos carregados no ambiente de execução.

A caixa de trabalho não pode armazenar em cache respostas opacas

Por padrão, o Workbox adota uma abordagem cautelosa para armazenar respostas opacas em cache. Como é impossível examinar o código de resposta em busca de respostas opacas, o armazenamento em cache de uma resposta de erro pode resultar em uma experiência corrompida persistente se uma estratégia que prioriza o cache ou somente o cache for usada.

Se você precisar armazenar uma resposta opaca em cache no Workbox, use uma estratégia que prioriza a rede ou obsoleto durante a validação. Sim, isso significa que o recurso ainda será solicitado pela rede todas as vezes, mas garante que as respostas com falha não permaneçam e sejam substituídas por respostas utilizáveis.

Se você usar outra estratégia de armazenamento em cache e uma resposta opaca for retornada, o Workbox avisará que a resposta não foi armazenada em cache no modo de desenvolvimento.

Forçar o armazenamento em cache de respostas opacas

Se você tiver certeza absoluta de que quer armazenar em cache uma resposta opaca usando uma estratégia que prioriza o cache ou apenas o cache, poderá forçar o Workbox a fazer isso com o 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);

Respostas opacas e a API navigator.storage

Para evitar o vazamento de informações entre domínios, foi adicionado padding significativo ao tamanho de uma resposta opaca usada para calcular os limites da cota de armazenamento. Isso afeta a forma como a API navigator.storage informa as cotas de armazenamento.

Esse preenchimento varia de acordo com o navegador. No entanto, para o Chrome, o tamanho mínimo que qualquer resposta opaca em cache única contribui para o armazenamento geral usado é aproximadamente 7 megabytes. Lembre-se disso ao determinar quantas respostas opacas você quer armazenar em cache, já que é possível exceder as cotas de armazenamento muito antes do esperado.