Mentalidade de service worker

O que deve ser considerado ao considerar os service workers.

Os service workers são poderosos e absolutamente vale a pena aprender. Com eles, você pode oferecer uma experiência totalmente nova aos seus usuários. Seu site pode ser carregado instantaneamente. Pode funcionar off-line. Ele pode ser instalado como um aplicativo específico de plataforma e parecer muito sofisticado, mas com o alcance e a liberdade da Web.

Mas os service workers são diferentes de tudo que a maioria dos desenvolvedores da Web está acostumado. Eles vêm com uma curva de aprendizado íngreme e alguns obstáculos que você precisa tomar cuidado.

Recentemente, o Google Developers e eu colaboramos em um projeto chamado Service Workies, um jogo sem custo financeiro para entender os service workers. Enquanto o construí e trabalhei com os altos e baixos dos service workers, encontrei alguns problemas. O que mais me ajudou foi criar algumas metáforas representativas. Nesta postagem, vamos explorar esses modelos mentais e entender os traços paradoxais que tornam os service workers ao mesmo tempo complicados e incríveis.

A mesma, mas diferente

Ao codificar seu service worker, muitas coisas parecerão familiares. Você pode usar seus novos recursos favoritos da 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 de costume.

Mas outros comportamentos de service worker causam confusão. Isso é útil especialmente quando você atualiza a página e não visualiza as alterações de código 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 nova camada que fica no meio.

Um service worker atua como uma camada intermediária entre o cliente e o servidor

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

A camada de 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 uma atualização de página para atualizar o código implantado em um servidor. Cada camada tem as próprias regras de atualização.

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

Avançado, mas limitado

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

Eles são limitados por design ao máximo que os service workers podem fazer. Eles não podem fazer nada síncrono ou na mesma conversa que seu site. Isso significa que não é possível acessar:

  • localStorage
  • o DOM
  • a janela

A boa notícia é que existem várias maneiras de sua página se comunicar com o service worker, incluindo o postMessage direto, os canais de mensagens um para um e os canais de transmissão um para muitos.

Longa duração, mas curta

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 service worker para que ele esteja pronto na próxima vez que o usuário voltar ao seu 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 que permite que um site funcione off-line. O service worker pode exibir uma versão em cache da própria página, mesmo que o usuário não tenha conexão com a Internet.

No Service Workies, visualizamos esse conceito com Kolohe, um service worker amigável, que intercepta e processa solicitações.

Parado

Apesar de os service workers parecerem imortais, eles podem ser interrompidos a qualquer momento. O navegador não quer desperdiçar recursos com um service worker que não esteja fazendo nada no momento. Ser interrompido não é o mesmo que ser encerrado. O service worker permanece instalado e ativado. É simplesmente colocado em modo de suspensão. Na próxima vez que for necessário (por exemplo, para processar uma solicitação), o navegador o reativará.

waitUntil

Devido à possibilidade constante de ser colocado em suspensão, seu service worker precisa de uma maneira de permitir que o navegador saiba quando está fazendo algo importante e não tem vontade de tirar uma soneca. É aqui que a event.waitUntil() entra em cena. Esse método estende o ciclo de vida em que é usado, impedindo que ele seja interrompido e passe 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 não está concluído até que o cache assets tenha sido 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 esse processo de inicialização/interrupção ocorre, o escopo global do service worker é redefinido. Portanto, tenha cuidado para não usar nenhum estado global em seu service worker. Caso contrário, você ficará triste da 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 service worker registrará um número, por exemplo, 0.13981866382421893. A variável hasHandledARequest também muda para true. Agora o service worker fica inativo 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 o ativará. O script será avaliado novamente. Agora, hasHandledARequest foi redefinido como 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 itens como canais de mensagem pode causar bugs: você terá uma nova instância sempre que o service worker parar/iniciar.

No capítulo 3 do Service Workies, vemos que o service worker interrompido perde todas as cores enquanto espera ser ativado.

visualização de um service worker interrompido

Juntos, mas separados

Sua página só pode ser controlada por um service worker de cada vez. Mas ele pode 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á realmente editando o service worker. Os service workers são imutáveis. Em vez disso, você está criando um conteúdo totalmente novo. Esse novo service worker (vamos chamá-lo de SW2) vai ser instalado, mas ainda não ativado. Ele precisa aguardar o encerramento do service worker (SW1) atual (quando o usuário sair do site).

Brincar com os caches de outro service worker

Durante a instalação, o SW2 pode fazer configurações, geralmente criando e preenchendo caches. Atenção: esse novo service worker tem acesso a tudo o que o service worker atual tem acesso. Se você não tiver cuidado, o novo service worker de espera pode estragar as coisas para o atual. Alguns exemplos que podem causar problemas:

  • O SW2 pode excluir o cache que 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.

Pular skipAguardando

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

Começar sem limpar

A maneira de evitar que os service workers batam uns aos outros é garantir que eles usem caches diferentes. A maneira mais fácil de fazer isso é criar uma versão dos nomes de cache que eles usam.

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

Limpeza final

Quando o service worker atingir o estado activated, você vai saber que ele foi transferido e o service worker anterior é redundante. Neste ponto, é importante fazer a limpeza após o service worker antigo. Ela respeita os limites de armazenamento em cache dos usuários e também evita 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 a iteração é feita nos 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 está aguardando o script mais recente armazenado em assets-2. Mas se você não tiver excluído o cache antigo, o caches.match('app.js') retornará o antigo de assets-1 e provavelmente corromperá seu site.

Tudo o que é necessário para uma 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 batam uns aos outros exige um pouco de trabalho e disciplina, mas vale o trabalho.

Mentalidade de service worker

Ter a mentalidade certa enquanto pensa nos service workers ajudará você a construir a sua com confiança. Depois de pegar o jeito deles, você será capaz de criar experiências incríveis para os usuários.

Se você quiser entender tudo isso jogando um jogo, então está com sorte! Acesse Service Workies para aprender como os service workers eliminem os feras off-line.