“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 activated
sabemos 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.