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 app específico da plataforma e ter a mesma aparência, 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.
Recentemente, os desenvolvedores do Google e eu colaboramos em um projeto, 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 worker de serviço, muitas coisas vão parecer familiares. Você pode usar seus novos recursos de linguagem JavaScript favoritos. 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 totalmente nova que fica no meio.
Pense no service worker como uma espécie de extensão do navegador que o 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 regras exclusivas 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:
- funcionar perfeitamente mesmo quando o usuário estiver off-line
- ter melhorias significativas na performance com o armazenamento em cache
- usar notificações push
- ser instalado como um PWA
Apesar de tudo o que os workers de serviço podem fazer, eles 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 Workers, mostramos esse conceito com o Kolohe (um service worker amigável) interceptando e processando solicitações.
Interrompida
Embora os service workers pareçam 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. A interrupção não é a mesma coisa que encerramento: o service worker permanece instalado e ativado. Ele é colocado em suspensão. 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 a instalação do service worker não será concluída até que o cache assets
seja 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, tome cuidado para não usar nenhum estado global no service worker, ou você vai ficar triste na próxima vez que ele for ativado 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 worker de serviço. 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.
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 immutable. Você está criando um novo. Esse novo worker de serviço (vamos chamá-lo de SW2) será instalado, mas ainda não será ativado. Ele precisa esperar que o service worker atual (SW1) seja encerrado (quando o usuário sair do site).
Mexer com os caches de outro service worker
Durante a instalação, o SW2 pode configurar as coisas, 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 tiver cuidado, o novo service worker em espera pode bagunçar o 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.
Limpeza final
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 é necessário para limpar depois de 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 se choquem exige um pouco de trabalho e disciplina, mas vale a pena.
Mentalidade de 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, onde você vai aprender as maneiras de usar o service worker para derrotar as feras off-line.