// apis · Web Platform Advent #4
Service Workers: caching, offline e atualizações explicados
Um guia prático sobre service workers — registar um, o ciclo de vida install/activate, a Cache API, servir uma página offline e distribuir atualizações com segurança.
Um service worker é um script que o navegador executa em segundo plano, separado da sua página. Coloca-se entre a sua aplicação e a rede como um proxy programável: pode intercetar pedidos, respondê-los a partir de uma cache e manter o seu site a funcionar mesmo quando o utilizador está offline. É a base das Progressive Web Apps.
Duas regras fundamentais antes de mais nada. Um service worker só funciona sobre HTTPS (ou localhost durante o desenvolvimento) e não tem acesso ao DOM — comunica com as páginas através de eventos e mensagens, não tocando em document.
Registar um service worker
Regista o worker a partir do script normal da sua página. Verifique primeiro a disponibilidade da funcionalidade, depois aponte o navegador para o ficheiro do script:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/sw.js')
.then((reg) => console.log('Scope:', reg.scope))
.catch((err) => console.error('SW failed:', err));
});
} A localização do ficheiro é importante: um worker em /sw.js controla toda a origem, enquanto um em /app/sw.js só controla páginas sob /app/. Esse limite é o seu scope.
O ciclo de vida: install e activate
Um service worker tem uma vida própria independente da página. Os dois eventos que mais lhe interessam são install (disparado uma vez, um bom sítio para pré-armazenar assets em cache) e activate (um bom sítio para limpar caches antigas):
const CACHE = 'site-v1';
const ASSETS = ['/', '/index.html', '/styles.css', '/app.js', '/offline.html'];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
}); Chamar event.waitUntil() diz ao navegador para não considerar a fase concluída até que a sua promise se resolva — caso contrário, poderia encerrar o worker a meio da tarefa.
Intercetar pedidos com fetch
O evento fetch é onde a magia acontece. Você decide o que cada pedido devolve. Uma estratégia comum e segura é cache-first com recurso à rede:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request);
})
);
}); Para mostrar uma página offline personalizada quando tanto a cache como a rede falham, adicione um catch:
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request).catch(() => caches.match('/offline.html'))
);
}); Estratégias de caching num relance
| Estratégia | Comportamento | Boa para |
|---|---|---|
| Cache-first | Servir da cache, recorrer à rede | Assets estáticos (CSS, JS, tipos de letra) |
| Network-first | Tentar a rede, recorrer à cache | HTML, dados de API que mudam |
| Stale-while-revalidate | Servir da cache, atualizá-la em segundo plano | Avatares, feeds, listas de conteúdo |
Distribuir atualizações sem prejudicar os utilizadores
Quando altera sw.js, o navegador deteta os novos bytes e instala o novo worker, mas este permanece em espera até que cada separador que usa o antigo seja fechado. Isto impede que duas versões sejam executadas ao mesmo tempo. Para assumir o controlo mais cedo, chame self.skipWaiting() em install e clients.claim() em activate — mas só o faça quando tiver a certeza de que a nova versão é compatível com as páginas já abertas.
Incremente sempre o nome da cache (site-v1 → site-v2) quando os assets mudam. O handler activate acima elimina então a cache obsoleta, para que os utilizadores nunca recebam uma mistura de ficheiros antigos e novos.
Este é todo o ciclo central: registar, pré-armazenar em cache no install, limpar no activate e responder aos pedidos no fetch. Comece com uma estratégia cache-first para ficheiros estáticos e uma network-first para o seu HTML, e terá um site que carrega instantaneamente em visitas repetidas e sobrevive a uma ligação interrompida.