Mentalidade de service worker

Como pensar sobre os service workers.

Os service workers são poderosos e muito vale a pena aprender. Eles permitem que você ofereça um nível totalmente novo de experiência aos usuários. Seu site pode ser carregado 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.

No entanto, os service workers são diferentes de tudo o que a maioria dos desenvolvedores Web está acostumada. Eles vêm com uma curva de aprendizado íngreme e alguns problemas que você precisa tomar cuidado.

O Google Developers e eu recentemente trabalhamos em um projeto, o Service Workies, um jogo sem custo financeiro para entender os service workers. Ao criá-la e trabalhar com os detalhes complexos dos service workers, tive 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 desafiadores 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ê detecta eventos de ciclo de vida da mesma forma que faz com eventos de interface. Você gerencia o fluxo de controle com promessas como de costume.

Mas outro comportamento de service worker faz você coçar a cabeça em confusão. Principalmente quando você atualiza a página e não vê 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 camada totalmente nova 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 do navegador, que seu site pode instalar no navegador do usuário. Depois de instalado, o service worker estende o navegador para seu site com uma camada intermediária eficiente. Essa camada de service worker pode interceptar e processar todas as solicitações feitas pelo seu 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 não se 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 os vários detalhes do ciclo de vida do service worker e vamos dar a você muita prática para trabalhar com ele.

Eficiente, mas limitado

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

Com tudo o que os service workers podem fazer, eles são limitados por design. Eles não podem fazer nada síncrono ou na mesma sequência do seu site. Isso significa que você não tem acesso a:

  • localStorage
  • o DOM
  • a janela

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

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

Um service worker ativo continua ativo mesmo depois que um usuário sai do site ou fecha a guia. O navegador mantém esse service worker por perto para que ele esteja pronto na próxima vez que o usuário voltar ao site. Antes da primeira solicitação ser 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.

Em Service Workies, visualizamos esse conceito com 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 ser encerrado. O service worker permanece instalado e ativado. Ele simplesmente adormece. Na próxima vez em que for necessário (por exemplo, para lidar com uma solicitação), o navegador o ativará novamente.

waitUntil

Devido à possibilidade constante de entrar em suspensão, seu service worker precisa de uma maneira de informar ao navegador quando ele está fazendo algo importante e não sente vontade de tirar uma soneca. É aí que a event.waitUntil() entra em jogo. Esse método estende o ciclo de vida em que é usado, evitando que 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 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 ocorre, 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 service worker registrará um número. Por exemplo, 0.13981866382421893. A variável hasHandledARequest também muda para true. Agora o service worker fica ocioso por um pouco, e o navegador o interrompe. Na próxima vez que houver uma solicitação, o service worker será necessário novamente e, por isso, o navegador o ativará. O script é avaliado novamente. Agora, hasHandledARequest é 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 coisas como canais de mensagens pode causar bugs: você terá uma instância totalmente nova sempre que o service worker parar/iniciar.

No capítulo 3 do Service Workies, vemos o service worker parado perder toda a cor enquanto espera ser ativado.

visualização de um service worker parado

Juntos, mas separados

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

Mexer nos caches de outro service worker

Durante a instalação, o SW2 recebe configurações, geralmente criando e preenchendo caches. Mas um aviso: esse novo service worker tem acesso a tudo o que o service worker atual pode acessar. Se você não for uma pessoa cuidadosa, seu novo service worker em espera poderá acabar atrapalhando o service worker atual. Confira 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 arriscado método skipWaiting() para assumir o controle da página assim que a instalação for concluída. Geralmente, não é uma boa ideia, a menos que você esteja tentando substituir um service worker com bugs. O novo service worker pode estar usando recursos atualizados que a página atual não espera, gerando erros e bugs.

Começar limpeza

Uma maneira de evitar que os service workers sejam prejudicados é garantir que eles usem caches diferentes. A maneira mais fácil de fazer isso é criar uma versão 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ê usará o version para que ele faça o que for necessário com um cache totalmente separado do service worker anterior.

visualização de um cache

Finalizar limpeza

Quando o service worker atingir o estado activated, você saberá que ele assumiu o controle e o service worker anterior será redundante. Neste ponto, é importante fazer a limpeza depois do service worker antigo. Além de respeitar o público limites de armazenamento em cache, mas 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 itera pelos 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á esperando o script mais recente armazenado em assets-2. No entanto, se você não excluiu o cache antigo, o caches.match('app.js') vai retornar o cache antigo de assets-1 e provavelmente vai corromper 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 enquanto pensa em service workers ajuda você a desenvolver a sua com confiança. Depois de pegar o jeito, você será capaz de criar experiências incríveis para seus usuários.

Se você quiser entender tudo isso jogando um jogo, então está com sorte! Jogue Service Workies, onde você vai aprender como o service worker vai ajudar a acabar com os monstros off-line.