Usar o Service Workers para gerenciar solicitações de rede

Os Trabalhadores de Serviço são um tipo especial de Web Workers com a capacidade de interceptar, modificar e responder a solicitações de rede usando a Fetch API. Os Trabalhadores de Serviço podem acessar a API e os Cache armazenamentos de dados assíncronos do lado do cliente, como IndexedDB, para armazenar recursos.

Os Trabalhadores de Serviço podem tornar seu PWA mais rápido, armazenando recursos localmente, e eles também podem tornar seu PWA mais confiável, tornando-o independente de rede.

Na primeira vez que um usuário acessa seu PWA, o Service Worker é instalado. Em seguida, o Service Worker é executado em paralelo ao seu aplicativo e pode continuar trabalhando mesmo quando seu aplicativo não estiver em execução.

Os Trabalhadores de Serviço são responsáveis por interceptar, modificar e responder a solicitações de rede. Eles podem ser alertados quando o aplicativo tenta carregar um recurso do servidor ou envia uma solicitação para obter dados do servidor. Quando isso acontece, um Service Worker pode decidir deixar a solicitação ir para o servidor ou interceptá-la e retornar uma resposta do cache.

Diagrama de arquitetura de alto nível mostrando que o Service Worker está entre o aplicativo e o armazenamento de rede e cache

Registrar um trabalhador de serviço

Semelhante a outros Trabalhos Web, os Trabalhadores de Serviço devem existir em um arquivo separado. Você faz referência a esse arquivo ao registrar o Service Worker, conforme mostrado no seguinte código:

if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("/serviceworker.js");
}

O navegador da Web que está executando seu PWA pode fornecer níveis diferentes de suporte para os Trabalhadores de Serviço. Além disso, o contexto em que sua PWA está em execução pode não estar seguro. Como tal, é uma boa prática testar a existência do objeto antes de navigator.serviceWorker executar qualquer código relacionado ao Service Worker. No código acima, um Service Worker é registrado usando o serviceworker.js arquivo localizado na raiz do site.

Certifique-se de colocar o arquivo do Service Worker no diretório de nível mais alto que você deseja que ele gerencie. Esse diretório é chamado de escopo do Service Worker. No código anterior, o arquivo é armazenado no diretório raiz do seu aplicativo e o Service Worker gerencia todas as páginas que estão sob o nome de domínio do aplicativo.

Se o arquivo Do Trabalho de Serviço fosse armazenado em um js diretório, o escopo do Service Worker seria limitado ao diretório e a js quaisquer subdiretórios. Como prática recomendada, coloque o arquivo Do Trabalho de Serviço na raiz do seu aplicativo, a menos que você precise reduzir o escopo do Trabalho de Serviço.

Interceptar solicitações

O evento main que você usa em um Service Worker é o fetch evento. O fetch evento é executado sempre que o navegador que seu aplicativo executa na tentativa de acessar o conteúdo no escopo do Service Worker.

O código a seguir mostra como adicionar um ouvinte para o fetch evento:

self.addEventListener("fetch", event => {
  console.log('WORKER: Fetching', event.request);
});

fetch No manipulador, você pode controlar se uma solicitação vai para a rede, é retirada do cache e assim por diante. A abordagem que você adotar provavelmente variará, com base no tipo de recurso que está sendo solicitado, com que frequência ele é atualizado e outra lógica de negócios exclusiva do seu aplicativo.

Aqui estão alguns exemplos do que você pode fazer no fetch manipulador:

  • Se estiver disponível, retorne uma resposta do cache; caso contrário, fallback para solicitar o recurso pela rede.
  • Buscar um recurso da rede, armazenar em cache uma cópia e retornar a resposta.
  • Permitir que os usuários especifiquem uma preferência para salvar dados.
  • Forneça uma imagem de espaço reservado para determinadas solicitações de imagem.
  • Gere uma resposta diretamente no Service Worker.

O ciclo de vida do Service Worker

O ciclo de vida de um Service Worker consiste em várias etapas, com cada etapa disparando um evento. Você pode adicionar ouvintes a esses eventos para executar o código para executar uma ação. A lista a seguir apresenta uma exibição de alto nível do ciclo de vida e eventos relacionados dos Trabalhadores de Serviço.

  1. Registre o Trabalho de Serviço.

  2. O navegador baixa o arquivo JavaScript, instala o Service Worker e dispara o install evento. Você pode usar o install evento para pré-armazenar em cache arquivos importantes e de longa duração (como arquivos CSS, arquivos JavaScript, imagens de logotipo ou páginas offline) do seu aplicativo.

    self.addEventListener("install", event => {
        console.log("WORKER: install event in progress.");
    });
    
  3. O Service Worker é ativado, o que dispara o activate evento. Use esse evento para limpo caches desatualizados.

    self.addEventListener("activate", event => {
        console.log("WORKER: activate event in progress.");
    });
    
  4. O Service Worker está pronto para ser executado quando a página for atualizada ou quando o usuário for para uma nova página no site. Se você quiser executar o Service Worker sem aguardar, use o self.skipWaiting() método durante o install evento, da seguinte maneira:

    self.addEventListener("install", event => {
        self.skipWaiting();
        // …
    });
    
  5. O Trabalho de Serviço agora está em execução e pode ouvir fetch eventos.

Recursos de pré-cache

Quando um usuário acessa seu aplicativo pela primeira vez, o Service Worker do aplicativo é instalado. Use o install evento em seu Service Worker para detectar quando isso ocorrer e armazenar em cache todos os recursos estáticos que seu aplicativo precisa. O cache dos recursos estáticos do seu aplicativo (como o código HTML, CSS e JavaScript) necessários pela página inicial permite que seu aplicativo seja executado mesmo quando o dispositivo do usuário está offline.

Para armazenar em cache os recursos do aplicativo, use o objeto global caches e o cache.addAll método, conforme mostrado abaixo:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

Observe que, após a instalação inicial, o install evento não será executado novamente. Para atualizar o código do Service Worker, consulte Atualizar seu Trabalho de Serviço.

Agora você pode usar o fetch evento para retornar os recursos estáticos do cache, em vez de carregá-los novamente da rede:

self.addEventListener("fetch", event => {
  async function returnCachedResource() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Find the response that was pre-cached during the `install` event.
    const cachedResponse = await cache.match(event.request.url);

    if (cachedResponse) {
      // Return the resource.
      return cachedResponse;
    } else {
      // The resource wasn't found in the cache, so fetch it from the network.
      const fetchResponse = await fetch(event.request.url);
      // Put the response in cache.
      cache.put(event.request.url, fetchResponse.clone());
      // And return the response.
      return fetchResponse.
    }
  }

  event.respondWith(returnCachedResource());
});

Para brevidade, o exemplo de código acima não manipula os casos em que falha ao obter a URL de solicitação da rede.

Usar uma página offline personalizada

Quando seu aplicativo usa várias páginas HTML, um cenário offline comum é redirecionar solicitações de navegação de página para uma página de erro personalizada quando o dispositivo do usuário estiver offline:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
// Note the offline page in this list.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js", "/offline"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

self.addEventListener("fetch", event => {
  async function navigateOrDisplayOfflinePage() {
    try {
      // Try to load the page from the network.
      const networkResponse = await fetch(event.request);
      return networkResponse;
    } catch (error) {
      // The network call failed, the device is offline.
      const cache = await caches.open(CACHE_NAME);
      const cachedResponse = await cache.match("/offline");
      return cachedResponse;
    }
  }

  // Only call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === 'navigate') {
    event.respondWith(navigateOrDisplayOfflinePage());
  }
});

Atualizar seu Trabalho de Serviço

Instalar uma nova versão do Service Worker

Se você fizer alterações no código do Service Worker e implantar o novo arquivo do Service Worker no servidor Web, os dispositivos dos usuários começarão gradualmente a usar o novo Service Worker.

Sempre que um usuário navega até uma das páginas do seu aplicativo, o navegador que está executando o aplicativo verifica se uma nova versão do Service Worker está disponível no servidor. O navegador detecta novas versões comparando o conteúdo entre o Trabalho de Serviço existente e o novo Service Worker. Quando uma alteração é detectada, o novo Service Worker é instalado (o install evento é disparado) e, em seguida, o novo Service Worker aguarda que o Trabalhador de Serviço existente pare de ser usado no dispositivo.

Na prática, isso significa que pode haver dois Trabalhadores de Serviço em execução ao mesmo tempo, mas apenas um intercepta as solicitações de rede do aplicativo. Quando o aplicativo é fechado, o Trabalho de Serviço existente deixa de ser usado. Na próxima vez que o aplicativo for aberto, o novo Service Worker será ativado. O activate evento é disparado e o novo Service Worker inicia a interceptação de fetch eventos.

Você pode ativar com força o novo Service Worker assim que ele for instalado, usando self.skipWaiting() no manipulador de eventos do install Service Worker.

Para saber mais sobre como os Trabalhadores do Serviço são atualizados, consulte Atualizando o Trabalho de Serviço no web.dev.

Atualizar seus arquivos estáticos armazenados em cache

Ao pré-armazenar recursos estáticos, como arquivos de planilha de estilos CSS, conforme descrito em recursos de pré-cache, seu aplicativo usa apenas as versões armazenadas em cache dos arquivos e não tenta baixar novas versões.

Para garantir que os usuários obtenham as alterações mais recentes nos recursos estáticos usados pelo seu aplicativo, use uma convenção de nomenclatura de quebra de cache e atualize seu código do Service Worker.

A quebra de cache significa que cada arquivo estático é nomeado de acordo com sua versão. Isso pode ser obtido de várias maneiras, mas geralmente envolve o uso de uma ferramenta de build que lê o conteúdo de um arquivo e gera uma ID exclusiva com base no conteúdo. Essa ID pode ser usada para nomear o arquivo estático armazenado em cache.

Em seguida, atualize o código do Service Worker para armazenar em cache os novos recursos estáticos durante install:

// The name of the new cache your app uses.
const CACHE_NAME = "my-app-cache-v2";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles-124656.css", "app-576391.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache the new static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

// Listen to the `activate` event to clear old caches.
self.addEventListener("activate", event => {
  async function deleteOldCaches() {
    // List all caches by their names.
    const names = await caches.keys();
    await Promise.all(names.map(name => {
      if (name !== CACHE_NAME) {
        // If a cache's name is the current name, delete it.
        return caches.delete(name);
      }
    }));
  }

  event.waitUntil(deleteOldCaches());
});

Compare os CACHE_NAME valores e PRE_CACHED_RESOURCES entre o snippet de código acima e o dos recursos pré-cache. Quando esse novo Service Worker for instalado, um novo cache será criado e os novos recursos estáticos serão baixados e armazenados em cache. Quando o Service Worker for ativado, o cache antigo será excluído. Neste ponto, o usuário terá a nova versão do aplicativo.

Às vezes, fazer alterações no Trabalho de Serviço pode ser complexo. Use uma biblioteca como a Workbox para simplificar a etapa de compilação de recursos estáticos e o código do Service Worker.

Teste para conexões de rede em seu PWA

É útil saber quando uma conexão de rede está disponível, a fim de sincronizar dados ou informar aos usuários que a rede status foi alterada.

Use as seguintes opções para testar a conectividade de rede:

A navigator.onLine propriedade é um booliano que permite que você saiba o status atual da rede. Se o valor for true, o usuário estará online; caso contrário, o usuário estará offline.

Para saber mais, consulte navigator.onLine no MDN.

Eventos online e offline

Você pode agir quando a conectividade de rede for alterada. Você pode ouvir e tomar medidas em resposta a eventos de rede. Os eventos estão disponíveis nos windowelementos , documente document.body , conforme mostrado abaixo:

window.addEventListener("online",  function(){
    console.log("You are online!");
});
window.addEventListener("offline", function(){
    console.log("Oh no, you lost your network connection.");
});

Para saber mais, consulte Navigator.onLine no MDN.

Outras funcionalidades

A responsabilidade main do Service Worker é tornar seu aplicativo mais rápido e confiável no caso de uma conexão de rede instável. Os Trabalhadores de Serviço usam principalmente o fetch evento e Cache a API para fazer isso, mas podem usar outras APIs para cenários avançados, como:

  • Sincronização em segundo plano de dados.
  • Sincronização periódica de dados.
  • Downloads de arquivos em segundo plano grandes.
  • Tratamento e notificações de mensagens push.

Sincronização em segundo plano

Use a API de Sincronização em Segundo Plano para permitir que os usuários continuem usando seu aplicativo e executem ações mesmo quando o dispositivo do usuário estiver offline.

Por exemplo, um aplicativo de email pode permitir que seus usuários componham e enviem mensagens a qualquer momento. O front-end do aplicativo pode tentar enviar a mensagem imediatamente e, se o dispositivo estiver offline, o Service Worker poderá capturar a solicitação com falha e usar a API de Sincronização em Segundo Plano para adiar a tarefa até estar conectado.

Para saber mais, confira Usar a API de Sincronização em Segundo Plano para sincronizar dados com o servidor.

Sincronização em segundo plano de período

A API de Sincronização de Antecedentes Periódicos permite que PWAs recuperem conteúdo novo periodicamente, em segundo plano, para que os usuários possam acessar imediatamente o conteúdo quando abrirem o aplicativo novamente.

Usando a API de Sincronização de Antecedentes Periódicos, as PWAs não precisam baixar novos conteúdos (como novos artigos) enquanto o usuário estiver usando o aplicativo. Baixar conteúdo pode retardar a experiência, portanto, o aplicativo pode recuperar o conteúdo em um momento mais conveniente.

Para saber mais, confira Usar a API de Sincronização de Planos de Fundo Periódico para obter conteúdo recente regularmente.

Downloads de arquivos em segundo plano grandes

A API de Busca em Segundo Plano permite que PWAs delegam completamente o download de grandes quantidades de dados para o mecanismo do navegador. Dessa forma, o aplicativo e o Service Worker não precisam estar em execução enquanto o download estiver em andamento.

Essa API é útil para aplicativos que permitem que os usuários baixem arquivos grandes (como música, filmes ou podcasts) para casos de uso offline. O download é delegado ao mecanismo do navegador, que sabe como lidar com uma conexão intermitente ou até mesmo uma perda completa de conectividade.

Para saber mais, confira Usar a API de Busca em Segundo Plano para buscar arquivos grandes quando o aplicativo ou o Service Worker não estiver em execução.

Enviar mensagens por push

Mensagens push podem ser enviadas para seus usuários sem que eles precisem usar o aplicativo no momento. Um Service Worker pode ouvir mensagens por push enviadas pelo servidor mesmo que o aplicativo não esteja em execução e exibir uma notificação na central de notificação do sistema operacional.

Para saber mais, consulte Reengajamento de usuários com mensagens push.

Depuração com DevTools

Usando o Microsoft Edge DevTools, você pode ver se o Service Worker foi registrado corretamente e ver em qual estado de ciclo de vida o Service Worker está atualmente. Além disso, você pode depurar o código JavaScript em seu Service Worker.

Para saber mais, confira Depurar seu Trabalho de Serviço.

Confira também