Neste codelab, você irá começar com uma aplicação web que mostra a previsão do tempo local, e usará os elemento Polymer <platinum-sw> para fazer a aplicação web funcionar mesmo que não haja uma conexão com a Internet. Este tipo de aplicação web "offline-first" é ótima para usuários que estão frequentemente em locais sem conexão, e carregar os recursos de sua aplicação web também significa que elas iniciarão mais rápido do que se tivessem que buscar estes recursos através da rede.

O que você aprenderá

O que você precisará

Como você utilizará este tutorial?

Apenas lê-lo Ler e completar os exercícios

Qual o seu nível atual de experiência trabalhando com Polymer?

Iniciante Intermediário Avançado

Você é livre para usar seu editor favorito, mas existem diversas instruções neste Codelab que são específicos do Chrome Dev Editor. Se você ainda não o instalou ainda, você pode instalá-lo através da Chrome Web Store.

Download Chrome Dev Editor

Use o comando "Git clone..." do Chrome Dev Editor para fazer o download de uma cópia do código fonte do repositório, https://github.com/googlecodelabs/polymer-offline-weather.git

O repositório contém diretórios correspondentes à aplicação web em vários estágios de funcionalidade, com a versão completa na pasta final/ e várias versões não finalizadas em pastas como step1/, etc. Existe também uma pasta common/ que contém recursos auxiliares usados em todos os estágios.

Primeiro, vamos ver como a versão final do app de demonstração se parece. Com o código importado dentro do Chrome Dev Editor, selecione a pasta final/ e aperte o botão na barra de ferramentas. O Chrome Dev Editor irá iniciar um servidor web e abrirá a página final/index.html.

Uma versão online deste mesmo código pode ser encontrada em: https://googlecodelabs.github.io/polymer-offline-weather/final/index.html

Por padrão, você verá o clima atual em Mountain View, CA:

O botão usará a Geolocation API para determinar a localização atual do seu dispositivo e mostrar o clima onde você está.

Inspecionando o tráfego de rede

Graças ao service worker controlando a aplicação web de exemplo, ela estará disponível mesmo que a própria página web não esteja. Você pode testar isso fechando o Chrome Dev Editor ou finalizando seu servidor web local e recarregando a página.

Se você quiser confirmar que o service worker está interceptando as requisições, você pode abrir o painel de Rede do Developer Tools. Requisições originadas da página que o service worker está responsável serão atribuídas (do ServiceWorker) na coluna Size. Requisições originadas via chamadas fetch() de onde o service worker será atribuído (from cache) ou com tamanho atual na coluna Size e terão um ícone na coluna Name.

Começando do zero

Durante este Codelab, e no desenvolvimento com service workers em geral, você pode tirar vantagem do incognito mode do Chrome para visualizar uma página em particular sem qualquer envolvimento de service workers instalados previamente.

Janelas no modo Incógnito iniciam sem qualquer service workers instalados e também qualquer service workers serão removidos quando a janela for encerrada. Você precisará fechar a janela do Incógnito completamente incluindo todas abas para começar de novo.

Agora que você já testou a versão final do aplicativo web de previsão do tempo, incluindo o suporte Offline via service worker, vamos voltar e construir o aplicativo. Nesta sessão nós teremos uma visão geral do código contido na pasta step1/, que contém um aplicativo web funcional, porém este não funcionará offline.

Web Components/Conceitos do Polymer

Vejamos alguns conceitos de Web Components e Polymer que você precisará neste codelab. Aqui temos links para esses conceitos seguidos por exemplos do código encontrado em step1/index.html:

<link rel="import" href="../common/elements.html">
<body class="fit vertical layout center-center">
<template is="dom-bind" id="t">
<template is="dom-if" if="[[coordinates]]">
Weather for <span>[[_calculateLocationString(weatherResponse.query.results.channel.location)]]</span>
<iron-ajax id="weather-ajax"
           auto
           loading="{{activeRequest}}"
           url="[[weatherApiUrl]]"
           params="[[_calculateWeatherParams(coordinates.latitude, coordinates.longitude)]]"
           handle-as="json"
           last-response="{{weatherResponse}}">
</iron-ajax>

Se você está familiarizado com todos esses conceitos e exemplos de código você deve ter uma boa ideia do que está acontecendo no código do step1/.

Estrutura do aplicativo Web

Vamos focar por alguns instantes na estrutura do aplicativo web e como ela está relacionado aos recursos que são necessários para o funcionamento. Desde o início estamos usando padrões que se adequam à adição do suporte offline mais tarde.

O núcleo da nossa web app, tudo que é necessário para mostrar a "casca" básica da app, consiste no HTML, CSS e JavaScript. Uma parte desta "casca" é disponibilizada no arquivo index.html e outras partes dela são carregadas através de componentes web via HTML Imports. Juntos, são tudo o que é necessário para mostrar um site estático. É crucial manter esta parte do seu web app enxuta, para garantir que algum conteúdo estático estrutural possa ser apresentado logo que o aplicativo web for aberto, independentemente da disponibilidade de uma conexão com a rede.

Claro que uma página estática que sempre mostra o mesmo valor não é o que os usuários esperam de um aplicativo web de condições climáticas. O app precisa recuperar dados específicos para o usuário. Neste caso, estes dados são as condições climáticas da localização atual do usuário. Esses dados dinâmicos, trazidos via API do Yahoo! Weather, estão logicamente separados da "casca" do aplicativo.

Quando chegar a hora de pensar sobre suporte offline, estruturar nosso aplicativo de modo que há uma distinção clara entre a "casca" e os recursos dinâmicos ou recursos específicos de estado virão a calhar. Mas antes de chegarmos a esse ponto, vamos cobrir algo crucial para a experiência offline: service workers!

Service workers! Nós os mencionamos ao longo deste codelab, mas até agora, não falamos do que se tratam e nem como eles se relacionam com o suporte offline. Antes de prosseguirmos, vamos corrigir isso.

Basicamente, você pode pensar em um service worker como uma "background thread" para a web. É código JavaScript executado fora do contexto de suas páginas web, e responde a eventos, incluindo requests feitos a partir de páginas web sob o seu controle.

Um service worker tem um tempo de vida curto. Ele só funciona enquanto for necessário para atender a um evento e revive da próxima vez que ele precisa lidar com um evento.

Service workers também têm um conjunto limitado de APIs em comparação com código JavaScript executado a partir do contexto de uma página web. Service workers, por exemplo, não tem acesso ao DOM, mas tem acesso ao Cache Storage API, e eles podem fazer requests usando a Fetch API. A IndexedDB API e postMessage() também estão disponíveis para persistência de dados e troca de mensagens entre o service worker e as páginas que são controladas.

Um service worker pode interceptar requests feitos a partir de uma página web (que dispara um evento fetch no service worker) e retornar uma resposta recuperada da rede ou recuperada de um cache local, ou até mesmo construída programaticamente! A parte interessante é que, independentemente de onde a resposta vem, uma vez que o service worker retorna para a página web, a resposta parece exatamente como se não tivesse havido o envolvimento do service worker.

Nada tem que mudar na sua página web, mesmo se as respostas estão vindo de um cache local porque o seu navegador está offline.

Agora que já conhecemos mais sobre a teoria por trás dos service workers, vamos explorar como utilizar os elementos <platinum-sw> para registrar um service worker para nossa aplicação web e implementar uma estratégia básica de caching que permitirá nossa aplicação funcionar offline. Nós olharemos no código do diretório step2/.

A maior mudança entre o step1/ e step2/ foi a adição dos seguintes elementos:

index.html

<platinum-sw-register skip-waiting
                      clients-claim
                      auto-register
                      reload-on-install>
  <platinum-sw-cache default-cache-strategy="fastest"></platinum-sw-cache>
</platinum-sw-register>

Muita coisa acontecendo nesse código, então vamos explorar esses novos elementos e as configurações dos atributos:

<platinum-sw-register>

Este é um elemento principal que gerencia o registro do service worker para controlar a página baseado na configuração provida via os elementos filho <platinum-sw-*>. O arquivo sw-import.js, localizado no mesmo diretório do arquivo index.html, contém o código para nossa implementação do service worker.

Nós usamos os seguintes atributos para configurar o comportamento:

<platinum-sw-cache>

Este elemento precisa ser um filho do elemento <platinum-sw-register> e é usado para configurar o comportamento do cache. Por debaixo dos panos, o elemento <platinum-sw-cache> faz uso da biblioteca sw-toolbox e a documentação deste projeto entra em detalhes sobre seu comportamento.

Neste caso, nós estamos fazendo uso da estratégia de cache chamada fastest, o que fará com que o nosso service worker intercepte todas as requisições e retorne uma resposta em cache ou uma resposta da rede, nesse caso o que ocorrer primeiro. Embora seja quase sempre o caso que a resposta em cache seja retornada primeiro, a vantagem de fazer simultaneamente um pedido de rede é que o cache será automaticamente atualizado com a nova resposta, resultando num cache que é mantido relativamente atualizado.

O diagrama a seguir ilustra esta estratégia de cache, assumindo que a versão em cache é retornada para a página e a resposta da rede atualiza o cache:

A seção correspondente do The Offline Cookbook explica essa teoria detalhadamente.

Lembra que a nossa aplicação web tem uma "casca", composta de arquivos estáticos HTML/JavaScript/CSS, distinto das requisções do clima atual? Vamos aproveitar essa estrutura agora e implementar diferentes estratégias de cache para a "casca" e as requisições dinâmicas.

Em virtude do <platinum-sw-cache default-cache-strategy="fastest"> adicionado na etapa anterior, nós temos por padrão o cache aplicado a todas nossas requisições de recursos, mas este cache é condicionado ao fato de que as URLs dos recursos se mantenham as mesmas.

Isso não é um problema para os nossos recursos estáticos—nosso arquivo HTML sempre será chamado de index.html. Mas o nosso aplicativo web faz requisições a API do Yahoo! Weather que tem URLs que mudam com base na latitude e longitude atuais do usuário, como os valores 40.741 e -74.002 no exemplo a seguir:

https://query.yahooapis.com/v1/public/yql?format=json&q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(SELECT%20woeid%20FROM%20geo.placefinder%20WHERE%20text%3D%2240.741%2C-74.002%22%20and%20gflags%3D%22R%22)

Fazer cache de cada uma dessas URLs independentemente não levará a experiência que seus usuários esperam. Ao em vez disso, nosso aplicativo deve armazenar e recuperar a última resposta da API independente da localização enviada nos parâmetros de busca da URL. Dessa forma se um usuário estiver offline o aplicativo ainda será capaz de mostrar alguma coisa (a requisição mais recente), mesmo se essa resposta não for a localização atual

Para implementar esse comportamento customizado, utilizaremos dois elementos adicionais do Polymer e escreveremos um sw-toolbox request handler. O código desta seção corresponde ao que está no diretório /final, uma vez que este é nosso último passo.

<platinum-sw-import-script>

Primeiro, vamos usar o elemento <platinum-sw-import-script> para carregar a definição do nosso request handler customizado usando o atributo href para especificar o arquivo necessário.

<platinum-sw-import-script href="weather-fetch-handler.js">

Aqui está o codigo completo do handler acompanhado de alguns comentários:

weather-fetch-handler.js

(function(global) {
  // Remove a parte de pesquisa/consulta da URL
  // Ex: stripSearchParameters("http://example.com/index.html?a=b&c=d")
  //      ➔ "http://example.com/index.html"
  function stripSearchParameters(url) {
    var strippedUrl = new URL(url);
    strippedUrl.search = '';
    return strippedUrl.toString();
  }

  global.weatherFetchHandler = function(request) {
    // Tentativa de fetch (requisição). Isto irá sempre fazer uma solicitação de rede, e incluirá a
    // requisição completa, incluindo os parâmetros de busca.
    return global.fetch(request).then(function(response) {
      if (response.ok) {
        // Se tivermos uma resposta bem sucedida, ótimo!
        return global.caches.open(global.toolbox.options.cacheName).then(function(cache) {
          // Primeiro, armazena a resposta em cache, removendo os parâmetros de pesquisa
          // para normalizar a URL.
          return cache.put(stripSearchParameters(request.url), response.clone()).then(function() {
            // Uma vez que a entrada é gravada no cache, retorne a resposta para a página controlada
            return response;
          });
        });
      }

      // Se uma resposta de erro for retornada, lançamos um novo Erro, que irá desencadear o catch().
      throw new Error('A response with an error status code was returned.');
    }).catch(function(error) {
      // Este código é executado quando há um erro de rede ou uma resposta com um código de status de erro.
      return global.caches.open(global.toolbox.options.cacheName).then(function(cache) {
        // Normaliza a URL removendo os parâmetros de pesquisa e, em seguida, retorna a 
        // resposta em cache como um fallback.
        return cache.match(stripSearchParameters(request.url));
      });
    });
  }
})(self);

<platinum-sw-fetch>

Agora que importamos a definição do nosso weatherFetchHandler customizado, estamos prontos para usá-lo para gerenciar as requisições. O elemento <platinum-sw-fetch> cuida da associação entre um request handler, URL externa e o path pattern. No nosso caso queremos que o handler de requisições customizado faça a associação entre as URLs da API do Yahoo! Weather com https://query.yahooapis.com/v1/public/yql, para isso usamos:

<platinum-sw-fetch handler="weatherFetchHandler"
                   path="/v1/public/yql"
                   origin="https://query.yahooapis.com">
</platinum-sw-fetch>

Agora você tem um aplicativo web que exibe o clima atual e que funciona offline graças ao elemento <platinum-sw>. Utilizando uma estratégia de cache personalizada para requisições dinâmicas à API e a estratégia de cache "fastest" para o conteúdo estático da aplicação web.

O que aprendemos

Próximos passos

Aprenda mais