Seu primeiro Progressive Web App

Última atualização:30/04/2019

O que faz com que um app da Web seja um Progressive Web App?

Os Progressive Web Apps oferecem uma experiência instalável, semelhante a um app, no computador e no dispositivo móvel, que são criados e entregues diretamente pela Web. Eles são apps da Web rápidos e confiáveis. E, o mais importante, eles são aplicativos da Web que funcionam em qualquer navegador. Se você está desenvolvendo um app da Web hoje, já está no caminho certo para criar um Progressive Web App.

Confiável e rápido

Cada experiência na Web precisa ser rápida, e isso é especialmente importante para Progressive Web Apps. "Rápido" refere-se ao tempo que leva para exibir conteúdo significativo na tela e proporcionar uma experiência interativa.

E precisa ser confiável rapidamente. É difícil destacar o desempenho confiável. Pense assim: o primeiro carregamento de um app nativo é frustrante. Ele é controlado por uma app store e por um grande download. No entanto, quando você chegar ao ponto do aplicativo instalado, o custo inicial será amortizado em todas as inicializações, e nenhuma dessas inicializações terá um atraso variável. Cada inicialização do aplicativo é tão rápida quanto a última, sem variação. Um Progressive Web App precisa oferecer um desempenho confiável que os usuários esperam de qualquer experiência instalada.

Instalável

Os Progressive Web Apps podem ser executados em uma guia do navegador, mas também podem ser instalados. Adicionar um site aos favoritos só adiciona um atalho, mas um Progressive Web App (PWA) instalado se parece com todos os outros apps instalados. Ele é iniciado no mesmo lugar que outros apps. Você pode controlar a experiência de inicialização, incluindo uma tela de apresentação personalizada, ícones e muito mais. Ele é executado como um app em uma janela do app sem uma barra de endereço ou outra IU do navegador. Como todos os outros apps instalados, ele é um app de nível superior no seletor de tarefas.

Não se esqueça de que é fundamental que um PWA instalável seja rápido e confiável. Os usuários que instalam um PWA esperam que os apps funcionem, independentemente do tipo de conexão de rede usada. Ela é uma expectativa do valor de referência que precisa ser atendida por todos os apps instalados.

Dispositivos móveis e computadores

Com as técnicas de design responsivas, os PWAs funcionam em dispositivos móveis e em uma única base de código entre as plataformas. Se você estiver pensando em criar um app nativo, veja os benefícios que um PWA oferece.

O que você vai criar

Neste codelab, você criará um app da Web de clima usando técnicas de PWA. Esse app vai:

  • Use um design responsivo para que ele funcione em um computador ou dispositivo móvel.
  • Seja rápido e use um service worker para pré-armazenar em cache os recursos do app (HTML, CSS, JavaScript, imagens) necessários para executar e armazenar em cache os dados climáticos no tempo de execução para melhorar o desempenho.
  • ser instaláveis usando um manifesto de app da Web e o evento beforeinstallprompt para notificar o usuário de que ele pode ser instalado;

O que você aprenderá

  • Como criar e adicionar um manifesto de app da Web
  • Como oferecer uma experiência off-line simples
  • Como oferecer uma experiência off-line completa
  • Como tornar o app instalável

Este codelab é focado em Progressive Web Apps. Conceitos não-relevantes e blocos de código são citados rapidamente e fornecidos para que você simplesmente os copie e cole.

O que é necessário

  • Uma versão recente do Chrome (74 ou mais recente) Os PWAs funcionam em todos os navegadores, mas usaremos alguns recursos do Chrome DevTools para entender melhor o que está acontecendo no nível do navegador e usá-lo para testar a experiência de instalação.
  • Conhecimento sobre HTML, CSS, JavaScript e Chrome DevTools

Gerar uma chave para a API Dark Sky

Nossos dados meteorológicos vêm da API Dark Sky. Para usá-lo, será preciso solicitar uma chave de API. Ele é fácil de usar e sem custo financeiro para projetos não comerciais.

Fazer o registro da chave de API

Verifique se a chave de API está funcionando corretamente

Para testar se a chave de API está funcionando corretamente, faça uma solicitação HTTP à API DarkSky. Atualize o URL abaixo para substituir DARKSKY_API_KEY pela sua chave de API. Se tudo funcionar, você verá a previsão do tempo mais recente em Nova York.

https://api.darksky.net/forecast/DARKSKY_API_KEY/40.7720232,-73.9732319

Buscar o código

Colocamos tudo o que você precisa para este projeto em um repositório Git. Para começar, será necessário pegar o código e abri-lo no seu ambiente de desenvolvimento favorito. Neste codelab, recomendamos usar o Glitch.

Recomendado: usar o Glitch para importar o repositório

Usar o Glitch é o método recomendado para trabalhar com este codelab.

  1. Abra uma nova guia do navegador e acesse https://glitch.com.
  2. Se você não tiver uma conta, precisará se inscrever.
  3. Clique em Novo projeto e em Clone do Git Repo.
  4. Clone https://github.com/googlecodelabs/your-first-pwapp.git e clique em "OK".
  5. Quando o repositório for carregado, edite o arquivo .env e atualize-o com sua chave de API DarkSky.
  6. Clique no botão Show e escolha In a New Window para ver o PWA em ação.

Alternativa: fazer o download do código & trabalhar localmente

Se você quiser fazer o download do código para trabalhar localmente, será necessário ter uma versão recente do Node.js e uma configuração do editor de código pronta para uso.

Download do código-fonte

  1. Descompacte o arquivo ZIP transferido por download.
  2. Execute npm install para instalar as dependências necessárias para executar o servidor.
  3. Edite server.js e defina sua chave de API DarkSky.
  4. Execute node server.js para iniciar o servidor na porta 8000.
  5. Abra uma guia do navegador e acesse http://localhost:8000

Qual é nosso ponto de partida?

Nosso ponto de partida é um app básico de previsão do tempo criado para este codelab. O código foi simplificado para mostrar os conceitos deste codelab e ele tem pouco processamento de erros. Caso você opte por reutilizar um desses códigos em um app de produção, será preciso processar todos os erros e testar todo o código.

O que você pode tentar...

  1. Adicione uma nova cidade com o botão azul + no canto inferior direito.
  2. Use o botão "Atualizar" no canto superior direito.
  3. Exclua uma cidade usando o x no canto superior direito de cada card.
  4. Usando a barra de ferramentas de alternância de dispositivos no Chrome DevTools, veja como ele funciona no computador e no dispositivo móvel.
  5. Usando o painel Rede no Chrome DevTools, veja o que acontece quando você fica off-line.
  6. No painel Rede do Chrome DevTools, veja o que acontece quando a rede está limitada para a conexão 3G lenta.
  7. Adicione um atraso ao servidor de previsão alterando o valor de FORECAST_DELAY no server.js.

Auditar com o Lighthouse

O Lighthouse é uma ferramenta fácil de usar que ajuda a melhorar a qualidade dos seus sites e páginas. O Lighthouse faz auditorias de desempenho, acessibilidade, Progressive Web Apps e muito mais. Cada auditoria tem um documento de referência que explica por que ela é importante e como corrigir problemas.

Usaremos o Lighthouse para auditar nosso aplicativo Weather e verificar as mudanças que fizemos.

Vamos executar o Lighthouse

  1. Abra seu projeto em uma nova guia.
  2. Abra o Chrome DevTools e mude para o painel Auditorias. Deixe todos os tipos de auditoria ativados.
  3. Clique em Fazer auditorias. Depois de algum tempo, o Lighthouse mostrará um relatório na página.

Auditoria de Progressive Web App

Vamos nos concentrar nos resultados da auditoria dos Progressive Web App.

E há muito vermelho para se concentrar:

  • ❗FAILED:a página atual não responde com um código 200 quando off-line.
  • ❗FAILED: start_url não responde com um código 200 quando off-line.
  • ❗FAILED: não registra um service worker que controla a página e o start_url..
  • ❗FAILED:o manifesto do app da Web não atende aos requisitos para instalação.
  • ❗FAILED: não está configurado para uma tela de apresentação personalizada.
  • ❗FAILED: não define uma cor de tema da barra de endereço.

Vamos começar e corrigir alguns desses problemas.

Ao final desta seção, nosso app de clima será aprovado nas seguintes auditorias:

  • O manifesto do app da Web não atende aos requisitos para instalação.
  • não é configurada para uma tela de apresentação personalizada.
  • Não define uma cor de tema da barra de endereço.

Criar o manifesto do app da Web

O manifesto do app da Web é um arquivo JSON simples que permite que você, como desenvolvedor, controle como seu app aparecerá para o usuário.

Com o manifesto do app da Web, ele pode:

  • Diga ao navegador que você quer que o app abra em uma janela autônoma (display).
  • Defina qual página será aberta quando o app for iniciado pela primeira vez (start_url).
  • Defina como o app será exibido na base ou no Acesso rápido aos apps (short_name, icons).
  • Crie uma tela de apresentação (name, icons, colors).
  • Peça para o navegador abrir a janela no modo paisagem ou retrato (orientation).
  • E muito mais.

Crie um arquivo chamado public/manifest.json no seu projeto. Copie e cole o conteúdo a seguir:

public/manifest.json

{
  "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"
    }, {
      "src": "/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

O manifesto é compatível com uma matriz de ícones destinados a diferentes tamanhos de tela. Neste codelab, incluímos outras para integração com o iOS.

Em seguida, precisamos informar o navegador sobre nosso manifesto adicionando uma <link rel="manifest"... a cada página do nosso app. Adicione a linha a seguir ao elemento <head> no seu arquivo index.html.

public/index.html (link em inglês)

<!-- CODELAB: Add link rel manifest -->
<link rel="manifest" href="/manifest.json">

Desvio do DevTools

O DevTools oferece uma maneira rápida e fácil de verificar seu arquivo manifest.json. Abra o painel Manifest no painel Application. Se você adicionou as informações do manifesto corretamente, poderá vê-las analisadas e exibidas em um formato legível neste painel.

Adicionar ícones de metatags e ícones do iOS

O Safari no iOS não é compatível com o manifesto de app da Web (ainda). Por isso, você precisará adicionar as tags meta tradicionais ao <head> do arquivo index.html:

public/index.html (link em inglês)

<!-- CODELAB: Add iOS meta tags and icons -->
<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">

Bônus: correções fáceis do Lighthouse

Nossa auditoria do Lighthouse destacou algumas outras coisas que são muito fáceis de corrigir, então vamos cuidar disso enquanto estamos aqui.

Definir a metadescrição

Durante a auditoria de SEO, o Lighthouse anotou o seguinte "Documento sem uma metadescrição." As descrições podem ser exibidas nos resultados da Pesquisa Google. Descrições únicas e de alta qualidade tornam os resultados mais relevantes para os usuários e aumentam o tráfego de pesquisa.

Para adicionar uma descrição, adicione a seguinte tag meta ao <head> do documento:

public/index.html (link em inglês)

<!-- CODELAB: Add description here -->
<meta name="description" content="A sample weather app">

Definir a cor de tema da barra de endereço

Na auditoria de PWA, o Lighthouse anotou o recurso Não define uma cor de tema da barra de endereços. Atribuir um nome à barra de endereço do navegador para que ele corresponda às cores da sua marca cria uma experiência do usuário mais imersiva.

Para definir a cor do tema em dispositivos móveis, adicione a seguinte tag meta ao <head> do documento:

public/index.html (link em inglês)

<!-- CODELAB: Add meta theme-color -->
<meta name="theme-color" content="#2F3BA2" />

Verificar alterações com o Lighthouse

Execute o Lighthouse novamente (clicando no sinal de + no canto superior esquerdo do painel "Auditorias") e verifique as alterações.

Auditoria de SEO

  • ✅ PASSED:o documento tem uma metadescrição.

Auditoria de Progressive Web App

  • ❗FAILED:a página atual não responde com um código 200 quando off-line.
  • ❗FAILED: start_url não responde com um código 200 quando off-line.
  • ❗FAILED: não registra um service worker que controla a página e o start_url..
  • ✅ PASSED:o manifesto do app da Web atende aos requisitos para instalação.
  • ✅ PASSED:configurado para uma tela de apresentação personalizada.
  • ✅ PASSED:define uma cor de tema da barra de endereços.

Os usuários já esperam que os apps instalados tenham uma experiência de referência se estiverem off-line. Por isso, é essencial que os apps da Web instaláveis nunca mostrem o jogo de dinossauros off-line do Chrome. A experiência off-line pode abranger desde uma página off-line simples até uma experiência somente leitura com dados armazenados em cache e uma experiência off-line totalmente funcional que é sincronizada automaticamente quando a conexão de rede é restaurada.

Nesta seção, adicionaremos uma página off-line simples ao app de clima. Se o usuário tentar carregar o app off-line, ele mostrará nossa página personalizada, em vez da página off-line típica que o navegador mostra. Ao final desta seção, nosso app de clima será aprovado nas seguintes auditorias:

  • A página atual não responde com um código 200 quando off-line.
  • start_url não responde com um código 200 quando off-line.
  • Não registra um service worker que controla a página e o start_url.

Na próxima seção, substituiremos nossa página off-line personalizada por uma experiência off-line completa. Isso melhora a experiência off-line, mas, principalmente, melhora significativamente o desempenho porque a maioria dos nossos recursos (HTML, CSS e JavaScript) será armazenado e veiculado localmente, eliminando a rede como um possível gargalo.

Service workers para o resgate

Se você não conhece os service workers, é possível ter uma compreensão básica do que eles podem fazer, como seu ciclo de vida funciona e muito mais, lendo Introdução aos service workers.

Os recursos oferecidos pelos service workers devem ser considerados um aprimoramento progressivo e adicionados apenas se o navegador for compatível. Por exemplo, com os service workers, você pode armazenar em cache o shell do app e os dados do seu app para que eles estejam disponíveis mesmo quando a rede não estiver. Quando os service workers não são compatíveis, o código off-line não é chamado e o usuário tem uma experiência básica. O uso da detecção de recursos para melhoria progressiva tem pouca sobrecarga e não falhará em navegadores mais antigos que não são compatíveis com esse recurso.

Registre o service worker

A primeira etapa é registrar o service worker. Adicione o seguinte código ao arquivo index.html:

public/index.html (link em inglês)

// CODELAB: Register service worker.
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}

Esse código verifica se a API do service worker está disponível e, se estiver, o service worker em /service-worker.js será registrado quando a página for carregada.

O service worker é exibido a partir do diretório raiz, não de um diretório /scripts/. Essa é a maneira mais fácil de definir o scope do seu service worker. O scope do service worker determina quais arquivos ele controla, ou seja, de qual caminho o service worker intercepta solicitações. O scope padrão é o local do arquivo do service worker e se estende a todos os diretórios abaixo. Portanto, se service-worker.js estiver localizado no diretório raiz, o service worker controlará as solicitações de todas as páginas da Web neste domínio.

Pré-armazenar em cache a página off-line

Primeiro, precisamos informar ao service worker o que armazenar em cache. Já criamos uma página off-line simples (public/offline.html) que será exibida sempre que você não tiver uma conexão de rede.

No service-worker.js, adicione '/offline.html', à matriz FILES_TO_CACHE. O resultado final ficará assim:

public/service-worker.js (link em inglês)

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/offline.html',
];

Em seguida, precisamos adicionar o código a seguir ao evento install para instruir o service worker a pré-armazenar em cache a página off-line:

public/service-worker.js (link em inglês)

// CODELAB: Precache static resources here.
evt.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[ServiceWorker] Pre-caching offline page');
      return cache.addAll(FILES_TO_CACHE);
    })
);

Nosso evento install agora abre o cache com caches.open() e fornece um nome de cache. Fornecer um nome de cache permite criar versões de arquivos ou separar dados dos recursos armazenados em cache para que possamos atualizar um com facilidade, mas não afetar o outro.

Quando o cache estiver aberto, poderemos chamar cache.addAll(), que usa uma lista de URLs, os busca do servidor e adiciona a resposta ao cache. Observe que cache.addAll() falhará se qualquer uma das solicitações individuais falhar. Isso significa que, se a etapa de instalação for bem-sucedida, o cache ficará em um estado consistente. No entanto, se ela falhar por algum motivo, ela será repetida automaticamente na próxima vez que o service worker for iniciado.

Desvio do DevTools

Vamos ver como você pode usar o DevTools para entender e depurar service workers. Antes de recarregar a página, abra o DevTools e acesse o painel Service Workers no painel Application. Você verá o seguinte:

Quando você vir uma página em branco como esta, isso significa que a página aberta no momento não tem service workers registrados.

Atualize a página. O painel Service Workers agora ficará assim:

Se você vir informações como esta, isso significa que a página tem um service worker em execução.

Ao lado do marcador "Status", há um número (neste caso, 34251). Fique de olho no número ao trabalhar com os service workers. Essa é uma maneira fácil de saber se o service worker foi atualizado.

Excluir páginas off-line antigas

Usaremos o evento activate para limpar todos os dados antigos no nosso cache. Esse código garante que o service worker atualize o cache sempre que um dos arquivos do shell do app mudar. Para que isso funcione, é necessário incrementar a variável CACHE_NAME na parte superior do arquivo do service worker.

Adicione o seguinte código ao seu evento activate:

public/service-worker.js (link em inglês)

// CODELAB: Remove previous cached data from disk.
evt.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(keyList.map((key) => {
        if (key !== CACHE_NAME) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
);

Desvio do DevTools

Com o painel dos service workers aberto, atualize a página. Você verá o novo service worker instalado e o número de status incrementado.

O service worker atualizado assume o controle imediatamente porque nosso evento install termina com self.skipWaiting(), e o evento activate termina com self.clients.claim(). Sem eles, o service worker antigo continuaria controlando a página, desde que haja uma guia aberta para a página.

Processar solicitações de rede com falha

Por fim, precisamos lidar com eventos fetch. Usaremos uma estratégia de substituição de rede em cache. O service worker primeiro tenta buscar o recurso na rede. Se isso falhar, o service worker retornará a página off-line do cache.

public/service-worker.js (link em inglês)

// CODELAB: Add fetch event handler here.
if (evt.request.mode !== 'navigate') {
  // Not a page navigation, bail.
  return;
}
evt.respondWith(
    fetch(evt.request)
        .catch(() => {
          return caches.open(CACHE_NAME)
              .then((cache) => {
                return cache.match('offline.html');
              });
        })
);

O gerenciador fetch só precisa gerenciar as navegações de página. Portanto, outras solicitações podem ser descartadas fora do gerenciador e processadas normalmente pelo navegador. No entanto, se a solicitação .mode for navigate, use fetch para tentar receber o item da rede. Se ele falhar, o gerenciador catch abrirá o cache com caches.open(CACHE_NAME) e usará cache.match('offline.html') para conseguir a página off-line pré-armazenada em cache. O resultado é retornado ao navegador usando evt.respondWith().

Desvio do DevTools

Vamos verificar se tudo está funcionando como esperado. Com o painel Service Workers aberto, atualize a página. Você verá o novo service worker instalado e o número de status incrementado.

Também podemos verificar o que foi armazenado em cache. Acesse o painel Cache Storage no painel Application do DevTools. Clique com o botão direito do mouse em Armazenamento de cache, escolha Atualizar caches e expanda a seção. Você verá o nome do seu cache estático listado no lado esquerdo. Clique no nome do cache para ver todos os arquivos armazenados nele.

Agora, vamos testar o modo off-line. Volte para o painel Service Workers no painel Application do DevTools e marque a caixa de seleção Off-line. Depois disso, você verá um pequeno ícone de aviso amarelo ao lado da guia do painel Rede. Esse ícone indica que você está off-line.

Atualize a página e... ela funciona! Nós temos nosso panda off-line em vez do dinossauro off-line do Chrome.

Dicas para testar service workers

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 como esperado. Entre o ciclo de vida típico do service worker e um bug no seu código, você pode se frustrar rapidamente. O que não fazer:

Usar o DevTools

No painel Service Workers do painel Application, há algumas caixas de seleção que facilitarão sua vida.

  • Off-line: quando selecionado, simula uma experiência off-line e impede que qualquer solicitação seja enviada para a rede.
  • Atualização no momento da atualização: quando esta opção é marcada, o service worker mais recente é instalado, instalado e ativado imediatamente.
  • Ignorar para rede: quando esta opção é marcada, as solicitações ignoram o service worker e são enviadas diretamente para a rede.

Comece do zero

Em alguns casos, é possível que você esteja carregando dados em cache ou que os itens não estejam atualizados como esperado. Para limpar todos os dados salvos (localStorage, indexDB, arquivos armazenados em cache) e remover quaisquer service workers, use o painel Clear storage no painel Application. Também é possível trabalhar em uma janela anônima.

Dicas adicionais:

  • Depois que o registro de um service worker é cancelado, ele pode permanecer listado até que a janela do navegador que ele contém seja fechada.
  • Se várias janelas do seu app estiverem abertas, um novo service worker só entrará em vigor depois que todas as janelas forem recarregadas e atualizadas para o service worker mais recente.
  • Cancelar o registro de um service worker não limpa o cache.
  • Se houver um service worker e um novo for registrado, o novo service worker não terá controle até que a página seja recarregada, a menos que você assuma o controle imediato.

Verificar alterações com o Lighthouse

Execute o Lighthouse novamente e verifique as alterações. Não se esqueça de desmarcar a caixa de seleção "Off-line" antes de verificar as alterações.

Auditoria de SEO

  • ✅ PASSED: o documento tem uma metadescrição.

Auditoria de Progressive Web App

  • ✅ PASSED:a página atual responde com 200 quando off-line.
  • ✅ PASSED: o start_url responde com 200 quando off-line.
  • ✅ PASSED: registra um service worker que controla a página e o start_url..
  • ✅ PASSED: o manifesto do app da Web atende aos requisitos para instalação.
  • ✅ PASSED: configurado para uma tela de apresentação personalizada.
  • ✅ PASSED:define uma cor de tema da barra de endereços.

Coloque o smartphone no modo avião e tente executar alguns dos seus apps favoritos. Em quase todos os casos, eles oferecem uma experiência off-line razoavelmente robusta. Os usuários esperam que a experiência nos apps seja robusta. E a Web não deve ser diferente. Os Progressive Web Apps precisam ser projetados com a tecnologia off-line como cenário principal.

Ciclo de vida do service worker

O ciclo de vida do service worker é a parte mais complicada. Se você não sabe o que está tentando fazer e quais são os benefícios, pode parecer que está lutando. Mas quando você sabe como ele funciona, pode oferecer atualizações simples e discretas aos usuários, misturando o melhor da Web e os padrões nativos.

install evento

O primeiro evento que um service worker recebe é install. Ele é acionado assim que o worker é executado e só é chamado uma vez por service worker. Se você alterar o script do service worker, o navegador o considerará um service worker diferente e ele receberá o próprio evento install.

Normalmente, o evento install é usado para armazenar em cache tudo o que você precisa para a execução do seu app.

activate evento

O service worker receberá um evento activate sempre que for iniciado. O objetivo principal do evento activate é configurar o comportamento do service worker, limpar todos os recursos deixados para trás em execuções anteriores (por exemplo, caches antigos) e preparar o service worker para processar solicitações de rede (por exemplo, o evento fetch descrito abaixo).

fetch evento

O evento de busca permite que o service worker intercepte quaisquer solicitações de rede e processe solicitações. Ele pode ir para a rede para receber o recurso, pode puxá-lo do cache próprio, gerar uma resposta personalizada ou qualquer número de opções diferentes. Consulte o Manual de receitas off-line para ver as diferentes estratégias disponíveis.

Como atualizar um service worker

O navegador verifica se há uma nova versão do seu service worker em cada carregamento de página. Se uma nova versão for encontrada, ela será transferida por download e instalada em segundo plano, mas não será ativada. A nova versão do service worker fica em estado de espera, até que não haja mais páginas abertas que usem o service worker antigo. Depois que todas as janelas com o service worker antigo forem fechadas, o novo service worker será ativado e poderá assumir o controle. Consulte a seção Como atualizar o service worker no documento "Ciclo de vida do service worker" para mais detalhes.

Como escolher a estratégia de armazenamento em cache certa

Escolher a estratégia de armazenamento em cache certa depende do tipo de recurso que você está tentando armazenar em cache e de como você precisará acessá-lo mais tarde. Para nosso aplicativo de previsão do tempo, dividiremos os recursos que precisamos para armazenar em cache em duas categorias: recursos que queremos pré-armazenar em cache e os dados que armazenaremos em cache no momento da execução.

Como armazenar recursos estáticos em cache

O pré-armazenamento em cache dos recursos é um conceito semelhante ao que acontece quando um usuário instala um app para computador ou dispositivo móvel. Os principais recursos necessários para que o app seja executado são instalados ou armazenados em cache no dispositivo, para que possam ser carregados posteriormente independentemente de haver ou não uma conexão de rede.

Para nosso app, pré-armazenaremos em cache todos os nossos recursos estáticos quando o service worker estiver instalado. Assim, tudo o que precisarmos para executar o app será armazenado no dispositivo do usuário. Para garantir que nosso app carregue rapidamente, vamos usar a estratégia que prioriza o cache. Em vez de acessar a rede para obter os recursos, eles são extraídos do cache local; apenas se não estiverem disponíveis, tentaremos obtê-los da rede.

Extrair do cache local elimina qualquer variabilidade de rede. Independentemente do tipo de rede em que o usuário está (Wi-Fi, 5G, 3G ou até mesmo 2G), os principais recursos necessários para execução estão disponíveis quase imediatamente.

Armazenar em cache os dados do app

A estratégia desatualizado e revalida é ideal para determinados tipos de dados e funciona bem no nosso app. Os dados são exibidos na tela o mais rápido possível e são atualizados quando a rede retorna os dados mais recentes. Isso significa que precisamos iniciar duas solicitações assíncronas, uma para o cache e outra para a rede.

Em circunstâncias normais, os dados armazenados em cache serão retornados quase imediatamente, fornecendo ao app dados recentes que podem ser usados. Depois, quando a solicitação de rede retornar, o aplicativo será atualizado usando os dados mais recentes da rede.

Para nosso app, isso proporciona uma experiência melhor do que a rede, recorrendo à estratégia de cache porque o usuário não precisa aguardar até que o pedido de rede expire para ver algo na tela. Inicialmente, eles podem ver dados mais antigos. No entanto, quando a solicitação de rede retorna, o aplicativo é atualizado com os dados mais recentes.

Atualizar lógica do app

Como mencionado anteriormente, o app precisa iniciar duas solicitações assíncronas, uma para o cache e outra para a rede. O app usa o objeto caches disponível em window para acessar o cache e recuperar os dados mais recentes. Esse é um excelente exemplo de aprimoramento progressivo, já que o objeto caches pode não estar disponível em todos os navegadores. Se ele não estiver, a solicitação de rede ainda funcionará.

Atualize a função getForecastFromCache() para verificar se o objeto caches está disponível no objeto window global e, se estiver, solicite os dados no cache.

public/scripts/app.js (em inglês)

// CODELAB: Add code to get weather forecast from the caches object.
if (!('caches' in window)) {
  return null;
}
const url = `${window.location.origin}/forecast/${coords}`;
return caches.match(url)
    .then((response) => {
      if (response) {
        return response.json();
      }
      return null;
    })
    .catch((err) => {
      console.error('Error getting data from cache', err);
      return null;
    });

Em seguida, precisamos modificar updateData() para fazer duas chamadas: uma para getForecastFromNetwork(), para receber a previsão da rede, e uma para getForecastFromCache(), para receber a previsão mais recente armazenada em cache:

public/scripts/app.js (em inglês)

// CODELAB: Add code to call getForecastFromCache.
getForecastFromCache(location.geo)
    .then((forecast) => {
      renderForecast(card, forecast);
    });

Nosso app de clima agora faz duas solicitações assíncronas de dados, uma do cache e outra por meio de uma fetch. Se houver dados no cache, eles serão retornados e renderizados de forma extremamente rápida (dezenas de milissegundos). Depois, quando o fetch responder, o card será atualizado com os dados mais recentes diretamente da API Weather.

Observe como a solicitação de cache e a solicitação fetch 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 é processado no seguinte código de renderForecast():

public/scripts/app.js (em inglês)

// If the data on the element is newer, skip the update.
if (lastUpdated >= data.currently.time) {
  return;
}

Toda vez que um card é atualizado, o app armazena o carimbo de data/hora dos dados em um atributo oculto. O app é restabelecido se o carimbo de data/hora do cartão for mais recente do que os dados transmitidos à função.

Pré-armazenar em cache os recursos do app

No service worker, vamos adicionar um DATA_CACHE_NAME para que possamos separar os dados do aplicativo do shell do app. Quando o shell do app é atualizado e os caches mais antigos são limpos, nossos dados permanecem intocados, prontos para um carregamento super-rápido. Lembre-se: se o formato dos seus dados mudar no futuro, você precisará encontrar uma maneira de lidar com isso e garantir que o shell do aplicativo e o conteúdo permaneçam sincronizados.

public/service-worker.js (link em inglês)

// CODELAB: Update cache names any time any of the cached files change.
const CACHE_NAME = 'static-cache-v2';
const DATA_CACHE_NAME = 'data-cache-v1';

Não se esqueça de atualizar a CACHE_NAME. Também mudaremos todos os nossos recursos estáticos.

Para que nosso app funcione off-line, precisamos pré-armazenar em cache todos os recursos necessários. Isso também ajudará no nosso desempenho. Em vez de ter que obter todos os recursos da rede, o app será capaz de carregar todos eles do cache local, eliminando qualquer instabilidade da rede.

Atualize a matriz FILES_TO_CACHE com a lista de arquivos:

public/service-worker.js (link em inglês)

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/',
  '/index.html',
  '/scripts/app.js',
  '/scripts/install.js',
  '/scripts/luxon-1.11.4.js',
  '/styles/inline.css',
  '/images/add.svg',
  '/images/clear-day.svg',
  '/images/clear-night.svg',
  '/images/cloudy.svg',
  '/images/fog.svg',
  '/images/hail.svg',
  '/images/install.svg',
  '/images/partly-cloudy-day.svg',
  '/images/partly-cloudy-night.svg',
  '/images/rain.svg',
  '/images/refresh.svg',
  '/images/sleet.svg',
  '/images/snow.svg',
  '/images/thunderstorm.svg',
  '/images/tornado.svg',
  '/images/wind.svg',
];

Como estamos gerando manualmente a lista de arquivos para armazenar em cache, sempre que atualizarmos um arquivo, precisamos atualizar a CACHE_NAME. Foi possível remover o offline.html da nossa lista de arquivos em cache porque nosso app agora tem todos os recursos necessários para trabalhar off-line e não exibirá a página off-line novamente.

Atualizar o manipulador de eventos de ativação

Para garantir que nosso evento activate não exclua acidentalmente nossos dados, no evento activate do service-worker.js, substitua if (key !== CACHE_NAME) { por:

public/service-worker.js (link em inglês)

if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {

Atualizar o manipulador de eventos de busca

Precisamos modificar o service worker para interceptar solicitações para a API Weather e armazenar as respostas no cache para facilitar o acesso. Na estratégia de inatividade enquanto revalida, esperamos que a resposta da rede seja a "fonte da verdade", sempre nos fornecendo as informações mais recentes. Se a rede não puder, não há problema, porque já recuperamos os dados de cache mais recentes no nosso app.

Atualize o manipulador de eventos fetch para processar as solicitações à API de dados separadamente de outras solicitações.

public/service-worker.js (link em inglês)

// CODELAB: Add fetch event handler here.
if (evt.request.url.includes('/forecast/')) {
  console.log('[Service Worker] Fetch (data)', evt.request.url);
  evt.respondWith(
      caches.open(DATA_CACHE_NAME).then((cache) => {
        return fetch(evt.request)
            .then((response) => {
              // If the response was good, clone it and store it in the cache.
              if (response.status === 200) {
                cache.put(evt.request.url, response.clone());
              }
              return response;
            }).catch((err) => {
              // Network request failed, try to get it from the cache.
              return cache.match(evt.request);
            });
      }));
  return;
}
evt.respondWith(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.match(evt.request)
          .then((response) => {
            return response || fetch(evt.request);
          });
    })
);

O código intercepta a solicitação e verifica se é uma previsão do tempo. Se estiver, use fetch para fazer a solicitação. Quando a resposta for retornada, abra o cache, clone a resposta, armazene-a no cache e retorne a resposta ao solicitante original.

Precisamos remover a verificação de evt.request.mode !== 'navigate' porque queremos que nosso service worker gerencie todas as solicitações (incluindo imagens, scripts, arquivos CSS etc.), não apenas as navegações. Se deixarmos essa verificação, somente o HTML seria veiculado do cache do service worker. Todo o restante seria solicitado da rede.

Fazer um teste

Agora, o app funcionará completamente off-line. Atualize a página para garantir que você tem o service worker mais recente instalado. Em seguida, salve algumas cidades e pressione o botão de atualização no app para ver dados meteorológicos recentes.

Em seguida, acesse o painel Cache Storage no painel Application do DevTools. Expanda a seção e você verá o nome do cache estático e do cache de dados listado no lado esquerdo. A abertura do cache de dados deve mostrar os dados armazenados para cada cidade.

Alterne para o painel Service Workers e marque a caixa de seleção Off-line. Tente atualizar a página, fique off-line e atualize a página.

Se você estiver em uma rede rápida e quiser ver como os dados de previsão do tempo são atualizados em uma conexão lenta, defina a propriedade FORECAST_DELAY em server.js como 5000. Todas as solicitações à API de previsão serão atrasadas em 5.000 ms.

Verificar alterações com o Lighthouse

Também é uma boa ideia executar o Lighthouse novamente.

Auditoria de SEO

  • ✅ PASSED:o documento tem uma metadescrição.

Auditoria de Progressive Web App

  • ✅ PASSED: a página atual responde com 200 quando off-line.
  • ✅ PASSED: o start_url responde com 200 quando off-line.
  • ✅ PASSED: registra um service worker que controla a página e o start_url..
  • ✅ PASSED: o manifesto do app da Web atende aos requisitos para instalação.
  • ✅ PASSED:configurado para uma tela de apresentação personalizada.
  • ✅ PASSED:define uma cor de tema da barra de endereços.

Quando um Progressive Web App está instalado, ele se parece com todos os outros apps instalados e se comporta como ele. Ele é iniciado no mesmo lugar que outros apps. Ele é executado em um app sem uma barra de endereço ou outra IU do navegador. E, como todos os outros apps instalados, ele é um app de nível superior no seletor de tarefas.

No Chrome, um Progressive Web App pode ser instalado pelo menu de contexto de três pontos. Também é possível fornecer um botão ou outro componente de IU que solicite a instalação do app.

Auditoria com o Lighthouse

Para que um usuário possa instalar seu Progressive Web App, ele precisa atender a determinados critérios. A maneira mais fácil de verificar isso é usar o Lighthouse e verificar se ele atende aos critérios instaláveis.

Se você já concluiu este codelab, seu PWA já precisa atender a esses critérios.

Adicionar o install.js ao index.html

Primeiro, vamos adicionar o install.js ao nosso arquivo index.html.

public/index.html (link em inglês)

<!-- CODELAB: Add the install script here -->
<script src="/scripts/install.js"></script>

Ouça o evento beforeinstallprompt

Se os critérios de adição à tela inicial forem atendidos, o Chrome disparará um evento beforeinstallprompt que pode ser usado para indicar que o app pode ser 'instalado' e solicitará que o usuário o instale. Adicione o código abaixo para detectar o evento beforeinstallprompt:

public/scripts/install.js (em inglês)

// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);

Salvar evento e mostrar o botão de instalação

Na função saveBeforeInstallPromptEvent, salvaremos uma referência ao evento beforeinstallprompt para que possamos chamar o método prompt() mais tarde e atualizar a IU para exibir o botão de instalação.

public/scripts/install.js (em inglês)

// CODELAB: Add code to save event & show the install button.
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');

Mostrar a solicitação e ocultar o botão

Quando o usuário clica no botão de instalação, é necessário chamar .prompt() no evento salvo de beforeinstallprompt. Também precisamos ocultar o botão de instalação, porque o .prompt() só pode ser chamado uma vez em cada evento salvo.

public/scripts/install.js (em inglês)

// CODELAB: Add code show install prompt & hide the install button.
deferredInstallPrompt.prompt();
// Hide the install button, it can't be called twice.
evt.srcElement.setAttribute('hidden', true);

Chamar .prompt() mostrará uma caixa de diálogo modal para o usuário, pedindo para ele adicionar seu app à tela inicial dele.

Registre os resultados.

Você pode verificar se o usuário respondeu à caixa de diálogo de instalação ouvindo a promessa retornada pela propriedade userChoice do evento beforeinstallprompt salvo. A promessa retorna um objeto com uma propriedade outcome depois que a solicitação é exibida e o usuário responde a ela.

public/scripts/install.js (em inglês)

// CODELAB: Log user response to prompt.
deferredInstallPrompt.userChoice
    .then((choice) => {
      if (choice.outcome === 'accepted') {
        console.log('User accepted the A2HS prompt', choice);
      } else {
        console.log('User dismissed the A2HS prompt', choice);
      }
      deferredInstallPrompt = null;
    });

Um comentário sobre userChoice: a especificação o define como uma propriedade, não uma função que você poderia esperar.

Registrar todos os eventos de instalação

Além de qualquer IU que você adicione para instalar o app, os usuários também poderão instalar o PWA usando outros métodos, como o menu de três pontos do Chrome. Para acompanhar esses eventos, ouça o evento appinstalled.

public/scripts/install.js (em inglês)

// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);

Em seguida, vamos atualizar a função logAppInstalled. Neste codelab, usaremos console.log, mas é provável que você queira registrar isso como um evento com seu software de análise em um app de produção.

public/scripts/install.js (em inglês)

// CODELAB: Add code to log the event
console.log('Weather App was installed.', evt);

Atualizar o service worker

Não se esqueça de atualizar o arquivo CACHE_NAME no arquivo service-worker.js, já que você fez alterações nos arquivos que já estão armazenados em cache. Ativar a caixa de seleção Ignore for network no painel Service Workers do painel Application no DevTools funcionará em desenvolvimento, mas não ajudará em situações reais.

Fazer um teste

Vejamos como foi nossa etapa de instalação. Por segurança, use o botão Clear site data no painel Application do DevTools para limpar tudo e garantir que estejamos começando do zero. Se você já instalou o app, desinstale-o. Caso contrário, o ícone de instalação não será exibido novamente.

Verifique se o botão de instalação está visível

Primeiro, vamos verificar se o ícone de instalação aparece corretamente. Faça isso em computadores e dispositivos móveis.

  1. Abra o URL em uma nova guia do Chrome.
  2. Abra o menu de três pontos do Chrome (ao lado da barra de endereço).
    ▢ Verifique se você verá a opção "Instalar clima..." no menu.
  3. Para atualizar os dados meteorológicos, use o botão "Atualizar" no canto superior direito e confira a heurística de engajamento do usuário.
    ▢ Verificar se o ícone de instalação está visível no cabeçalho do app.

Verificar se o botão de instalação funciona

Em seguida, vamos garantir que tudo seja instalado corretamente e que os eventos sejam disparados corretamente. Você pode fazer isso em um computador ou dispositivo móvel. Se você quiser testar esse recurso em dispositivos móveis, verifique se está usando a depuração remota para ver o que está registrado no console.

  1. Abra o Chrome e navegue até o PWA Weather em uma nova guia do navegador.
  2. Abra o DevTools e acesse o painel Console.
  3. Clique no botão "Instalar" no canto superior direito.
    ▢ Verifique se o botão de instalação desaparece
    ▢ Verifique se a caixa de diálogo de instalação modal é exibida.
  4. Clique em “Cancel”.
    ▢ Confirme que o usuário concluiu a solicitação.O usuário dispensou a solicitação do A2HS" é mostrado no resultado do console.
    ▢ Verifique se o botão de instalação reaparece.
  5. Clique no botão "Instalar" novamente e depois no botão "Instalar" na caixa de diálogo modal.
    Verificar se o usuário aceitou a solicitação de A2HS" é exibido na saída do console.
    ▢ Verifique se o aplicativo Weather foi instalado" é mostrado na saída do console.
    ▢ Verifique se o aplicativo de previsão do tempo está adicionado ao lugar onde você costuma encontrar apps.
  6. Inicie o PWA Weather.
    ▢ Verifique se o app é aberto como um app independente, em uma janela do app em um computador ou em tela cheia em um dispositivo móvel.

.

Verificar se a instalação para iOS funciona corretamente

Vamos ver também o comportamento no iOS. Se você tiver um dispositivo iOS, poderá usá-lo ou, se estiver usando um Mac, experimente o simulador de iOS disponível com o Xcode.

  1. Abra o Safari e, em uma nova guia do navegador, acesse o Weather PWA.
  2. Clique no botão Compartilhar .
  3. Role para a direita e clique no botão Adicionar à tela inicial.
    ▢ Verifique se o título, o URL e o ícone estão corretos.
  4. Clique em Adicionar
    ▢ Verifique se o ícone do aplicativo foi adicionado à tela inicial.
  5. Inicie o PWA Clima na tela inicial.
    ▢ Verifique se o app inicia em tela cheia.

Bônus: como detectar se o app foi iniciado na tela inicial

A consulta de mídia display-mode possibilita aplicar estilos, dependendo de como o app foi iniciado, ou determinar como ele foi iniciado com JavaScript.

@media all and (display-mode: standalone) {
  body {
    background-color: yellow;
  }
}

Também é possível verificar a consulta de mídia display-mode no JavaScript para ver se você está executando no modo autônomo.

Bônus: desinstalando seu PWA

Lembre-se de que a beforeinstallevent não é disparada se o app já está instalado. Portanto, durante o desenvolvimento, é recomendável instalar e desinstalar várias vezes para garantir que tudo esteja funcionando conforme o esperado.

Android

No Android, os PWAs são desinstalados da mesma forma que outros apps instalados.

  1. Abra a gaveta de apps.
  2. Role a tela para baixo até encontrar o ícone de Clima.
  3. Arraste o ícone do app para a parte superior da tela.
  4. Selecione Desinstalar.

ChromeOS

No ChromeOS, os PWAs são desinstalados facilmente da caixa de pesquisa.

  1. Abra a tela de início.
  2. Digite "Clima" na caixa de pesquisa. Seu PWA Clima aparecerá nos resultados.
  3. Clique com o botão direito do mouse (clicar) no PWA Weather.
  4. Clique em Remover do Chrome...

macOS e Windows

No Mac e Windows, os PWAs podem ser desinstalados pelo Chrome:

  1. Em uma nova guia do navegador, abra chrome://apps.
  2. Clique com o botão direito do mouse (clicar) no PWA Weather.
  3. Clique em Remover do Chrome...

Também é possível abrir o PWA instalado, clicar no menu de contexto de três pontos no canto superior direito e escolher "Desinstalar o PWA Clima..."

Parabéns! Você criou seu primeiro Progressive Web App.

Você adicionou um manifesto de app da Web para permitir a instalação e um service worker para garantir que o PWA seja sempre rápido e confiável. Você aprendeu a usar o DevTools para auditar um app e a melhorar a experiência do usuário.

Agora você sabe as principais etapas necessárias para transformar qualquer aplicativo da Web em Progressive Web App.

Qual é a próxima etapa?

Confira alguns dos seguintes codelabs:

Leia mais

Documentos de referência