Progressive Web Apps são aplicações que combinam o melhor que a web oferece com o melhor oferecido pelos aplicativos nativos. Elas são úteis para os usuários desde o primeiro acesso via navegador, sem nenhuma necessidade de instalação. A medida que o usuário interaje e se engaja com o site, ele vai se tornando cada vez mais poderoso. PWAs carregam rápido, mesmo em conexões mais fracas, enviam notificações via push, tem um ícone na tela inicial do usuário e carregam como aplicações de primeiro porte, oferecendo uma experiência em tela cheia.
Um Progressive Web App é:
Esse codelab irá apresentar a você como criar seu próprio Progressive Web App, incluindo desde considerações de desing até detalhes da implementação do código, para certificar que você aprendeu os princípios chaves de um Progressive Web App.
Nesse codelab, você irá construir um aplicativo de Previsão do Tempo utilizando as técnicas de construção de um PWA. Seu aplicativo irá:
|
Esse codelab é focado em Progressive Web Apps. Conceitos não ligados diretamente as PWAs e alguns códigos serão providos diretamente para você para simplesmente copiar e colar, para que você foque no aprendizado central do codelab.
Clique no link a seguir para fazer o download do código-fonte base do codelab:
Extraia os arquivos do zip. Você agora terá uma pasta (your-first-pwapp-master
), que contém uma pasta
para cada etapa desse codelab, com todos os arquivos extras que você possa precisar.
As pastas step-NN
contém o estado final de cada etapa desse codelab. O próposito dessas pastas é para
referência. Nós iremos escrever todo o código na pasta work
.
Você pode usar qualquer aplicação para ser o seu sevidor local, mas esse codelab foi desenhado pensando no uso com o Chrome Web Server. Se você ainda não o tem instalado, você pode instalar ele diretamente da Chrome Web Store.
Depois de instalar o aplicativo do Web Server for Chrome, clique no atalho de Apps na sua barra de favoritos:
Na página que acabou de abrir, clique no ícone do Web Server:
Você irá ver esse diálogo a seguir, que permite você à configurar o seu servidor local:
Clique no botão choose folder, e selecione a pasta work
. Isso irá permitir você acessar
os arquivos dessa pasta pela URL destacada na aplicação. (na seção Web Server URL(s)).
Em Options, marque o checkbox de "Automatically show index.html", como demonstrado abaixo:
Agora visite o seu site no navegador. Você deve ver uma página mais ou menos assim:
Esse app ainda não faz nada interessante - até agora, é só um esqueleto simples. Nós iremos adicionar as funcionalidades e desenvolver a interface nas próximas etapas.
A app shell (casca da aplicação) é o mínimo de HTML, CSS e JavaScript que é necessário para o funcionamento da interface de um Progressive Web App e um dos componentes que garante ótima performance. Este primeiro carregamento deve ser extremamente rápido e armazenado imediatamente no cache.Isso significa que os arquivos correspondetes ao shell são carregados apenas uma vez do servidor, e a partir daí, irão ser carregados localmente pelo dispositivo, o que garante um carregamento praticamente instantâneo do website.
A arquitetura de um App shell separa o cerne da aplicação da interface do usuário e dos dados gerados ou armazenados. Toda a interface e estrutura são armazenadas localmente usando um service worker para que nas próximas vezes que a aplicação seja carregada, apenas o necessário seja requisitado, ao invés de carregar tudo de novo mais uma vez.
Um service worker é um script que permite que seu navegador execute funções em segundo plano, mesmo que sua página não esteja aberta. Uma limitação do servie worker é que ele não tem nenhuma interação direta com o usuário, entretanto, se torna uma ferramenta extremamente poderosa quando utilizada em todo seu potencial.
Colocando de outra forma, o app shell é similar a porção de código que você publicaria numa loja de aplicativos, por exemplo, se estivessemos construindo um aplicativo nativo. São os componentes chave necessários para a aplicação funcionar, mas normalmente não contém nenhum tipo de dado.
Usar essa arquitetura permite que você tenha foco na velocidade, dando uma propriedade ao seu Progressive Web App similar properties to as dos aplicativos nativos: carregamento instantâneo e atualizações regulares, tudo isso sem precisar enviar para uma loja de aplicativos.
A primeira etapa é quebrar a construção da shell em componentes fundamentais.
Pergunte à si mesmo:
Nós iremos criar um aplicativo de Previsão do Tempo como nosso primeiro Progressive Web App. Os componentes principais são:
|
Quando estiver desenvolvendo uma aplicação mais complexa, conteúdos que não são necessários para o primeiro carregamento podem ser requisitados posteriormente e então armazenados no cache para uso futuro. Por exemplo, nós podemos esperar um pouco para carregar as informações de temperatura de Nova Iorque para o primeiro carregamento, mas depois podemos utiliza-las para um carregamento instantâneo (enquanto é feita uma requisição em segundo plano, atualizando os valores se necessário).
Existem multiplas maneiras para começar um projeto web, e nós geralmente sugerimos usar o Kit do iniciante na Web. Mas, nesse caso, para manter o projeto o mais simples possível, nós iremos fornecer tudo que você precisa para desenvolver esse projeto
Nós agora iremos desenvolver a arquitetura da App Shell.
Lembre-se de que os componentes principais consistirão em:
O arquivo index.html
que já está em seu diretório work
deve ser algo parecido com isso (este
é um subconjunto do conteúdo efetivo, não copie este código em seu arquivo):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather PWA</title>
<link rel="stylesheet" type="text/css" href="styles/inline.css">
</head>
<body>
<header class="header">
<h1 class="header__title">Weather PWA</h1>
<button id="butRefresh" class="headerButton"></button>
<button id="butAdd" class="headerButton"></button>
</header>
<main class="main">
<div class="card cardTemplate weather-forecast" hidden>
. . .
</div>
</main>
<div class="dialog-container">
. . .
</div>
<div class="loader">
<svg viewBox="0 0 32 32" width="32" height="32">
<circle id="spinner" cx="16" cy="16" r="14" fill="none"></circle>
</svg>
</div>
<!-- Insert link to app.js here -->
</body>
</html>
Perceba que o carregador é visível por padrão. Isso garante que o usuário veja o carregador imediatamente conforme a página é carregada, o que oferece uma indicação clara de que o conteúdo está sendo carregado.
Para poupar tempo, já criamos a folha de estilo para seu uso.
Agora que grande parte da IU está pronta, é hora de começar a conectar o código para fazer tudo funcionar. Como o restante do shell do aplicativo, tenha consciência dos códigos que são necessários como parte da experiência principal e o que pode ser carregado posteriormente.
Seu diretório de trabalho também já inclui o código do aplicativo (scripts/app.js
). Nele, você encontrará:
app
que contém algumas informações essenciais necessárias para o aplicativo.add/refresh
) e na caixa de diálogo de adição
de cidade (add/cancel
).app.updateForecastCard
).app.getForecast
).app.getForecast
para obter os últimos dados de previsão
(
app.updateForecasts
).initialWeatherForecast
) que podem ser usados para testar rapidamente a renderização
dos elementos.Agora que você tem o HTML, os estilos e o JavaScript principais, chegou a hora de testar o aplicativo.
Para ver como os dados falsos de meteorologia são processados, retire o comentário da seguinte linha na parte inferior
do seu arquivo index.html
:
<!--<script src="scripts/app.js" async></script>-->
Em seguida, remova o comentário da seguinte linha na parte inferior do seu arquivo app.js
:
// app.updateForecastCard(initialWeatherForecast);
Atualize seu aplicativo. O resultado deve ser um cartão de previsão bem formatado (embora falso, como pode ser percebido pela data) cartão de previsão com o controle giratório desativado, como este:
Depois de experimentar e verificar que ele funciona como esperado, você pode remover a chamada para app.updateForecastCard
com os dados falsos. Ela foi necessária apenas para garantir que tudo funcionava como esperado.
Progressive Web Apps devem ser iniciados rapidamente e disponibilizados para uso imediatamente. No seu estado atual, nosso aplicativo de previsão do tempo é iniciado rapidamente, mas não pode ser usado. Não há dados. Nós poderíamos fazer uma solicitação AJAX para obter os dados, mas isso resultaria em uma solicitação adicional e faria o carregamento inicial demorar mais. Em vez disso, forneça dados reais no primeiro carregamento.
Neste codelab, simularemos o servidor injetando a previsão do tempo diretamente no JavaScript, mas em um aplicativo de produção, os dados de previsão do tempo mais recentes seriam injetados pelo servidor com base na geolocalização do endereço IP do usuário.
O código já contém os dados que injetaremos. É a initialWeatherForecast
que usamos no passo anterior.
Então, como podemos saber quando exibir essas informações, que podem não ser relevantes em carregamentos futuros, quando o aplicativo de previsão for coletado do cache? Quando o usuário carregar o aplicativo em visitas subsequentes, ele poderá estar em uma cidade diferente, então será preciso carregar as informações dessa cidade, não necessariamente da primeira cidade que foi procurada.
As preferências do usuário, como a lista de cidades nas quais um usuário está inscrito, devem ser armazenadas localmente usando o IndexedDB ou outro mecanismo de armazenamento rápido. Para simplificar este codelab o máximo possível, usamos localStorage, que não é ideal para aplicativos de produção por ser um mecanismo de armazenamento sincrônico com bloqueio que pode ser muito lento em alguns dispositivos.
Primeiro, vamos adicionar o código necessário para salvar as preferências do usuário. Localize o seguinte comentário TODO em seu código.
// TODO add saveSelectedCities function here
E adicione o código a seguir abaixo do comentário.
// Save list of cities to localStorage.
app.saveSelectedCities = function() {
var selectedCities = JSON.stringify(app.selectedCities);
localStorage.selectedCities = selectedCities;
};
Em seguida, adicionamos o código de inicialização para verificar se o usuário salvou alguma cidade e renderizar esses dados, ou usar os dados injetados. Localize o seguinte comentário:
// TODO add startup code here
E adicione o código a seguir abaixo desse comentário:
/************************************************************************
*
* Code required to start the app
*
* OBSERVAÇÃO: To simplify this codelab, we've used localStorage.
* localStorage is a synchronous API and has serious performance
* implications. It should not be used in production applications!
* Instead, check out IDB (https://www.npmjs.com/package/idb) or
* SimpleDB (https://gist.github.com/inexorabletash/c8069c042b734519680c)
************************************************************************/
app.selectedCities = localStorage.selectedCities;
if (app.selectedCities) {
app.selectedCities = JSON.parse(app.selectedCities);
app.selectedCities.forEach(function(city) {
app.getForecast(city.key, city.label);
});
} else {
/* The user is using the app for the first time, or the user has not
* saved any cities, so show the user some fake data. A real app in this
* scenario could guess the user's location via IP lookup and then inject
* that data into the page.
*/
app.updateForecastCard(initialWeatherForecast);
app.selectedCities = [
{key: initialWeatherForecast.key, label: initialWeatherForecast.label}
];
app.saveSelectedCities();
}
O código de inicialização verifica se existem cidades salvas no armazenamento local. Se houver, ele analisa os dados de armazenamento local e exibe um cartão de previsão para cada uma das cidades salvas. Caso contrário, o código de inicialização usa apenas os dados falsos de previsão e salva isso como a cidade padrão.
Finalmente, você precisa modificar o gerenciador do botão "add city" para salvar a cidade escolhida no armazenamento local.
Atualize seu gerenciador de clique butAddCity
para que ele corresponda ao seguinte código:
document.getElementById('butAddCity').addEventListener('click', function() {
// Add the newly selected city
var select = document.getElementById('selectCityToAdd');
var selected = select.options[select.selectedIndex];
var key = selected.value;
var label = selected.textContent;
if (!app.selectedCities) {
app.selectedCities = [];
}
app.getForecast(key, label);
app.selectedCities.push({key: key, label: label});
app.saveSelectedCities();
app.toggleAddDialog(false);
});
As novas adições são a inicialização de app.selectedCities
se ele não existir, e as chamadas para app.selectedCities.push()
e app.saveSelectedCities()
.
initialWeatherForecast
.Progressive Web Apps precisam ser rápidos e instaláveis, o que significa que eles devem funcionar on-line, off-line ou em condições intermitentes e lentas. Para conseguir isso, precisamos armazenar em cache nosso shell de aplicativo usando service worker para que ele seja sempre disponibilizado de forma rápida e confiável.
Se não tiver experiência com service workers, você pode obter uma noção básica lendo Introdução aos service workers sobre o que eles podem fazer, como seu ciclo de vida funciona e muito mais. Após concluir este codelab, certifique-se de verificar o codelab Depurar Service Workers para ter uma visão mais detalhada de como trabalhar com service workers.
Recursos fornecidos por service workers devem ser considerados aprimoramentos progressivos e adicionados apenas se o navegador for compatível. Por exemplo, com service workers, você pode armazenar em cache o shell de aplicativo e os dados do seu aplicativo para que eles estejam disponíveis mesmo quando a rede não estiver. Quando service workers não forem compatíveis, o código off-line não será chamado e o usuário terá uma experiência básica. O uso da detecção de recursos para fornecer aprimoramentos progressivos incorre em poucos custos adicionais e não falhará em navegadores mais antigos incompatíveis com o recurso.
A primeira etapa necessária para fazer com que o aplicativo funcione off-line é registrar um service worker, um script que oferece a funcionalidade off-line sem precisar de uma página da Web aberta ou de interação do usuário.
Bastam duas etapas simples:
Primeiro, precisamos verificar se o navegador oferece suporte a service workers e, em caso positivo, registrar o service
worker. Adicionar o seguinte código ao app.js
(após o comentário // TODO add service worker code here
):
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./service-worker.js')
.then(function() { console.log('Service Worker Registered'); });
}
Quando o service worker é registrado, um evento de instalação é acionado na primeira vez que o usuário visitar a página. Nesse gerenciador de eventos, nós armazenaremos em cache todos os ativos dos quais o aplicativo precisa.
Quando o service worker é acionado, ele deve abrir o objeto caches e preenchê-lo com os ativos necessários para carregar o shell do aplicativo. Crie um arquivo chamado service-worker.js
na pasta raiz do seu aplicativo (que deve ser o diretório your-first-pwapp-master/work
). Esse arquivo
deve estar ativo na raiz do aplicativo, poiso escopo dos service workers é definido pelo diretório onde o arquivo
se encontra. Adicione este código ao seu novo arquivo service-worker.js
:
var cacheName = 'weatherPWA-step-6-1';
var filesToCache = [];
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(filesToCache);
})
);
});
Primeiro, precisamos abrir o cache com caches.open()
e fornecer um nome de cache. Fornecer um nome de
cache nos permite distinguir as versões dos arquivos, ou separar os dados do shell do aplicativo para atualizarmos
um sem afetar o outro com facilidade.
Quando cache estiver aberto, podemos chamar cache.addAll()
, que aceita uma lista de URLs e os recupera
do servidor e os adiciona à resposta ao cache. Infelizmente, cache.addAll()
é atômico e, se qualquer
arquivo falhar, toda a etapa do cache também falha.
Muito bem, vamos começar nos familiarizar com a forma como você pode usar DevTools para entender e debug service workers. Antes de recarregar sua página, abra DevTools, vá ao painel Service Worker no painel Application. Ele deve ter a aparência a seguir.
Quando se vê uma página em branco como esta, isso significa que a página atualmente aberta não possui service workers registrados.
Agora, atualize sua página. O painel Service Worker deve ter a aparência a seguir.
Quando você vê informações como estas, isso significa que a página tem um service worker em execução.
OK, agora faremos um breve desvio para demonstrar um problema que você pode encontrar ao desenvolver service workers.
Para demonstrar, vamos adicionar um ouvinte de evento activate
abaixo do ouvinte de evento install
em seu arquivo service-worker.js
.
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activate');
});
O evento activate
é acionado quando o service worker inicia.
Abra o Console do DevTools e recarregue a página, alterne para o painel Service Worker no painel Application e clique
em inspecionar no service worker ativado. Você espera ver a mensagem [ServiceWorker] Activate
registrada
para o console, mas isso não aconteceu. Confira seu painel Service Worker e você pode ver que o novo service worker
(que inclui ativar o ouvinte de evento) parece estar em um estado de "espera".
Basicamente, o antigo service worker continua a controlar a página enquanto houver uma guia aberta para página. Então, você poderia fechar e reabrir a página ou pressionar o botão skipWaiting, mas uma solução de longo prazo é simplesmente ativar a caixa de seleção Update on Reload no painel Service Worker do DevTools. Quando esta caixa de seleção está ativada, o service worker é forçosamente atualizado toda vez que a página recarrega.
Ative a caixa de seleção atualizar ao recarregar e recarregue a página para confirmar que o novo service worker é ativado.
Observação: Você pode ver um erro no painel Service Worker do painel Application semelhante ao mostrado abaixo, é seguro ignorar este erro.
Por enquanto é só sobre a inspeção e depuração de service workers no DevTools. Mostraremos mais alguns truques depois. Vamos voltar para a construção do seu aplicativo.
Vamos expandir sobre o ouvinte de evento activate
para incluir alguma lógica para atualizar o cache. Atualize
seu código para coincidir com o código abaixo.
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activate');
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== cacheName) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
Este código garante que o service worker atualiza seu cache sempre que qualquer um dos arquivos do shell do aplicativo
mudar. Para que isso funcione, você precisa incrementar a variável cacheName
na parte superior do seu
arquivo do service worker.
A última declaração corrige um corner case sobre o qual você pode ler na caixa de informações (opcional) abaixo.
Por fim, vamos atualizar a lista de arquivos necessários para o shell do aplicativo. Na matriz, precisamos incluir
todos os arquivos dos quais o aplicativo precisa, incluindo imagens, JavaScript, folhas de estilo etc. Perto do topo
do seu arquivo service-worker.js
, substitua var filesToCache = [];
pelo o código abaixo:
var filesToCache = [
'/',
'/index.html',
'/scripts/app.js',
'/styles/inline.css',
'/images/clear.png',
'/images/cloudy-scattered-showers.png',
'/images/cloudy.png',
'/images/fog.png',
'/images/ic_add_white_24px.svg',
'/images/ic_refresh_white_24px.svg',
'/images/partly-cloudy.png',
'/images/rain.png',
'/images/scattered-showers.png',
'/images/sleet.png',
'/images/snow.png',
'/images/thunderstorm.png',
'/images/wind.png'
];
Nosso aplicativo ainda não funciona off-line. Nós armazenamos em cache os componentes do shell do aplicativo, mas ainda precisamos carregá-los do cache local.
Service workers fornecem a capacidade de interceptar solicitações feitas do nosso Progressive Web App e gerenciá-las no service worker. Isso significa que podemos determinar como queremos gerenciar a solicitação e potencialmente fornecer nossa própria resposta de cache.
Por exemplo:
self.addEventListener('fetch', function(event) {
// Do something interesting with the fetch here
});
Agora vamos fornecer a estrutura do aplicativo do cache. Adicione o seguinte código na parte inferior do seu arquivo
service-worker.js
:
self.addEventListener('fetch', function(e) {
console.log('[ServiceWorker] Fetch', e.request.url);
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
De dentro para fora, o caches.match()
avalia a solicitação da Web que acionou o evento de busca e verifica se ele está disponível no cache. Em seguida, ele responde com a versão do cache ou usa fetch
para obter uma cópia da rede. A response
é passada à página da Web com e.respondWith()
.
Agora seu aplicativo tem funcionalidade off-line. Vamos experimentar.
Atualize sua página e, em seguida, vá para o painel Cache Storage no painel Application do DevTools. Expanda a seção e você deve ver o nome do cache do seu shell do aplicativo listado do lado esquerdo. Ao clicar no cache do seu shell do aplicativo, você pode ver todos os recursos que estão armazenados em cache atualmente.
Agora, vamos testar o modo off-line. Volte para o painel Service Worker do DevTools e ative a caixa de seleção Offline. Após ativá-la, você deve ver um ícone de aviso amarelo pequeno ao lado da guia do painel Network. Isto indica que você está off-line.
Atualize sua página e... ela funciona! Quer dizer, mais ou menos. Observe como ela carrega os dados meteorológicos iniciais (falsos).
Confira a cláusula else
em app.getForecast()
para entender por que o aplicativo consegue
carregar os dados falsos.
O próximo passo é modificar a lógica do aplicativo e do service worker para poder armazenar dados meteorológicos em cache e retornar os dados mais recentes do cache quando o aplicativo estiver off-line.
Dica: Para começar do zero, limpar todos os dados salvos (localStoarge, dados de indexedDB, arquivos armazenados em cache) e remover quaisquer service workers, use o painel Clear storage na guia Application.
Como já mencionamos, esse código não deve ser usado em produção devido aos muitos casos de borda não gerenciados.
Por exemplo, esse método de armazenamento em cache exige que você atualize a chave de cache sempre que o conteúdo for alterado, caso contrário, o cache não será atualizado e o conteúdo antigo será fornecido. Portanto, não deixe de alterar a chave de cache após cada alteração enquanto trabalha no seu projeto.
Outra desvantagem é que todo o cache é invalidado e precisa ser baixado novamente sempre que um arquivo é alterado. Isso significa que a correção de um simples erro de ortografia invalidará o cache e exigirá que tudo seja baixado novamente. Isso não é muito eficiente.
Existe outra ressalva. É essencial que a solicitação HTTPS realizada durante o gerenciador de instalação vá diretamente para a rede e não retorne uma resposta do cache do navegador. Caso contrário, o navegador poderá retornar a versão de cache antiga, resultando em um service worker que nunca é atualizado realmente.
Nosso aplicativo usa uma estratégia que prioriza o cache, o que resulta em uma cópia de qualquer conteúdo no cache sendo retornada sem consultar a rede. Embora esse tipo de estratégia seja fácil de implementar, ela pode causar problemas no futuro. Depois que a cópia da página do host e do registro do service worker é armazenada em cache, pode ser extremamente difícil alterar a configuração do service worker (pois a configuração depende de onde ele foi definido) e você pode acabar implantando sites muito difíceis de atualizar.
Então, como evitar esses casos de borda? Use uma biblioteca como sw-precache, que oferece um controle preciso do que é expirado, garante que as solicitações vão diretamente para a rede e faz todo o trabalho pesado para você.
A depuração de service workers pode ser um desafio e, quando ela envolve o cache, o problema pode ser ainda maior se o cache não for atualizado quando você espera. Entre o ciclo de vida de um service worker e os erros típicos no seu código, você pode se frustrar rapidamente. Mas não desanime. Existem algumas ferramentas que podem facilitar sua vida.
Em alguns casos, você pode perceber que está carregando dados armazenados em cache ou que as coisas não são atualizadas conforme o esperado. Para limpar todos os dados salvos (localStoarge, dados de indexedDB, arquivos armazenados em cache) e remover quaisquer service workers, use o painel Clear storage na guia Application.
Algumas outras dicas:
Escolher a estratégia de armazenamento em cache certa é essencial e depende do tipo de dados apresentado por seu aplicativo. Por exemplo, dados que dependem do momento, como dados meteorológicos ou a cotação da bolsa, devem ser o mais atualizados possível, enquanto imagens de avatar ou o conteúdo de artigos podem ser atualizados com menos frequência.
A estratégia cache-primeiro-depois-rede é ideal para o nosso aplicativo. Ele apresenta dados na tela com a máxima rapidez possível e atualiza esses dados quando a rede retornar as informações mais recentes. Em comparação com a estratégia que prioriza a rede e depois o cache, o usuário não precisa aguardar até que o evento fetch atinja o tempo limite para obter os dados do cache.
Priorizar o cache em vez da rede significa que precisamos acionar duas solicitações assíncronas, uma para o cache e outra para a rede. Nossa solicitação de rede com o aplicativo não precisa mudar muito, mas devemos modificar o service worker para armazenar a resposta em cache antes de retorná-la.
Em circunstâncias normais, dos dados do cache serão retornados quase imediatamente, fornecendo ao aplicativo dados recentes que podem ser usados. Em seguida, quando a solicitação de rede retornar, o aplicativo será atualizado usando os dados mais recentes da rede.
Nós precisamos modificar o service worker para interceptar solicitações para a Weather API e armazenar suas respostas no cache para que possamos acessá-las com facilidade posteriormente. Na estratégia que prioriza o cache em vez da rede, esperamos que a resposta da rede seja a "fonte da verdade", sempre nos fornecendo as informações mais recentes. Se isso não for possível, não há problema, pois já recuperamos os dados de cache mais recentes no nosso aplicativo.
No service worker, vamos adicionar um dataCacheName
para que possamos separar os dados do aplicativo do
shell do aplicativo. Quando o shell do aplicativo for atualizado e os caches mais antigos forem limpos, nossos dados
estarão intocados, prontos para um carregamento rápido. Lembre-se de que, se o formato dos seus dados for alterado
no futuro, você precisará de uma maneira para gerenciar isso e garantir que o shell do aplicativo e o conteúdo permaneçam
sincronizados.
Adicione a seguinte linha à parte superior do seu arquivo service-worker.js
:
var dataCacheName = 'weatherData-v1';
Em seguida, atualize o gerenciador de eventos activate
para não excluir o cache de dados ao limpar o cache
do shell do aplicativo.
if (key !== cacheName && key !== dataCacheName) {
Finalmente, atualize o gerenciador de eventos fetch
para gerenciar solicitações para a API de dados separadamente
de outras solicitações.
self.addEventListener('fetch', function(e) {
console.log('[Service Worker] Fetch', e.request.url);
var dataUrl = 'https://query.yahooapis.com/v1/public/yql';
if (e.request.url.indexOf(dataUrl) > -1) {
/*
* When the request URL contains dataUrl, the app is asking for fresh
* weather data. In this case, the service worker always goes to the
* network and then caches the response. This is called the "Cache then
* network" strategy:
* https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
*/
e.respondWith(
caches.open(dataCacheName).then(function(cache) {
return fetch(e.request).then(function(response){
cache.put(e.request.url, response.clone());
return response;
});
})
);
} else {
/*
* The app is asking for app shell files. In this scenario the app uses the
* "Cache, falling back to the network" offline strategy:
* https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
*/
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
}
});
O código intercepta a solicitação e verifica se o URL é iniciado pelo endereço da Weather API. Em caso positivo, usaremos fetch para realizar a solicitação. Quando a resposta for retornada, nosso código abrirá o cache, clonará a resposta, a armazenará no cache e, por fim, a retornará para o solicitador original.
Nosso aplicativo ainda não funciona off-line. Já implementamos o armazenamento em cache e a recuperação para o shell do aplicativo, mas mesmo armazenando dados em cache, o aplicativo ainda não verifica o cache para ver se há algum dado meteorológico.
Como já mencionamos, o aplicativo precisa acionar duas solicitações assíncronas, uma para o cache e outra para a rede.
O aplicativo usa o objeto caches
disponível em window
para acessar o cache e recuperar
os dados mais recentes. Esse é um exemplo excelente de aprimoramento progressivo, pois o objeto caches
pode não estar disponível em todos os navegadores e, se não houver uma solicitação de rede, ele ainda funcionará.
Para isso, é preciso:
caches
está disponível no objeto global window
.Solicitar dados do cache.
Se a solicitação do servidor ainda estiver pendente, atualizar o aplicativo com os dados em cache.
Solicitar dados do servidor.
Salvar os dados para acesso rápido posteriormente.
Em seguida, precisamos verificar se o objeto caches
existe e solicitar os dados mais recentes dele. Localize
o comentário TODO add cache logic here
em app.getForecast()
, e depois adicione o código
abaixo do comentário.
if ('caches' in window) {
/*
* Check if the service worker has already cached this city's weather
* data. If the service worker has the data, then display the cached
* data while the app fetches the latest data.
*/
caches.match(url).then(function(response) {
if (response) {
response.json().then(function updateFromCache(json) {
var results = json.query.results;
results.key = key;
results.label = label;
results.created = json.query.created;
app.updateForecastCard(results);
});
}
});
}
Nosso aplicativo de previsão do tempo agora realiza duas solicitações de dados assíncronas, uma para o cache
e
a outra via XHR. Se houver dados no cache, eles serão retornados e renderizados com extrema rapidez (dezenas de milissegundos)
e atualizarão o cartão somente se o XHR ainda estiver pendente. Em seguida, quando o XHR responder, o cartão será
atualizado com os dados mais recentes diretamente da weather API.
Repare como a solicitação de cache e a solicitação XHR terminam com uma chamada para atualizar o cartão de previsão.
Como o app sabe se ele está exibindo os dados mais recentes? Isso é gerenciado no seguinte código de app.updateForecastCard
:
var cardLastUpdatedElem = card.querySelector('.card-last-updated');
var cardLastUpdated = cardLastUpdatedElem.textContent;
if (cardLastUpdated) {
cardLastUpdated = new Date(cardLastUpdated);
// Bail if the card has more recent data then the data
if (dataLastUpdated.getTime() < cardLastUpdated.getTime()) {
return;
}
}
Toda vez que um cartão é atualizado, o aplicativo armazena o timestamp dos dados em um atributo oculto no cartão. O aplicativo só resgata se o timestamp que já existe no cartão for mais recente que os dados que foram passados para a função.
Agora aplicativo deve ser completamente funcional off-line. Salve algumas cidades e pressione o botão de atualização no aplicativo para obter dados meteorológicos atuais, e depois fique off-line e recarregue a página.
Em seguida, vá para o painel Cache Storage no painel Application do DevTools. Expanda a seção e você deve ver o nome do cache do seu shell do aplicativo e cache de dados listado do lado esquerdo. Abrir o cache de dados deve mostrar os dados armazenados para cada cidade.
Ninguém gosta de digitar URLs longos em um dispositivo móvel se não for absolutamente necessário. Com o recurso de adicionar à tela inicial, seus usuários podem optar por adicionar um link de atalho ao seu dispositivo nativo da mesma forma que instalariam um aplicativo nativo de uma loja de app, mas com menos atrito.
Os banners de instalação de aplicativos web permitem que seus usuários adicionem seu aplicativo web forma rápida e tranquila à tela inicial do seu dispositivo, o que facilita a inicialização e o retorno ao aplicativo. É muito fácil adicionar banners de instalação de aplicativo e o Chrome realiza a maior parte do trabalho para você. Basta incluir um arquivo de manifesto de app da Web com detalhes sobre o aplicativo.
Em seguida, o Chrome usa um conjunto de critérios, incluindo o uso de um service worker, status de SSL e dados heurísticos de frequência de visitas para determinar quando mostrar o banner. Além disso, um usuário pode adicionar o aplicativo manualmente pelo botão de menu "Add to Home Screen" no Chrome.
manifest.json
O manifesto do app da Web é um arquivo JSON simples que proporciona a você, desenvolvedor, a capacidade de controlar a aparência do seu aplicativo para o usuário nas áreas onde ele pode ver aplicativos (por exemplo, na tela inicial do celular), direcionar o que o usuário pode acessar e, o mais importante, como pode acessar.
Usando o manifesto do app da Web, seu aplicativo pode:
Crie um arquivo com o nome manifest.json
em sua pasta work
e copie/cole os conteúdos a seguir:
{
"name": "Weather",
"short_name": "Weather",
"icons": [{
"src": "images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
}, {
"src": "images/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "images/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
}, {
"src": "images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}, {
"src": "images/icons/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
}],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#3E4EB8",
"theme_color": "#2F3BA2"
}
O manifesto uma variedade de ícones, destinados a diferentes tamanhos de tela. No momento da redação deste artigo, Chrome e Opera Mobile, os únicos navegadores que suportam manifestos de apps da Web, não usam nada menor que 192px.
Uma maneira fácil de controlar como o aplicativo é inicializado é adicionar uma string de consulta ao parâmetro start_url
e depois usar um pacote de análise para rastrear a string de consulta Se usar esse método, lembre-se de atualizar
a lista de arquivos em cache pelo App Shell para garantir que o arquivo com a string de consulta está armazenado
no cache.
Agora, adicione a linha a seguir na parte inferior do elemento <head>
no seu arquivo index.html
:
<link rel="manifest" href="/manifest.json">
short_name
é preferencial no Chrome e será usado se ele estiver presente em vez do campo nome.background_color
.Leitura adicional:
Como usar banners de instalação de aplicativo
No seu index.html
, adicione o seguinte à parte inferior do elemento <head>
:
<!-- Add to home screen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Weather PWA">
<link rel="apple-touch-icon" href="images/icons/icon-152x152.png">
No seu index.html
, adicione o seguinte à parte inferior do elemento <head>
:
<meta name="msapplication-TileImage" content="images/icons/icon-144x144.png">
<meta name="msapplication-TileColor" content="#2F3BA2">
Nesta seção, mostraremos algumas maneiras de testar o manifesto do seu app da Web.
A primeira maneira é DevTools. Abra o painel Manifest no painel Application. Se adicionou as informações do manifesto corretamente, você poderá vê-las analisadas e exibidas em um formato de fácil leitura para seres humanos neste painel.
Também é possível testar o recurso de adicionar à tela principal característica a partir deste painel. Clique no botão Add to homescreen. Você deve ver uma mensagem "adicionar este site à sua estante" abaixo da sua barra de URL, como na imagem abaixo.
Este é o equivalente para desktop do recurso adicionar à tela principal de dispositivos móveis. Se conseguir acionar esta solicitação com sucesso em desktop, você pode ter certeza de que usuários de dispositivos móveis conseguem adicionar seu aplicativo a seus aparelhos.
A segunda maneira de testar é via Web Server for Chrome. Com esta abordagem, você expõe seu servidor de desenvolvimento local (no seu desktop ou laptop) a outros computadores, e depois basta acessar seu Progressive Web App de um dispositivo móvel real.
Na caixa de diálogo do Web Server for Chrome, selecione a opção Accessible on local network
:
Alterne o Web Server para STOPPED
e de volta para STARTED
. Você verá um novo URL que pode
ser usado para acessar o aplicativo remotamente.
Agora, acesse seu site a partir de um dispositivo móvel, usando o novo URL.
Você verá erros do service worker no console ao testar desta forma, porque o Service Worker não está sendo servido por HTTPS.
Usando o Chrome a partir de um dispositivo Android, tente adicionar o aplicativo à tela inicial e verificar que a tela de inicialização aparece corretamente e os ícones corretos são utilizados.
No Safari e no Internet Explorer, você também pode adicionar o aplicativo à sua tela inicial manualmente.
A etapa final é implantar nosso aplicativo de previsão do tempo em um servidor que ofereça suporte a HTTPS. Se ainda não tiver um, a abordagem mais fácil (e gratuita) é usar o conteúdo estático hospedado no Firebase. Ele é muito fácil de usar, fornece conteúdo por HTTPS e tem o apoio de uma CDN global.
Mais de uma consideração deve feita ao minificar os estilos principais e adicioná-los em linha diretamente no index.html
. O Page Speed Insights recomenda fornecer o conteúdo acima da dobra dos primeiros 15 mil bytes da solicitação.
Veja até onde você pode reduzir a solicitação inicial com todos os elementos em linha.
Leitura adicional: Regras do Page Speed Insight
Se nunca tiver usado o Firebase, você deverá criar uma conta e instalar algumas ferramentas primeiro.
npm install -g firebase-tools
Após criar a conta e fazer login, você estará pronto para implantar!
firebase login
work
) onde se encontra o aplicativo concluído: firebase init
firebase deploy
https://YOUR-FIREBASE-APP.firebaseapp.com
Leitura adicional: Guia de hospedagem do Firebase