Mentalidade de service worker

Como pensar sobre os service workers.

Os service workers são poderosos e valem a pena aprender. Eles permitem que você ofereça um nível totalmente novo de experiência aos usuários. Seu site pode carregar instantaneamente. Ele pode funcionar off-line. Ele pode ser instalado como um aplicativo específico da plataforma e parecer tão polido, mas com o alcance e a liberdade da web.

Mas os workers de serviço são diferentes de tudo o que a maioria dos desenvolvedores da Web está acostumada. Eles têm uma curva de aprendizado íngreme e alguns problemas que você precisa conhecer.

O Google Developers e eu recentemente trabalhamos em um projeto, o Service Workies, um jogo sem custo financeiro para entender os service workers. Durante o desenvolvimento e o trabalho com as complexidades dos service workers, encontrei alguns problemas. O que mais me ajudou foi criar algumas metáforas descritivas. Neste post, vamos analisar esses modelos mentais e entender as características paradoxais que tornam os service workers ao mesmo tempo complicados e incríveis.

O mesmo, mas diferente

Ao codificar seu service worker, muitas coisas parecerão familiares. Você pode usar seus novos recursos favoritos de linguagem JavaScript. Você ouve eventos de ciclo de vida da mesma forma que os eventos de interface. Você gerencia o fluxo de controle com promessas, como já está acostumado.

Mas outros comportamentos do service worker fazem você se confundir. Principalmente quando você atualiza a página e as mudanças no código não são aplicadas.

Uma nova camada

Normalmente, ao criar um site, você tem apenas duas camadas para pensar: o cliente e o servidor. O service worker é uma camada nova que fica no meio.

Um worker de serviço atua como uma camada intermediária entre o cliente e o servidor.

Pense no service worker como uma espécie de extensão do navegador, que seu site pode instalar no navegador do usuário. Depois de instalado, o service worker amplia o navegador do seu site com uma camada intermediária poderosa. Essa camada de service worker pode interceptar e processar todas as solicitações feitas pelo site.

A camada do service worker tem o próprio ciclo de vida, independente da guia do navegador. Uma simples atualização de página não é suficiente para atualizar um service worker, assim como você não espera que uma atualização de página atualize o código implantado em um servidor. Cada camada tem as próprias regras de atualização.

No jogo Service Workies, abordamos muitos detalhes do ciclo de vida do service worker e oferecemos muita prática para trabalhar com ele.

Poderoso, mas limitado

Ter um service worker no seu site oferece benefícios incríveis. Seu site pode:

Apesar de serem capazes de fazer muitas coisas, os service workers são limitados pelo design. Eles não podem fazer nada síncrono ou na mesma linha de execução do seu site. Isso significa que não há acesso a:

  • localStorage
  • o DOM
  • a janela

A boa notícia é que há várias maneiras de a página se comunicar com o service worker, incluindo postMessage direto, canais de mensagens individuais e canais de transmissão individuais.

De longa duração, mas de curta duração

Um service worker ativo continua ativo mesmo depois que um usuário sai do seu site ou fecha a guia. O navegador mantém esse worker do serviço para que ele esteja pronto na próxima vez que o usuário retornar ao site. Antes que a primeira solicitação seja feita, o service worker tem a chance de interceptá-la e assumir o controle da página. Isso permite que um site funcione off-line: o service worker pode exibir uma versão armazenada em cache da própria página, mesmo que o usuário não tenha conexão com a Internet.

Em Service Workies, mostramos esse conceito com o Kolohe (um service worker amigável) interceptando e processando solicitações.

Interrompida

Apesar dos service workers parecerem imortais, eles podem ser interrompidos quase a qualquer momento. O navegador não quer desperdiçar recursos em um service worker que não está fazendo nada no momento. Ser interrompido não é o mesmo que encerrar. O service worker permanece instalado e ativado. Ele vai ser suspenso. Na próxima vez que for necessário (por exemplo, para processar uma solicitação), o navegador vai reativá-lo.

waitUntil

Devido à constante possibilidade de ser colocado em suspensão, o worker de serviço precisa de uma maneira de informar ao navegador quando ele está fazendo algo importante e não quer tirar uma soneca. É aí que entra a event.waitUntil(). Esse método estende o ciclo de vida em que é usado, evitando que ele seja interrompido e que avance para a próxima fase do ciclo de vida até que estejamos prontos. Isso nos dá tempo para configurar caches, buscar recursos da rede etc.

Este exemplo informa ao navegador que nosso service worker só termina a instalação depois que o cache assets é criado e preenchido com a imagem de uma espada:

self.addEventListener("install", event => {
  event.waitUntil(
    caches.open("assets").then(cache => {
      return cache.addAll(["/weapons/sword/blade.png"]);
    })
  );
});

Cuidado com o estado global

Quando essa inicialização/parada acontece, o escopo global do service worker é redefinido. Portanto, tenha cuidado para não usar nenhum estado global no seu service worker ou você ficará triste na próxima vez que ele for reativado e tiver um estado diferente do esperado.

Considere este exemplo que usa um estado global:

const favoriteNumber = Math.random();
let hasHandledARequest = false;

self.addEventListener("fetch", event => {
  console.log(favoriteNumber);
  console.log(hasHandledARequest);
  hasHandledARequest = true;
});

Em cada solicitação, esse worker de serviço registra um número, digamos 0.13981866382421893. A variável hasHandledARequest também muda para true. Agora, o service worker fica ocioso por um tempo, então o navegador o interrompe. Na próxima vez que houver uma solicitação, o service worker será necessário novamente, então o navegador vai ativá-lo. O script é avaliado novamente. Agora hasHandledARequest foi redefinido para false, e favoriteNumber é algo completamente diferente: 0.5907281835659033.

Não é possível confiar no estado armazenado em um service worker. Além disso, a criação de instâncias de coisas como canais de mensagens pode causar bugs: você vai receber uma instância nova toda vez que o worker de serviço for interrompido/iniciado.

No capítulo 3 dos service workers, mostramos que o service worker parado perde toda a cor enquanto espera para ser ativado.

visualização de um service worker parado

Juntos, mas separados

Sua página só pode ser controlada por um service worker por vez. No entanto, é possível ter dois service workers instalados ao mesmo tempo. Quando você faz uma alteração no código do service worker e atualiza a página, não está editando o service worker. Os service workers são imutáveis. Em vez disso, você está criando um novo. Esse novo worker de serviço (vamos chamá-lo de SW2) será instalado, mas ainda não será ativado. É preciso esperar o encerramento do service worker atual (SW1) quando o usuário sair do seu site.

Mexer com os caches de outro service worker

Durante a instalação, o SW2 recebe configurações, geralmente criando e preenchendo caches. Mas atenção: esse novo service worker tem acesso a tudo que o service worker atual tem. Se você não for uma pessoa cuidadosa, seu novo service worker em espera poderá acabar atrapalhando o service worker atual. Alguns exemplos que podem causar problemas:

  • O SW2 pode excluir um cache que o SW1 está usando ativamente.
  • O SW2 pode editar o conteúdo de um cache que o SW1 está usando, fazendo com que o SW1 responda com recursos que a página não espera.

Ignorar skipWaiting

Um service worker também pode usar o método skipWaiting() de risco para assumir o controle da página assim que a instalação for concluída. Isso geralmente não é uma boa ideia, a menos que você esteja tentando substituir intencionalmente um worker de serviço com bugs. O novo service worker pode estar usando recursos atualizados que a página atual não espera, o que pode causar erros e bugs.

Começar a limpar

Para evitar que os service workers se sobreponham, use caches diferentes. A maneira mais fácil de fazer isso é criar versões dos nomes de cache usados.

const version = 1;
const assetCacheName = `assets-${version}`;

self.addEventListener("install", event => {
  caches.open(assetCacheName).then(cache => {
    // confidently do stuff with your very own cache
  });
});

Ao implantar um novo service worker, você vai aumentar o version para que ele faça o que precisa com um cache totalmente separado do service worker anterior.

visualização de um cache

Finalizar limpeza

Quando o worker do serviço chega ao estado activated, você sabe que ele assumiu o controle e que o worker anterior está redundante. Nesse ponto, é importante limpar o service worker antigo. Além de respeitar os limites de armazenamento em cache dos usuários, isso também pode evitar bugs não intencionais.

O método caches.match() é um atalho usado com frequência para recuperar um item de qualquer cache em que haja uma correspondência. Mas ele itera os caches na ordem em que foram criados. Digamos que você tenha duas versões de um arquivo de script app.js em dois caches diferentes: assets-1 e assets-2. Sua página espera o script mais recente armazenado em assets-2. No entanto, se você não tiver excluído o cache antigo, o caches.match('app.js') vai retornar o antigo de assets-1 e provavelmente vai quebrar seu site.

Tudo o que é preciso para fazer a limpeza após os service workers anteriores é excluir qualquer cache que o novo service worker não precise:

const version = 2;
const assetCacheName = `assets-${version}`;

self.addEventListener("activate", event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== assetCacheName){
            return caches.delete(cacheName);
          }
        });
      );
    });
  );
});

Impedir que os service workers atrapalhem uns aos outros exige um pouco de trabalho e disciplina, mas vale a pena o problema.

Mentalidade do service worker

Ter a mentalidade certa ao pensar sobre os workers de serviço vai ajudar você a criar o seu com confiança. Depois de entender como eles funcionam, você poderá criar experiências incríveis para seus usuários.

Se você quer entender tudo isso jogando um jogo, você está com sorte! Acesse o Service Workies (link em inglês) para aprender sobre o service worker e acabar com os problemas off-line.