É difícil saber o que os service workers estão fazendo sem entender o ciclo de vida deles. O funcionamento interno delas vai parecer opaco, até mesmo arbitrário. Isso ajuda a lembrar que, como em qualquer outra API de navegador, os comportamentos dos service workers são bem definidos, especificados, e viabilizam aplicativos off-line, além de facilitar as atualizações sem prejudicar a experiência do usuário.
Antes de mergulhar no Workbox, é importante entender o ciclo de vida do service worker para que o que o Workbox faz faça sentido.
Definição de termos
Antes de entrar no ciclo de vida do service worker, vale a pena definir alguns termos sobre como esse ciclo de vida funciona.
Controle e escopo
A ideia de controle é crucial para entender como os service workers funcionam. Uma página descrita como controlada por um service worker é aquela que permite que um service worker intercepte solicitações de rede em nome dele. O service worker está presente e pode fazer o trabalho para a página dentro de um determinado escopo.
Escopo
O escopo de um service worker é determinado pela localização dele em um servidor da Web.
Se um service worker for executado em uma página localizada em /subdir/index.html
e estiver em /subdir/sw.js
,
o escopo do service worker seja /subdir/
.
Para ver o conceito de escopo em ação, veja este exemplo:
- Acessar
https://service-worker-scope-viewer.glitch.me/subdir/index.html.
Será exibida uma mensagem informando que nenhum service worker está controlando a página.
No entanto, essa página registra um service worker de
https://service-worker-scope-viewer.glitch.me/subdir/sw.js
. - Recarregue a página. Como o service worker foi registrado e agora está ativo, ela está controlando a página. Um formulário contendo o escopo do service worker estado atual, e seu URL ficará visível. Observação: atualizar a página não tem nada a ver com o escopo, mas sim o ciclo de vida do service worker, que será explicado mais adiante.
- Agora acesse https://service-worker-scope-viewer.glitch.me/index.html. Mesmo que um service worker esteja registrado nessa origem, ainda haverá uma mensagem informando que não há service worker atual. Isso ocorre porque essa página não está no escopo do service worker registrado.
O escopo limita quais páginas o service worker controla.
Neste exemplo, isso significa que o service worker carregado de /subdir/sw.js
só pode controlar páginas localizadas em /subdir/
ou na subárvore dele.
Confira acima como o escopo funciona por padrão,
mas o escopo máximo permitido pode ser substituído pela configuração
Cabeçalho de resposta do Service-Worker-Allowed
,
além de transmitir
scope
ao método register
.
A menos que haja um bom motivo para limitar o escopo do service worker a um subconjunto de uma origem,
carregar um service worker a partir do diretório raiz do servidor da Web para que seu escopo seja o mais amplo possível,
e não se preocupe com o cabeçalho Service-Worker-Allowed
. Assim, é muito mais simples para todos.
Cliente
Quando dizem que um service worker está controlando uma página, na verdade, na verdade está controlando um cliente.
Um cliente é qualquer página aberta cujo URL esteja no escopo desse service worker.
Especificamente, essas são instâncias de uma WindowClient
.
O ciclo de vida de um novo service worker
Para que um service worker controle uma página, ela precisa existir primeiro, por assim dizer. Vamos começar com o que acontece quando um novo service worker é implantado em um site sem service worker ativo.
Registro
O registro é a etapa inicial do ciclo de vida do service worker:
<!-- In index.html, for example: -->
<script>
// Don't register the service worker
// until the page has fully loaded
window.addEventListener('load', () => {
// Is service worker available?
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(() => {
console.log('Service worker registered!');
}).catch((error) => {
console.warn('Error registering service worker:');
console.warn(error);
});
}
});
</script>
Esse código é executado na linha de execução principal e faz o seguinte:
- Como a primeira visita do usuário a um site ocorre sem um service worker registrado, aguarde até que a página esteja totalmente carregada antes de registrar uma. Isso evita contenção de largura de banda se o service worker armazena algo em cache previamente.
- O service worker tem bom suporte, uma verificação rápida ajuda a evitar erros em navegadores que não são compatíveis.
- Quando a página estiver totalmente carregada e se o service worker for compatível, registre
/sw.js
.
Alguns pontos importantes para entender são:
- Os service workers são disponível apenas por HTTPS ou localhost.
- Se o conteúdo de um service worker tiver erros de sintaxe, o registro falhará e o service worker será descartado.
- Lembrete: os service workers funcionam dentro de um escopo. Aqui, o escopo é a origem inteira, já que foi carregado do diretório raiz.
- Quando o registro começa, o estado do service worker é definido como
'installing'
.
Quando o registro terminar, a instalação será iniciada.
Instalação
Um service worker dispara seu
install
após a inscrição.
install
é chamado apenas uma vez por service worker e não é acionado novamente até que seja atualizado.
Um callback para o evento install
pode ser registrado no escopo do worker com addEventListener
:
// /sw.js
self.addEventListener('install', (event) => {
const cacheKey = 'MyFancyCacheName_v1';
event.waitUntil(caches.open(cacheKey).then((cache) => {
// Add all the assets in the array to the 'MyFancyCacheName_v1'
// `Cache` instance for later use.
return cache.addAll([
'/css/global.bc7b80b7.css',
'/css/home.fe5d0b23.css',
'/js/home.d3cc4ba4.js',
'/js/jquery.43ca4933.js'
]);
}));
});
Isso cria uma nova instância de Cache
e pré-armazena os recursos em cache.
Teremos muitas oportunidades de falar sobre pré-armazenamento em cache mais tarde,
então vamos nos concentrar no papel
event.waitUntil
event.waitUntil
aceita uma promessa.
e espera até que a promessa seja resolvida.
Nesse exemplo, essa promessa faz duas coisas assíncronas:
- Cria uma nova instância de
Cache
chamada'MyFancyCache_v1'
. - Após a criação do cache,
um conjunto de URLs de recursos é pré-armazenado em cache usando seu
método
addAll
.
A instalação falhará se as promessas transmitidas para event.waitUntil
forem
rejeitada.
Se isso acontecer, o service worker será descartado.
Se as promessas forem resolvidas,
será bem-sucedida, e o estado do service worker mudará para 'installed'
e, em seguida, será ativado.
Ativação
Se o registro e a instalação forem concluídos,
o service worker é ativado e o estado dele se torna 'activating'
.
O trabalho pode ser feito durante a ativação no
activate
evento.
Uma tarefa típica nesse evento é limpar caches antigos,
mas, para um novo service worker,
se isso não for relevante para o momento,
e será ampliada quando abordarmos as atualizações dos service workers.
Para novos service workers, activate
é disparado imediatamente depois que install
for bem-sucedido.
Quando a ativação for concluída,
o estado do service worker se torna 'activated'
.
Observe que, por padrão,
o novo service worker não começará a controlar a página até a próxima navegação ou atualização de página.
Como gerenciar atualizações do service worker
Depois que o primeiro service worker é implantado, ele provavelmente precisará ser atualizado mais tarde. Por exemplo, uma atualização pode ser necessária se ocorrerem alterações na lógica do processamento de solicitações ou de pré-armazenamento em cache.
Quando as atualizações acontecem
Os navegadores verificam se há atualizações para um service worker quando:
- O usuário navega para uma página dentro do escopo do service worker.
navigator.serviceWorker.register()
é chamado com um URL diferente do service worker atualmente instalado, mas não altere o URL de um service worker.navigator.serviceWorker.register()
for chamado com o mesmo URL do service worker instalado, mas com um escopo diferente. Novamente, evite isso mantendo o escopo na raiz de uma origem, se possível.- Quando eventos como
'push'
ou'sync'
foram acionados nas últimas 24 horas. Mas não se preocupe com esses eventos ainda.
Como as atualizações acontecem
Saber quando o navegador atualiza um service worker é importante, mas o “como” também. Supondo que o URL ou o escopo de um service worker não mude, um service worker atualmente instalado só atualiza para uma nova versão se seu conteúdo for alterado.
Os navegadores detectam alterações de duas maneiras:
- Qualquer alteração byte por byte a scripts solicitados pelo
importScripts
, se aplicável. - Todas as alterações no código de nível superior do service worker que afeta a impressão digital gerada pelo navegador.
O navegador faz um grande trabalho aqui. Para garantir que o navegador tenha tudo o que precisa para detectar alterações no conteúdo de um service worker com segurança, não instrui o cache HTTP a mantê-lo, nem altera o nome do arquivo. O navegador executa verificações de atualização automaticamente quando há uma navegação para uma nova página no escopo de um service worker.
Como acionar manualmente verificações de atualização
Com relação às atualizações, a lógica de registro geralmente não deve mudar. No entanto, uma exceção seria se as sessões em um site fossem de longa duração. Isso pode acontecer em aplicativos de página única, em que solicitações de navegação são raras, porque o aplicativo normalmente encontra uma solicitação de navegação no início do ciclo de vida. Nessas situações, uma atualização manual pode ser acionada na linha de execução principal:
navigator.serviceWorker.ready.then((registration) => {
registration.update();
});
Para sites tradicionais, ou em qualquer caso em que as sessões de usuário não são de longa duração, o acionamento de atualizações manuais provavelmente não é necessário.
Instalação
Ao usar um bundler para gerar recursos estáticos,
esses recursos terão hashes no nome,
como framework.3defa9d2.js
.
Suponha que alguns desses recursos sejam pré-armazenados em cache para acesso off-line mais tarde.
Isso exigiria uma atualização do service worker para pré-armazenar em cache os recursos atualizados:
self.addEventListener('install', (event) => {
const cacheKey = 'MyFancyCacheName_v2';
event.waitUntil(caches.open(cacheKey).then((cache) => {
// Add all the assets in the array to the 'MyFancyCacheName_v2'
// `Cache` instance for later use.
return cache.addAll([
'/css/global.ced4aef2.css',
'/css/home.cbe409ad.css',
'/js/home.109defa4.js',
'/js/jquery.38caf32d.js'
]);
}));
});
Duas coisas são diferentes do primeiro exemplo de evento install
mostrado anteriormente:
- Uma nova instância
Cache
com uma chave de'MyFancyCacheName_v2'
é criada. - Os nomes dos recursos armazenados em cache foram alterados.
Uma coisa a ser observada é que um service worker atualizado é instalado junto com o anterior. Isso significa que o antigo service worker ainda controla as páginas abertas e, após a instalação, o novo entra em um estado de espera até ser ativado.
Por padrão, um novo service worker será ativado quando nenhum cliente estiver sendo controlado pelo antigo. Isso ocorre quando todas as guias abertas do site relevante são fechadas.
Ativação
Quando um service worker atualizado é instalado e a fase de espera termina,
ela é ativada e o service worker antigo é descartado.
Uma tarefa comum a ser executada no evento activate
de um service worker atualizado é limpar caches antigos.
Remova os caches antigos conseguindo as chaves de todas as instâncias Cache
abertas com
caches.keys
e excluir caches que não estão em uma lista de permissões definida com
caches.delete
:
self.addEventListener('activate', (event) => {
// Specify allowed cache keys
const cacheAllowList = ['MyFancyCacheName_v2'];
// Get all the currently active `Cache` instances.
event.waitUntil(caches.keys().then((keys) => {
// Delete all caches that aren't in the allow list:
return Promise.all(keys.map((key) => {
if (!cacheAllowList.includes(key)) {
return caches.delete(key);
}
}));
}));
});
Os caches antigos não se organizam.
Precisamos fazer isso por conta própria ou corremos o risco de exceder
cotas de armazenamento.
Como o 'MyFancyCacheName_v1'
do primeiro service worker está desatualizado,
a lista de permissões de cache será atualizada para especificar 'MyFancyCacheName_v2'
,
que exclui caches com um nome diferente.
O evento activate
será concluído depois que o cache antigo for removido.
Neste ponto, o novo service worker vai assumir o controle da página,
finalmente a substituição do antigo!
O ciclo de vida continua
Se o Workbox é usado para lidar com a implantação e as atualizações do service worker ou se a API Service Worker for usada diretamente, é preciso entender o ciclo de vida do service worker. Com esse entendimento, os comportamentos dos service workers vão parecer mais lógicos do que misteriosos.
Se você quiser se aprofundar nesse assunto, vale a pena conferir este artigo de Jake Archibald. Há várias nuances no ciclo de vida do serviço, mas é conhecido, e esse conhecimento será muito útil ao usar o Workbox.