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

Alguns recursos no seu aplicativo da Web podem ser usados com pouca frequência, muito grandes ou variam de acordo com o dispositivo (como imagens responsivas) ou o idioma do usuário. Essas são instâncias em que o pré-armazenamento em cache pode ser um antipadrão. Em vez disso, use o armazenamento em cache no ambiente de execução.

No Workbox, é possível processar o armazenamento em cache no ambiente de execução para recursos usando o módulo workbox-routing para associar rotas e processar estratégias de armazenamento em cache para eles 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 integradas de armazenamento em cache. Eles são abordados em detalhes anteriormente nesta documentação, mas veja alguns que vale a pena recapitular:

  • Desatualizado durante a revalidação: usa uma resposta armazenada em cache para uma solicitação (se 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 vai aguardar a resposta da rede e usá-lo. É 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.
  • Prioridade à rede tenta conseguir uma resposta da rede primeiro. Se uma resposta é recebida, ele a passa 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 o cache em busca de uma resposta primeiro 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 passada para o navegador.
  • Somente rede força a resposta a vir da rede.
  • Somente cache força a resposta a vir do cache.

É possível aplicar essas estratégias para selecionar solicitações usando métodos oferecidos por 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 para a rota, normalmente uma estratégia fornecida por workbox-strategies.

Os callbacks de correspondência têm preferência para corresponder às 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 indicando se a solicitação é da mesma origem.

Em seguida, o manipulador processa a rota correspondente. No exemplo a seguir, é criada uma nova rota que corresponde às solicitações de imagem da mesma origem, aplicando o cache primeiro, recorrendo à 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

O Workbox permite agrupar respostas armazenadas em cache em instâncias de Cache separadas usando a opção cacheName disponível nas estratégias agrupadas.

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

Como definir uma expiração para entradas de cache

Esteja ciente das cotas de armazenamento ao gerenciar cache(s) do service worker. ExpirationPlugin simplifica a manutenção do cache e é exposto por workbox-expiration. Para usá-lo, especifique-o 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);

O cumprimento das cotas de armazenamento pode ser complicado. Recomendamos considerar os usuários com pressão de armazenamento ou que querem usar o espaço da maneira mais eficiente. Os pares de ExpirationPlugin do Workbox podem ajudar a alcançar essa meta.

Considerações sobre origem cruzada

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

Respostas opacas

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

As respostas opacas são uma medida de segurança destinada a impedir a inspeção de um recurso de origem cruzada. Ainda é possível fazer solicitações de recursos de origem cruzada e até mesmo armazená-los em cache. No entanto, não é possível ler o corpo da resposta nem o código de status.

Ative o modo CORS

Mesmo que você carregue recursos de origem cruzada que definam cabeçalhos CORS permissivos que permitam a leitura de respostas, o corpo da resposta de origem cruzada ainda poderá ficar opaco. Por exemplo, o HTML a seguir acionará solicitações no-cors que levarão a respostas opacas, independentemente dos cabeçalhos CORS 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 produza uma resposta não opaca, você precisa ativar 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 em cache sub-recursos carregados no ambiente de execução.

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

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 persistentemente se uma estratégia que prioriza o cache ou apenas o cache for usada.

Se você precisar armazenar uma resposta opaca no Workbox em cache, use uma estratégia que prioriza a rede ou a validação para obsoleto durante a execução. Sim, isso significa que o recurso ainda será solicitado da rede, mas garante que as respostas com falha não sejam mantidas e serão 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 absoluta certeza de que quer armazenar uma resposta opaca em cache usando uma estratégia que prioriza o cache ou apenas para o cache, force 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, há um preenchimento significativo adicionado ao tamanho de uma resposta opaca usada para calcular limites de cota de armazenamento. Isso afeta a forma como a API navigator.storage informa as cotas de armazenamento.

Esse padding varia de acordo com o navegador. No entanto, para o Chrome, o tamanho mínimo que qualquer resposta opaca em cache contribui para o armazenamento total usado é de aproximadamente 7 megabytes. Tenha isso em mente ao determinar quantas respostas opacas você quer armazenar em cache, já que é possível exceder as cotas de armazenamento muito antes do esperado.