Table of Contents
Compreensão e utilização de trabalhadores de serviço em JavaScript para capacidades off-line
Os usuários modernos da Web esperam experiências instantâneas e confiáveis, mesmo quando as condições da rede são ruins ou inexistentes. Os trabalhadores de serviços são a pedra angular desta expectativa, permitindo que os aplicativos Web Progressive (PWAs) trabalhem offline, carreguem rapidamente em conexões desfocadas e forneçam notificações de push. Ao contrário das técnicas tradicionais de cache, um trabalhador de serviço atua como um proxy programável entre o navegador e a rede. Ele intercepta todas as solicitações de saída da página e pode responder com conteúdo em cache, obter dados novos da rede ou aplicar uma estratégia híbrida. Este artigo irá levá- lo dos fundamentos dos trabalhadores de serviços através de padrões avançados de implementação, estratégias de cache e melhores práticas do mundo real.
Os trabalhadores de serviços não são uma nova tecnologia – eles têm sido suportados em navegadores principais há vários anos – mas eles permanecem subutilizados por muitas equipes de desenvolvimento. Quando construídos corretamente, eles transformam um aplicativo estático em uma experiência resistente, semelhante a aplicativos. Nós vamos caminhar através do ciclo de vida completo, discutir estratégias de cache que se adequam a diferentes tipos de conteúdo e cobrir APIs companheiras como Sincronização de Fundo e Notificações de Empurra. Até o final, você terá um modelo mental pronto para integrar os trabalhadores de serviço em seus próprios projetos JavaScript.
O que é um trabalhador de serviço?
Um trabalhador de serviço é um arquivo JavaScript que roda em um escopo global separado de sua página web, em seu próprio tópico. Ele não tem acesso DOM e se comunica com sua página apenas através de . Seu trabalho principal é agir como um intermediário para solicitações de rede: o navegador pode enviar qualquer pedido de sua página web para o trabalhador de serviço, que então decide como responder – da rede, de um cache, ou construindo uma resposta personalizada.
Como ele é executado em segundo plano, um Trabalhador de Serviço pode continuar a operar mesmo depois que o usuário fecha a página que a registrou. Isso o torna ideal para tarefas como:
- Suporte offline – servindo páginas em cache quando a rede não está disponível.
- Sincronização de dados de base – filar ações enquanto offline e enviá-las quando a conectividade retorna.
- Introduza notificações[ – recebendo mensagens de um servidor e exibindo-as ao usuário mesmo quando o aplicativo não estiver aberto.
- Optimização de desempenho – pré-carregando ativos críticos para que o aplicativo carregue instantaneamente em visitas repetidas.
Os trabalhadores de serviços dependem de uma exigência de segurança estrita: eles só trabalham sob HTTPS (ou ] para o desenvolvimento), o que protege os usuários de ataques de homem-no-médio que poderiam alterar o script.
O ciclo de vida do trabalhador de serviço
Um trabalhador de serviços avança através de um ciclo de vida bem definido: registo, instalação, activação e, em seguida, o tratamento ocioso/desengate. Compreender este ciclo de vida é essencial porque determina quando os seus recursos em cache ficam disponíveis e quando os caches antigos são limpos.
1. Registo
Antes de qualquer lógica do Trabalhador de Serviço poder ser executada, a sua página deverá registar o programa. Isto é feito a partir do seu ficheiro JavaScript principal (normalmente o ponto de entrada da página). O registo informa o navegador da localização e âmbito do Trabalhador de Serviço. O escopo define quais URLs o Trabalhador de Serviço pode controlar; por padrão, é o directório do programa.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(function(error) {
console.log('Registration failed:', error);
});
}
Se o script já existe, o navegador compara o conteúdo byte-for-byte com a versão instalada anteriormente. Se tiver mudado, uma nova versão é instalada em segundo plano enquanto a versão antiga continua a controlar páginas.
2. Instalação
O evento é o primeiro evento que o seu Trabalhador de Serviço recebe. Este é o momento perfeito para pré-entrar os arquivos essenciais que o seu aplicativo precisa para funcionar offline: o shell de aplicativo, CSS central, JavaScript e qualquer página de reserva.
const CACHE_NAME = 'my-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/offline.html'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
Se algum dos arquivos não conseguir baixar durante , a etapa de instalação falha e o Trabalhador de Serviço não é considerado instalado. Este é um recurso de segurança para garantir que você nunca tenha um aplicativo parcialmente em cache.
3. Ativação
Após a instalação, o navegador espera até que todas as páginas que foram carregadas com o antigo Trabalhador de Serviço sejam fechadas. Em seguida, ele dispara o evento . Este evento é normalmente usado para limpar caches antigas e para controlar qualquer página que tenha sido aberta antes da ativação.
self.addEventListener('activate', function(event) {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
Chamar dentro do manipulador assume imediatamente o controle de todas as páginas abertas sem exigir uma recarga. Isto é frequentemente usado durante o desenvolvimento para uma iteração mais rápida.
4. Obter Eventos
Uma vez ativado, o Trabalhador de Serviço escuta eventos . Cada vez que o navegador inicia uma solicitação de uma página controlada, o Trabalhador de Serviço pode interceptá-la. Dentro do gerenciador de eventos de busca você implementa sua estratégia de cache de escolha.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
Estratégias de cache para diferentes cenários
A escolha da estratégia de cache certa determina o equilíbrio entre frescura e velocidade. Não há solução única para todos os tamanhos; diferentes recursos requerem abordagens diferentes.
Cache-First (Cache e Rede)
Melhor para ativos estáticos que raramente mudam: imagens, fontes, CSS. O Trabalhador de Serviço verifica primeiro a cache; se for encontrado, retorna imediatamente. Caso contrário, ela obtém da rede e armazena a resposta. Esta estratégia produz cargas de repetição rápida.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
return response || fetch(event.request).then(function(networkResponse) {
return caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
});
})
);
});
Rede-Primeiro (Rede com Cache Fallback)
Ideal para conteúdo dinâmico: endpoints de API, fontes de notícias. Tente a rede primeiro; se falhar ou for muito lenta, volte para a versão em cache. Isto garante que os usuários sempre vejam os dados mais recentes, a menos que estejam offline.
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request)
.then(function(networkResponse) {
return caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
})
.catch(function() {
return caches.match(event.request);
})
);
});
Stale-While-Revalidate
Melhor para recursos que não precisam ser instantaneamente frescos: avatars de perfil, feeds RSS. Responda imediatamente com a versão em cache, mas também obtenha uma atualização da rede em segundo plano. Da próxima vez que o recurso for solicitado, a nova entrada de cache é servida.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
return cache.match(event.request).then(function(cachedResponse) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchPromise;
});
})
);
});
Rede-Somente e Cache-Somente
Para dados sensíveis, como dados bancários do usuário, use Rede-Somente (sem cache). Para conteúdo estático puramente offline (por exemplo, um manual), Cache-Somente[] pode ser suficiente.
Recursos avançados do trabalhador de serviço
Além do cache, os trabalhadores de serviço desbloquear recursos de fundo poderosos.
Sincronização de Fundo
Quando um usuário envia um formulário ou envia uma mensagem enquanto está offline, você não quer perder essa ação. O permite registrar um evento de sincronização que o navegador irá disparar uma vez que a conectividade esteja de volta.
// In page script
navigator.serviceWorker.ready.then(function(registration) {
return registration.sync.register('sync-messages');
});
// In Service Worker
self.addEventListener('sync', function(event) {
if (event.tag === 'sync-messages') {
event.waitUntil(sendPendingMessages());
}
});
O Background Sync é ideal para aplicativos de chat, submissões de formulários ou qualquer fila de dados que precise de consistência.
Fazer a Push de Notificações
As mensagens de push chegam de um servidor e acordam o Trabalhador de Serviço, que então exibe uma notificação. Isso funciona mesmo quando sua aplicação web está fechada, dando a PWAs uma sensação nativa.
self.addEventListener('push', function(event) {
const options = {
body: 'This notification was triggered by a push message.',
icon: '/icons/icon-192x192.png',
badge: '/icons/badge.png',
data: {
url: '/new-content'
}
};
event.waitUntil(
self.registration.showNotification('New Update', options)
);
});
Clicar na notificação pode abrir uma URL específica usando o evento .
Segurança e Âmbito de aplicação
Os trabalhadores de serviços podem interceptar e modificar pedidos, então eles devem ser servidos sobre HTTPS para evitar que scripts maliciosos sejam injetados. Durante o desenvolvimento local, navegadores permitem que os trabalhadores de serviço em ] como uma exceção. Em produção, sempre sirva seu arquivo de trabalhador de serviço sobre HTTPS.
O scope de um Trabalhador de Serviço determina quais páginas ele controla. Por padrão, o escopo é o diretório onde o script do Trabalhador de Serviço está localizado. Para ampliar o escopo (por exemplo, controlar todas as páginas sob , você pode definir a opção durante o registro. Você também deve incluir um cabeçalho HTTP se quiser ampliar o escopo para além do diretório do script.
Trabalhadores de Serviços de Depuração
Navegador DevTools são essenciais para inspecionar o comportamento do trabalhador de serviço. No Chrome, abra ou vá para Application → Service Workers. Você pode ver o status de cada trabalhador, iniciar/parar manualmente e registrar claramente. Firefox oferece ferramentas semelhantes em torno de:debugging.
Dicas comuns de depuração:
- Atualizar ao recarregar – no Chrome DevTools, verifique “Atualizar ao recarregar” para que um novo trabalhador de serviço seja instalado cada vez que a página recarrega (útil durante o desenvolvimento).
- Bypass for network – desactivar temporariamente o Trabalhador de Serviço para ver como o seu aplicativo se comporta sem ele.
- Armazenamento de cache – inspecionar recursos em cache no painel de armazenamento de cache para verificar sua estratégia de cache.
- Logging – use dentro do Trabalhador de Serviço; as mensagens aparecem no console de fundo em DevTools.
Implicações de Desempenho
Os trabalhadores de serviços podem melhorar drasticamente o desempenho percebido, mas também introduzir complexidade. Pré-encher muitos recursos durante a instalação pode atrasar o passo de 'instalar' e fazer com que a instalação falhe se ocorrerem tempos de espera. Uma boa prática é armazenar apenas o shell mínimo em e escavar recursos adicionais durante eventos de busca.
O tamanho da 'cache' é limitado por origem (normalmente algumas centenas de megabytes em navegadores móveis). O cache repetido de arquivos de mídia grandes pode preencher esta quota. Use a API de armazenamento (]) para monitorar o uso.
Outra armadilha de desempenho: se seu trabalhador de serviço executa computação de peso pesado dentro do manipulador , ele pode bloquear ou atrasar as respostas da rede. Mantenha os manipuladores de busca leves e use padrões assíncronos.
Pistas comuns e como evitá - las
- Servir conteúdo obsoleto – sempre versionar os seus caches (por exemplo, , ) e apagar caches antigas durante o evento . Nunca use um nome de cache ilimitado.
- Página de reserva não cacheada – se a sua página offline não estiver pré-cacheada, os utilizadores verão uma página de erro do navegador. Cache uma simples falha de reserva offline durante a instalação.
- Requisitos POST quebrados – Os trabalhadores de serviço podem interceptar solicitações POST, mas você deve ter cuidado com cachê-los. O não suporta POST por padrão; lide com eles separadamente ou simplesmente passe-os através.
- Faltando – se você não ligar , o navegador tentará buscar o recurso por conta própria, potencialmente ignorando sua lógica de cache.
Exemplo do mundo real: um simples PWA desligado
Vamos juntar tudo. Vamos criar um pequeno PWA que cache seu shell e fornece um backback offline. O arquivo completo do trabalhador de serviço ():
const CACHE_NAME = 'pwa-shell-v2';
const SHELL_URLS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/offline.html'
];
// Install: pre‑cache shell
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(SHELL_URLS);
})
);
});
// Activate: clean old caches
self.addEventListener('activate', function(event) {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(name) {
if (!cacheWhitelist.includes(name)) {
return caches.delete(name);
}
})
);
}).then(function() {
return self.clients.claim();
})
);
});
// Fetch: network‑first for pages, cache‑first for static assets
self.addEventListener('fetch', function(event) {
if (event.request.mode === 'navigate') {
// Pages: try network, fall back to cache
event.respondWith(
fetch(event.request).then(function(networkResponse) {
// Update cache with fresh page
const responseClone = networkResponse.clone();
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, responseClone);
});
return networkResponse;
}).catch(function() {
return caches.match(event.request).then(function(cached) {
// If page not cached, show offline fallback
return cached || caches.match('/offline.html');
});
})
);
} else {
// Static assets: cache‑first
event.respondWith(
caches.match(event.request).then(function(cached) {
return cached || fetch(event.request).then(function(networkResponse) {
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
})
);
}
});
Este exemplo utiliza uma abordagem de primeira rede para navegação (páginas novas quando online, backback offline quando não está online) e cache-primeiro para todos os outros ativos. O código lida com a versão cache completamente.
Conclusão
Os trabalhadores de serviços são a espinha dorsal de aplicações web modernas com capacidade offline. Eles fornecem uma camada de rede programável que vai muito além do cache simples: sincronização de fundo, notificações de push e controle total sobre como os recursos são servidos. Ao entender o ciclo de vida, escolher a estratégia de cache certa para cada tipo de recurso e seguir as melhores práticas de segurança, você pode construir experiências web que são resilientes, rápidas e envolventes.
Para mergulhar mais profundamente nas APIs, consulte a documentação oficial no guia MDN e Google Web Fundamentals[]. Para padrões de cache avançados, o Cookbook offline por Jake Archibald oferece receitas detalhadas. Comece com um registro simples, teste completamente em DevTools, e iterate. Seus usuários irão agradecer por isso.