Desenvolvimento de Progressive Web Apps 02.0: início rápido off-line

Este codelab faz parte do curso de treinamento "Desenvolvimento de Apps Web Progressivos", desenvolvido pela equipe de treinamento do Google Developers. Você vai aproveitar mais este curso se fizer os codelabs em sequência.

Para detalhes completos sobre o curso, consulte a visão geral sobre o desenvolvimento de Progressive Web Apps.

Introdução

Neste laboratório, você vai usar o Lighthouse para auditar um site de acordo com os padrões de Progressive Web App (PWA). Você também vai adicionar funcionalidade off-line com a API Service Worker.

O que você vai aprender

  • Como auditar sites com o Lighthouse
  • Como adicionar recursos off-line a um aplicativo

O que você precisa saber

  • HTML, CSS e JavaScript básicos
  • Familiaridade com Promises do ES2015

O que é necessário

  • Computador com acesso ao terminal/shell
  • Conexão com a Internet
  • Navegador Chrome (para usar o Lighthouse)
  • Um editor de texto
  • Opcional: Chrome em um dispositivo Android

Faça o download ou clone o repositório pwa-training-labs do GitHub e instale a versão LTS do Node.js, se necessário.

Navegue até o diretório offline-quickstart-lab/app/ e inicie um servidor de desenvolvimento local:

cd offline-quickstart-lab/app
npm install
node server.js

Você pode encerrar o servidor a qualquer momento com Ctrl-c.

Abra o navegador e acesse localhost:8081/. Você vai notar que o site é uma página da Web simples e estática.

Observação:cancele o registro de todos os service workers e limpe todos os caches deles para localhost para que não interfiram no laboratório. No Chrome DevTools, clique em Limpar dados do site na seção Limpar armazenamento da guia Aplicativo.

Abra a pasta offline-quickstart-lab/app/ no editor de texto de sua preferência. A pasta app/ é onde você vai criar o laboratório.

Essa pasta contém:

  • A pasta images/ contém imagens de exemplo
  • styles/main.css é a principal folha de estilo
  • index.html é a página HTML principal do nosso site de exemplo.
  • package-lock.json e package.json rastreiam as dependências do app. Nesse caso, as únicas dependências são para o servidor de desenvolvimento local.
  • server.js é um servidor de desenvolvimento local para testes.
  • service-worker.js é o arquivo do service worker (atualmente vazio).

Antes de começar a fazer mudanças no site, vamos fazer uma auditoria com o Lighthouse para ver o que pode ser melhorado.

Volte ao app (no Chrome) e abra a guia Auditorias das Ferramentas para desenvolvedores. O ícone do Lighthouse e as opções de configuração vão aparecer. Selecione "Mobile" em Dispositivo, todas as Auditorias, uma das opções de Limitação e Limpar armazenamento:

Clique em Executar auditorias. As auditorias levam alguns instantes para serem concluídas.

Explicação

Quando a auditoria for concluída, um relatório com pontuações vai aparecer nas Ferramentas para desenvolvedores. Ele vai mostrar pontuações, algo assim (as pontuações podem não ser exatamente as mesmas):

Observação:as pontuações do Lighthouse são uma aproximação e podem ser influenciadas pelo seu ambiente. Por exemplo, se você tiver muitas janelas do navegador abertas. Suas pontuações podem não ser exatamente iguais às mostradas aqui.

A seção App Web Progressivo vai ficar assim:

O relatório tem pontuações e métricas em cinco categorias:

  • App Web Progressivo
  • Desempenho
  • Acessibilidade
  • Práticas recomendadas
  • SEO

Como você pode ver, nosso app tem uma pontuação baixa na categoria Progressive Web App (PWA). Vamos melhorar nossa pontuação!

Confira a seção de PWAs do relatório e veja o que está faltando.

Registrar um service worker

Uma das falhas listadas no relatório é que nenhum service worker está registrado. No momento, temos um arquivo de service worker vazio em app/service-worker.js.

Adicione o script a seguir à parte de baixo de index.html, antes da tag de fechamento </body>:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('service-worker.js')
      .then(reg => {
        console.log('Service worker registered! 😎', reg);
      })
      .catch(err => {
        console.log('😥 Service worker registration failed: ', err);
      });
  });
}
</script>

Explicação

Esse código registra o arquivo vazio do service worker service-worker.js assim que a página é carregada. No entanto, o arquivo de service worker atual está vazio e não fará nada. Vamos adicionar o código de serviço na próxima etapa.

Pré-cache de recursos

Outra falha listada no relatório é que o app não responde com um código de status 200 quando está off-line. Precisamos atualizar nosso service worker para resolver isso.

Adicione o seguinte código ao arquivo do service worker (service-worker.js):

const cacheName = 'cache-v1';
const precacheResources = [
  '/',
  'index.html',
  'styles/main.css',
  'images/space1.jpg',
  'images/space2.jpg',
  'images/space3.jpg'
];

self.addEventListener('install', event => {
  console.log('Service worker install event!');
  event.waitUntil(
    caches.open(cacheName)
      .then(cache => {
        return cache.addAll(precacheResources);
      })
  );
});

self.addEventListener('activate', event => {
  console.log('Service worker activate event!');
});

self.addEventListener('fetch', event => {
  console.log('Fetch intercepted for:', event.request.url);
  event.respondWith(caches.match(event.request)
    .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request);
      })
    );
});

Agora, volte para o navegador e atualize o site. Verifique no console se o service worker:

  • registrados
  • instalado
  • Ativado

Observação:se você já registrou o service worker antes ou está com problemas para acionar todos os eventos, cancele o registro de todos os service workers e atualize a página. Se isso não funcionar, feche todas as instâncias do app e abra-o novamente.

Em seguida, encerre o servidor de desenvolvimento local na linha de comando executando Ctrl + c. Atualize o site novamente e observe que ele é carregado mesmo com o servidor off-line.

Observação:talvez você veja um erro no console indicando que não foi possível buscar o service worker: An unknown error occurred when fetching the script. service-worker.js Failed to load resource: net::ERR_CONNECTION_REFUSED. Esse erro é mostrado porque o navegador não conseguiu buscar o script do service worker (porque o site está off-line), mas isso é esperado porque não podemos usar o service worker para armazenar em cache a si mesmo. Caso contrário, o navegador do usuário ficaria preso ao mesmo service worker para sempre.

Explicação

Depois que o service worker é registrado pelo script de registro em index.html, o evento install do service worker ocorre. Durante esse evento, o listener de eventos install abre um cache nomeado e armazena em cache os arquivos especificados com o método cache.addAll. Isso é chamado de "pré-cache" porque acontece durante o evento install, que geralmente é a primeira vez que um usuário visita seu site.

Depois que um service worker é instalado e se outro não está controlando a página, o novo service worker é "ativado" (o listener de eventos activate é acionado no service worker), e ele começa a controlar a página.

Quando os recursos são solicitados por uma página controlada por um service worker ativado, as solicitações passam pelo service worker, como um proxy de rede. Um evento fetch é acionado para cada solicitação. No service worker, o listener de eventos fetch pesquisa os caches e responde com o recurso armazenado em cache, se ele estiver disponível. Se o recurso não estiver armazenado em cache, ele será solicitado normalmente.

O armazenamento em cache permite que o app funcione off-line, evitando solicitações de rede. Agora nosso app pode responder com um código de status 200 quando off-line.

Observação:o evento de ativação não é usado para nada além do registro neste exemplo. O evento foi incluído para ajudar a depurar problemas de ciclo de vida do service worker.

Opcional: você também pode ver os recursos em cache na guia Application das Ferramentas para desenvolvedores ao abrir a seção Cache Storage:

Reinicie o servidor de desenvolvimento com node server.js e atualize o site. Em seguida, abra a guia Auditorias nas Ferramentas para desenvolvedores novamente e execute a auditoria do Lighthouse de novo selecionando Nova auditoria (o sinal de mais no canto superior esquerdo). Quando a auditoria terminar, você vai notar que nossa pontuação de PWA melhorou bastante, mas ainda pode ser aprimorada. Vamos continuar melhorando nossa pontuação na seção a seguir.

Observação:esta seção é opcional porque testar o banner de instalação de apps da Web está além do escopo do laboratório. Você pode testar por conta própria usando a depuração remota.

Nossa pontuação de PWA ainda não é ótima. Alguns dos erros restantes listados no relatório são que o usuário não será solicitado a instalar nosso web app e que não configuramos uma tela de abertura ou cores da marca na barra de endereço. Podemos corrigir esses problemas e implementar progressivamente o Adicionar à tela inicial atendendo a alguns critérios adicionais. E o mais importante: precisamos criar um arquivo de manifesto.

Criar um arquivo de manifesto

Crie um arquivo em app/ chamado manifest.json e adicione o seguinte código:

{
  "name": "Space Missions",
  "short_name": "Space Missions",
  "lang": "en-US",
  "start_url": "/index.html",
  "display": "standalone",
  "theme_color": "#FF9800",
  "background_color": "#FF9800",
  "icons": [
    {
      "src": "images/touch/icon-128x128.png",
      "sizes": "128x128"
    },
    {
      "src": "images/touch/icon-192x192.png",
      "sizes": "192x192"
    },
    {
      "src": "images/touch/icon-256x256.png",
      "sizes": "256x256"
    },
    {
      "src": "images/touch/icon-384x384.png",
      "sizes": "384x384"
    },
    {
      "src": "images/touch/icon-512x512.png",
      "sizes": "512x512"
    }
  ]
}

As imagens referenciadas no manifesto já estão no app.

Em seguida, adicione o seguinte HTML à parte de baixo da tag <head> em index.html:

<link rel="manifest" href="manifest.json">

<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Space Missions">
<meta name="apple-mobile-web-app-title" content="Space Missions">
<meta name="theme-color" content="#FF9800">
<meta name="msapplication-navbutton-color" content="#FF9800">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">

Volte para o site. Na guia Application das Ferramentas para desenvolvedores, selecione a seção Clear storage e clique em Clear site data. Depois, atualize a página. Agora selecione a seção Manifesto. Os ícones e as opções de configuração definidos no arquivo manifest.json vão aparecer. Se as mudanças não aparecerem, abra o site em uma janela anônima e verifique novamente.

Explicação

O arquivo manifest.json informa ao navegador como estilizar e formatar alguns dos aspectos progressivos do seu app, como o cromo do navegador, o ícone da tela inicial e a tela de abertura. Ele também pode ser usado para configurar seu web app para abrir no modo standalone, como um app nativo (ou seja, fora do navegador).

O suporte ainda está em desenvolvimento para alguns navegadores no momento da redação deste artigo, e as tags <meta> configuram um subconjunto desses recursos para determinados navegadores que ainda não têm suporte total.

Precisamos limpar os dados do site para remover a versão em cache antiga de index.html, já que ela não tinha o link do manifesto. Execute outra auditoria do Lighthouse e veja quanto a pontuação do PWA melhorou.

Ativar a solicitação de instalação

A próxima etapa para instalar nosso app é mostrar aos usuários o comando de instalação. O Chrome 67 solicitava automaticamente aos usuários, mas a partir do Chrome 68, o pedido de instalação precisa ser ativado programaticamente em resposta a um gesto do usuário.

Adicione um botão e um banner "Instalar app" na parte de cima de index.html (logo após a tag <main>) com o seguinte código:

<section id="installBanner" class="banner">
    <button id="installBtn">Install app</button>
</section>

Em seguida, adicione os seguintes estilos a styles/main.css para estilizar o banner:

.banner {
  align-content: center;
  display: none;
  justify-content: center;
  width: 100%;
}

Salve o arquivo. Por fim, adicione a seguinte tag de script a index.html:

  <script>
    let deferredPrompt;
    window.addEventListener('beforeinstallprompt', event => {

      // Prevent Chrome 67 and earlier from automatically showing the prompt
      event.preventDefault();

      // Stash the event so it can be triggered later.
      deferredPrompt = event;

      // Attach the install prompt to a user gesture
      document.querySelector('#installBtn').addEventListener('click', event => {

        // Show the prompt
        deferredPrompt.prompt();

        // Wait for the user to respond to the prompt
        deferredPrompt.userChoice
          .then((choiceResult) => {
            if (choiceResult.outcome === 'accepted') {
              console.log('User accepted the A2HS prompt');
            } else {
              console.log('User dismissed the A2HS prompt');
            }
            deferredPrompt = null;
          });
      });

      // Update UI notify the user they can add to home screen
      document.querySelector('#installBanner').style.display = 'flex';
    });
  </script>

Salve o arquivo. Abra o app no Chrome em um dispositivo Android usando a depuração remota. Quando a página carregar, você vai ver o botão "Instalar app". Ele não aparece em um computador, então faça o teste em um dispositivo móvel. Clique no botão para que a solicitação "Adicionar à tela inicial" apareça. Siga as etapas para instalar o app no dispositivo. Depois da instalação, você poderá abrir o web app no modo independente (fora do navegador) tocando no ícone da tela inicial recém-criado.

Explicação

O código HTML e CSS adiciona um banner e um botão ocultos que podem ser usados para permitir que os usuários ativem a solicitação de instalação.

Quando o evento beforeinstallprompt é acionado, impedimos a experiência padrão (em que o Chrome 67 e versões anteriores pedem automaticamente que os usuários instalem) e capturamos o beforeinstallevent na variável global deferredPrompt. O botão "Instalar app" é configurado para mostrar a solicitação com o método prompt() do beforeinstallevent. Depois que o usuário faz uma escolha (instalar ou não), a promessa userChoice é resolvida com a opção do usuário (outcome). Por fim, mostramos o botão de instalação quando tudo estiver pronto.

Você aprendeu a auditar sites com o Lighthouse e a implementar os conceitos básicos da funcionalidade off-line. Se você concluiu as seções opcionais, também aprendeu a instalar apps da Web na tela inicial.

Mais recursos

O Lighthouse é de código aberto. Você pode fazer um fork, adicionar seus próprios testes e registrar bugs. O Lighthouse também está disponível como uma ferramenta de linha de comando para integração com processos de build.

Para conferir todos os codelabs do curso de treinamento de PWA, consulte o codelab de boas-vindas do curso.