“Off-line” é um tópico importante atualmente, especialmente porque muitos aplicativos da Web também funcionam como aplicativos móveis. A API auxiliar off-line original, a Application Cache API (também conhecida como “appcache”), tem uma série de problemas, muitos dos quais podem ser encontrados no artigo de Jake Archibald Application Cache is a Douchebag post. Os problemas com o appcache incluem:
- Os arquivos são servidos a partir do cache mesmo quando o usuário está on-line.
- Não há dinamismo: o arquivo appcache é simplesmente uma lista de arquivos a serem armazenados em cache.
- É possível armazenar em cache o próprio arquivo .appcache e isso leva a problemas de atualização.
- Outras pegadinhas.
Hoje, há uma nova API disponível para os desenvolvedores para garantir que seus aplicativos da Web funcionem corretamente: a API do Service Worker. A API do Service Worker permite que os desenvolvedores gerenciem o que entra e o que não entra no cache para uso off-line com JavaScript.
Apresentando o livro de receitas do Service Worker
Para apresentar aos senhores a API do Service Worker, usaremos exemplos do novo site da Mozilla Livro de receitas do Service Worker! O Cookbook é uma coleção de exemplos práticos e funcionais de service workers em uso em aplicativos modernos da Web. Apresentaremos os service workers nesta série de três partes:
- Receitas off-line para Service Workers (postagem de hoje)
- A seu serviço para mais do que apenas o appcache
- Atualizações Web Push para as massas
É claro que essa API tem outras vantagens além de permitir recursos off-line, como o desempenho, por exemplo, mas gostaria de começar apresentando estratégias básicas de service worker para off-line.
O que queremos dizer com offline?
Off-line não significa apenas que o usuário não tem uma conexão com a Internet, mas também que o usuário está em uma conexão de rede instável. Essencialmente, “off-line” significa que o usuário não tem uma conexão confiável, e todos nós já passamos por isso!
A receita de Status off-line ilustra como usar um service worker para armazenar em cache uma lista de ativos conhecidos e, em seguida, notificar o usuário de que agora ele pode ficar off-line e usar o aplicativo. O aplicativo em si é bastante simples: mostrar uma imagem aleatória quando um botão é clicado. Vamos dar uma olhada nos componentes envolvidos para que isso aconteça.
O trabalhador de serviços
Começaremos examinando o service-worker.js para ver o que está sendo armazenado em cache. Colocaremos em cache as imagens aleatórias a serem exibidas, bem como a página de exibição e os recursos críticos de JavaScript, em um cache chamado dependencies-cache:
var CACHE_NAME = 'dependencies-cache'; // Files required to make this app work offline var REQUIRED_FILES = [ 'random-1.png', 'random-2.png', 'random-3.png', 'random-4.png', 'random-5.png', 'random-6.png', 'style.css', 'index.html', '/', // Separate URL than index.html! 'index.js', 'app.js' ];
O trabalhador do serviço install abrirá o cache e usará o evento addAll para direcionar o service worker para armazenar em cache nossos arquivos especificados:
self.addEventListener('install', function(event) {
// Perform install step: loading each required file into cache
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
// Add all offline dependencies to the cache
return cache.addAll(REQUIRED_FILES);
})
.then(function() {
// At this point everything has been cached
return self.skipWaiting();
})
);
});
O fetch de um service worker é disparado para cada solicitação que a página faz. O evento fetch também permite que o senhor forneça conteúdo alternativo ao que foi realmente solicitado. Para fins de conteúdo off-line, no entanto, nosso fetch será muito simples: se o arquivo estiver em cache, retorne-o do cache; caso contrário, recupere o arquivo do servidor:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return the response from the cached version
if (response) {
return response;
}
// Not in cache - return the result from the live server
// `fetch` is essentially a "fallback"
return fetch(event.request);
}
)
);
});
A última parte deste service-worker.js é o arquivo activate onde reivindicamos imediatamente o service worker para que o usuário não precise atualizar a página para ativar o service worker. O activate event é acionado quando uma versão anterior de um service worker (se houver) é substituída e o service worker atualizado assume o controle do escopo.
self.addEventListener('activate', function(event) {
// Calling claim() to force a "controllerchange" event on navigator.serviceWorker
event.waitUntil(self.clients.claim());
});
Essencialmente, não queremos exigir que o usuário atualize a página para que o service worker comece a funcionar – queremos que o service worker seja ativado no carregamento inicial da página.
Registro de trabalhadores de serviços
Com o service worker simples criado, é hora de registrar o service worker:
// Register the ServiceWorker
navigator.serviceWorker.register('service-worker.js', {
scope: '.'
}).then(function(registration) {
// The service worker has been registered!
});
Lembre-se de que o objetivo da receita é notificar o usuário quando os arquivos necessários forem armazenados em cache. Para fazer isso, precisaremos ouvir a mensagem do service worker state. Quando o state se tornou o activatedsabemos que os arquivos essenciais foram armazenados em cache, que nosso aplicativo está pronto para ficar off-line e que podemos notificar nosso usuário:
// Listen for claiming of our ServiceWorker
navigator.serviceWorker.addEventListener('controllerchange', function(event) {
// Listen for changes in the state of our ServiceWorker
navigator.serviceWorker.controller.addEventListener('statechange', function() {
// If the ServiceWorker becomes "activated", let the user know they can go offline!
if (this.state === 'activated') {
// Show the "You may now use offline" notification
document.getElementById('offlineNotification').classList.remove('hidden');
}
});
});
Para testar o registro e verificar se o aplicativo funciona off-line, basta usar a receita! Essa receita fornece um botão para carregar uma imagem aleatória, alterando a propriedade src da imagem:
// This file is required to make the "app" work offline
document.querySelector('#randomButton').addEventListener('click', function() {
var image = document.querySelector('#logoImage');
var currentIndex = Number(image.src.match('random-([0-9])')[1]);
var newIndex = getRandomNumber();
// Ensure that we receive a different image than the current
while (newIndex === currentIndex) {
newIndex = getRandomNumber();
}
image.src="https://davidwalsh.name/random-" + newIndex + '.png';
function getRandomNumber() {
return Math.floor(Math.random() * 6) + 1;
}
});
Alterando a imagem src acionaria uma solicitação de rede para essa imagem, mas como temos a imagem armazenada em cache pelo service worker, não há necessidade de fazer a solicitação de rede.
Essa receita abrange provavelmente o mais simples dos casos off-line: armazenar em cache os arquivos estáticos necessários para uso off-line.
Esta receita segue outro caso de uso simples: buscar uma página via AJAX, mas responder com outro recurso HTML em cache (offline.html) se a solicitação falhar.
O trabalhador de serviços
O install do trabalhador de serviço busca o offline.html e o coloca em um cache chamado offline:
self.addEventListener('install', function(event) {
// Put `offline.html` page into cache
var offlineRequest = new Request('offline.html');
event.waitUntil(
fetch(offlineRequest).then(function(response) {
return caches.open('offline').then(function(cache) {
return cache.put(offlineRequest, response);
});
})
);
});
Se essa solicitação falhar, o service worker não será registrado porque a promessa foi rejeitada.
O fetch escuta uma solicitação para a página e, em caso de falha, responde com o offline.html que armazenamos em cache durante o registro do evento:
self.addEventListener('fetch', function(event) {
// Only fall back for HTML documents.
var request = event.request;
// && request.headers.get('accept').includes('text/html')
if (request.method === 'GET') {
// `fetch()` will use the cache when possible, to this examples
// depends on cache-busting URL parameter to avoid the cache.
event.respondWith(
fetch(request).catch(function(error) {
// `fetch()` throws an exception when the server is unreachable but not
// for valid HTTP responses, even `4xx` or `5xx` range.
return caches.open('offline').then(function(cache) {
return cache.match('offline.html');
});
})
);
}
// Any other handlers come here. Without calls to `event.respondWith()` the
// request will be handled without the ServiceWorker.
});
Observe que usamos catch para detectar se a solicitação falhou e que, portanto, devemos responder com offline.html conteúdo.
Registro de trabalhadores de serviços
Um service worker precisa ser registrado apenas uma vez. Este exemplo mostra como ignorar o registro, caso ele já tenha sido feito, verificando a presença do navigator.serviceWorker.controller se a propriedade controller não existir, passaremos ao registro do service worker.
if (navigator.serviceWorker.controller) {
// A ServiceWorker controls the site on load and therefor can handle offline
// fallbacks.
console.log('DEBUG: serviceWorker.controller is truthy');
debug(navigator.serviceWorker.controller.scriptURL + ' (onload)', 'controller');
}
else {
// Register the ServiceWorker
console.log('DEBUG: serviceWorker.controller is falsy');
navigator.serviceWorker.register('service-worker.js', {
scope: './'
}).then(function(reg) {
debug(reg.scope, 'register');
});
}
Com o service worker confirmado como registrado, o senhor pode testar a receita (e acionar a nova solicitação de página) clicando no link “refresh”: (que aciona uma atualização de página com um parâmetro de eliminação de cache):
// The refresh link needs a cache-busting URL parameter
document.querySelector('#refresh').search = Date.now();
Fornecer ao usuário uma mensagem off-line em vez de permitir que o navegador mostre sua própria mensagem (às vezes feia) é uma excelente maneira de manter um diálogo com o usuário sobre por que o aplicativo não está disponível enquanto ele está off-line!
Fique off-line!
Os service workers levaram a experiência e o controle off-line para um novo e poderoso espaço. Hoje, o senhor pode usar a API do Service Worker no Chrome e no Firefox Developer Edition. Muitos sites estão usando service workers atualmente, como o senhor pode ver por si mesmo acessando about:serviceworkers no Firefox Developer Edition; o senhor verá uma lista de service workers instalados de sites que visitou!
O Livro de receitas do Service Worker está repleto de receitas excelentes e práticas, e continuamos a adicionar mais. Fique atento à próxima postagem desta série, A seu serviço para mais do que apenas o appcacheonde o senhor aprenderá a usar a API do Service Worker para mais do que apenas fins off-line.
