Como usar plug-ins

Ao usar o Workbox, talvez você queira manipular uma solicitação e uma resposta enquanto ela é buscada ou armazenada em cache. Os plug-ins da caixa de trabalho permitem adicionar outros comportamentos ao service worker com o mínimo de código boilerplate. Eles podem ser empacotados e reutilizados nos seus próprios projetos ou liberados publicamente para uso por outras pessoas.

O Workbox oferece uma série de plug-ins prontos para uso que estão disponíveis para nós e, se você for do seu jeito, poderá criar plug-ins personalizados de acordo com as necessidades do seu aplicativo.

Plug-ins do Workbox disponíveis

O Workbox oferece os seguintes plug-ins oficiais para uso no service worker:

Os plug-ins do Workbox, sejam eles um dos plug-ins listados acima ou personalizados, são usados com uma estratégia do Workbox por meio da adição de uma instância do plug-in à propriedade plugins (link em inglês) da estratégia:

import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import {ExpirationPlugin} from 'workbox-expiration';

registerRoute(
  ({request}) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
      }),
    ],
  })
);

Métodos para plug-ins personalizados

Um plug-in do Workbox precisa implementar uma ou mais funções de callback. Quando você adiciona um plug-in a uma Strategy, as funções de callback são executadas automaticamente no momento certo. A Strategy transmite informações relevantes à função de callback sobre a solicitação e/ou resposta atual, fornecendo ao plug-in o contexto necessário para agir. Há suporte para as seguintes funções de callback:

  • cacheWillUpdate: chamado antes que um Response seja usado para atualizar um cache. Nesse método, a resposta pode ser alterada antes de ser adicionada ao cache, ou você pode retornar null para evitar a atualização completa do cache.
  • cacheDidUpdate: chamado quando uma nova entrada é adicionada a um cache ou se uma entrada existente é atualizada. Os plug-ins que usam esse método podem ser úteis quando você quiser executar uma ação após uma atualização de cache.
  • cacheKeyWillBeUsed: chamado antes que uma solicitação seja usada como chave de cache. Isso ocorre nas pesquisas de cache (quando mode é 'read') e nas gravações em cache (quando mode é 'write'). Esse callback é útil se você precisa modificar ou normalizar URLs antes de usá-los para acessar caches.
  • cachedResponseWillBeUsed: é chamado pouco antes de uma resposta de um cache ser usada, o que permite examinar essa resposta. Nesse momento, é possível retornar uma resposta diferente ou retornar null.
  • requestWillFetch: chamado sempre que uma solicitação está prestes a ir para a rede. Útil quando você precisar mudar o Request logo antes de ir para a rede.
  • fetchDidFail: chamado quando uma solicitação de rede falha, provavelmente devido à ausência de conectividade de rede, e não é disparado quando o navegador tem uma conexão de rede, mas recebe um erro (por exemplo, 404 Not Found).
  • fetchDidSucceed: chamado sempre que uma solicitação de rede é bem-sucedida, independentemente do código de resposta HTTP.
  • handlerWillStart: chamado antes de qualquer lógica do gerenciador começar a ser executada, o que é útil se você precisar definir o estado inicial do gerenciador. Por exemplo, se você quiser saber quanto tempo o gerenciador levou para gerar uma resposta, anote o horário de início nesse callback.
  • handlerWillRespond: chamado antes que o método handle() da estratégia retorne uma resposta, o que é útil se você precisar modificar uma resposta antes de retorná-la para uma RouteHandler ou outra lógica personalizada.
  • handlerDidRespond: chamado depois que o método handle() da estratégia retorna uma resposta. Nesse momento, pode ser útil registrar os detalhes finais da resposta (por exemplo, depois de alterações feitas por outros plug-ins).
  • handlerDidComplete: chamado depois que todas as promessas de ciclo de vida adicionadas ao evento a partir da invocação da estratégia forem resolvidas. Isso é útil se você precisar gerar relatórios sobre dados que precisem esperar até que o gerenciador seja concluído para calcular itens como status de ocorrência em cache, latência de cache, latência de rede e outras informações úteis.
  • handlerDidError: chamado se o gerenciador não puder fornecer uma resposta válida de qualquer origem. Esse é o momento ideal para fornecer algum tipo de resposta substituta como alternativa à falha imediata.

Todos esses callbacks são async e, portanto, vão exigir que o await seja usado sempre que um evento de cache ou de busca atingir o ponto relevante para o callback em questão.

Se um plug-in usasse todos os callbacks acima, este seria o código resultante:

const myPlugin = {
  cacheWillUpdate: async ({request, response, event, state}) => {
    // Return `response`, a different `Response` object, or `null`.
    return response;
  },
  cacheDidUpdate: async ({
    cacheName,
    request,
    oldResponse,
    newResponse,
    event,
    state,
  }) => {
    // No return expected
    // Note: `newResponse.bodyUsed` is `true` when this is called,
    // meaning the body has already been read. If you need access to
    // the body of the fresh response, use a technique like:
    // const freshResponse = await caches.match(request, {cacheName});
  },
  cacheKeyWillBeUsed: async ({request, mode, params, event, state}) => {
    // `request` is the `Request` object that would otherwise be used as the cache key.
    // `mode` is either 'read' or 'write'.
    // Return either a string, or a `Request` whose `url` property will be used as the cache key.
    // Returning the original `request` will make this a no-op.
    return request;
  },
  cachedResponseWillBeUsed: async ({
    cacheName,
    request,
    matchOptions,
    cachedResponse,
    event,
    state,
  }) => {
    // Return `cachedResponse`, a different `Response` object, or null.
    return cachedResponse;
  },
  requestWillFetch: async ({request, event, state}) => {
    // Return `request` or a different `Request` object.
    return request;
  },
  fetchDidFail: async ({originalRequest, request, error, event, state}) => {
    // No return expected.
    // Note: `originalRequest` is the browser's request, `request` is the
    // request after being passed through plugins with
    // `requestWillFetch` callbacks, and `error` is the exception that caused
    // the underlying `fetch()` to fail.
  },
  fetchDidSucceed: async ({request, response, event, state}) => {
    // Return `response` to use the network response as-is,
    // or alternatively create and return a new `Response` object.
    return response;
  },
  handlerWillStart: async ({request, event, state}) => {
    // No return expected.
    // Can set initial handler state here.
  },
  handlerWillRespond: async ({request, response, event, state}) => {
    // Return `response` or a different `Response` object.
    return response;
  },
  handlerDidRespond: async ({request, response, event, state}) => {
    // No return expected.
    // Can record final response details here.
  },
  handlerDidComplete: async ({request, response, error, event, state}) => {
    // No return expected.
    // Can report any data here.
  },
  handlerDidError: async ({request, event, error, state}) => {
    // Return a `Response` to use as a fallback, or `null`.
    return fallbackResponse;
  },
};

O objeto event disponível nos callbacks listados acima é o evento original que acionou a ação de busca ou armazenamento em cache. Às vezes, não vai haver um evento original. Por isso, seu código precisa verificar se ele existe antes de fazer referência a ele.

Todos os callbacks do plug-in também recebem um objeto state, que é exclusivo de determinado plug-in e da estratégia que ele invoca. Isso significa que é possível criar plug-ins em que um callback pode executar uma tarefa condicionalmente com base no que outro callback do mesmo plug-in fez (por exemplo, calcular a diferença entre executar requestWillFetch() e fetchDidSucceed() ou fetchDidFail()).

Plug-ins de terceiros

Se você desenvolve um plug-in e acha que ele pode ser usado fora do seu projeto, recomendamos que ele seja publicado como um módulo. Confira abaixo uma pequena lista de plug-ins do Workbox fornecidos pela comunidade:

Pesquise no repositório do npm para encontrar mais plug-ins do Workbox fornecidos pela comunidade.

Por fim, se você criou um plug-in do Workbox que gostaria de compartilhar, adicione a palavra-chave workbox-plugin ao publicá-lo. Se tiver, mande para nós pelo Twitter @WorkboxJS.