Começando com Service Workers

Worker Header
Service Workers, aquela tecnologia maneira utilizada para resolver gargalos de perfomance existentes em aplicações Web (como perda de conexão, latência de rede etc.), que prejudicam a experência do usuário dentro de uma aplicação.

O uso de Service Workers nos traz um recurso sensacional, o funcionamento Offline (antes a solução era usar AppCache, mas né, não deu muito certo).

A parte Teórica

Aquele negócio, os Service Workers surgiram na intenção de resolver o problema do funcionamento Offline, apesar de apresentar uma sintaxe complexa (em comparação com o seu antecessor AppCache), ele te dá a total liberdade de manipulação do que está acontecendo, do que será salvo, quando será salvo e quando será removido, basicamente o princípio para se rodar uma PWA (Progressive Web App).

Vale destacar que os Service Workers funcionam no escopo do Browser, uma camada acima do que estamos costumados a lidar quando trabalhamos com JavaScript (ou seja, os Service Workers NÃO tem acesso ao DOM), outro ponto importante é que os SWs só funcionam embaixo do protocolo HTTPS, visto que faz sentido, tendo em vista riscos de segurança que possa trazer.

Antes de Rodar, um service worker passa por um processo de instalação e validação, seguindo o seguinte Ciclo de Vida:
Worker Lifecycle

No qual, o SW deve ser registrado, se instalado corretamente, começa a funcionar.

Existem eventos que podem ser escutados na utilização de um SW:
Worker Events

Um fator importante a se levantar, é que toda a API é baseada em Promises, ou seja, ela é totalmente assíncrona.

Bora Escrever o Primeiro?

O Exemplo a ser apresentado segue duas vertentes:

  1. Instalação da parada com um Cache
  2. Atualização de Cache Exclusivo

Instalando um Service Worker

O exemplo consiste em realização do cache de arquivos para funcionamento offline e/ou melhoria de desempenho em algumas requisições.
Bora parar de enrolar, existe a seguinte receita de bolo, a instalação simples de um SW:

1
2
3
4
5
6
7
8
9
10
11
12
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js', { scope: './' })
.then(function(reg){
if (reg.installing) {
console.log('Service worker installing');
} else if (reg.waiting) {
console.log('Service worker installed');
} else if (reg.active) {
console.log('Service worker active');
}
}).catch((error) => console.log('Registration failed with ' + error));
}

Explicando um pouco: Neste trecho de códico é registrado sw.js, ao qual tem um escopo definido, no caso do exemplo a rota raiz do app (definição opcional). Fator importante pois um Service Worker só irá enxergar os arquivos que estão em diretórios no mesmo nível ou abaixo do diretório em que se encontra.

1
2
3
4
5
6
7
8
9
10
const cacheVersion = 1;
const currentCaches = {
font: `font-cache-v${cacheVersion}`
};
//Starter example, caching some files
const cacheUrls = [
'/',
'/js/app.js',
'/fonts/roboto/Roboto-Regular.woff2'
];

Definimos algumas maneiras de controlar nosso Cache, agora vamos gerenciar o evento de instalação, ao qual adiciona ao cache todas as URL’s definidas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(`v${cacheVersion}`)
.then(function(cache) {
return cache.addAll(cacheUrls);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
});
);
});

No trecho de código acima, nós também interceptamos as requisições, gerenciando o evento de fetch, este funciona da seguinte forma: a requisição chega, caso o arquivo exista no cache, ele responde o que estava no cache, senão ele passa a requisição para frente. Como uma imagem sempre ajuda, tá aí:
fetch event

Alterando um pouco o Exemplo

Vamos agora alterar o que já foi feito, nesta segunda parte iremos criar um Cache exclusivo para fontes, vamos ignorar o que foi feito acima (só a última parte). Além do evento de fetch também vamos cuidar do evento activate, porém de um jeito simples, vamos excluir dentro do evento o cache que for velho e estiver fora da nossa lista de caches válidos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
self.addEventListener('activate', function(event) {
const expectedCacheNames = Object.keys(currentCaches)
.map(key => currentCaches[key]);
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (expectedCacheNames.indexOf(cacheName) == -1) {
return caches.delete(cacheName); //exclusão de cache velho
}
})
);
})
);
});
self.addEventListener('fetch', function(event) {
console.log('Handling fetch event for', event.request.url);
event.respondWith(
caches.open(currentCaches.font).then(function(cache) {
return cache.match(event.request).then(function(response) {
console.log(response);
if (response) {
console.log('Found response in cache:', response);
return response;
}
return fetch(event.request.clone()).then(function(response) {
if (response.status < 400 &&
response.headers.has('content-type') &&
response.headers.get('content-type').match('font')) {
console.log('Caching the response to', event.request.url);
cache.put(event.request, response.clone());
} else {
console.log('Not caching the response to', event.request.url);
}
return response;
});
}).catch(function(error) {
console.error('Error in fetch handler:', error);
throw error;
});
})
);
});

No evento de fetch, acontece algo similar exemplo anterior, porém se a fonte não for encontrada no Cache, ela será salva e a requisição continuará.

Conclusões

Service Worker é um negócio que deve ser estudado com calma, assim como a validade de sua implementação em um projeto, como quase tudo na computação, depende do contexto. Entretanto temos um ganho no desempenho na segunda vez que o usuário acessar de nossa aplicação.

Como nem tudo são flores, nem todos browser tem suporte a Service Workers, uma boa leitura é is SERVICE WORKER READY?.

Referências