“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:

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!

about:serviceworkers

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.