Guia de início rápido off-line

Este codelab faz parte do curso Developing Progressive Web Apps, desenvolvido pela equipe de treinamento do Google Developers. Você aproveitará mais o curso se fizer os codelabs em sequência.

Para ver todos os detalhes sobre o curso, consulte a Visão geral do desenvolvimento de Progressive Web Apps.

Introdução

Neste laboratório, você usará o Lighthouse para fazer auditoria de um site com base nos padrões Progressive Web App (PWA). Você também adicionará a funcionalidade off-line com a API do 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 promessas 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 usando o Ctrl-c.

Abra seu navegador e acesse localhost:8081/. Você verá 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 de service worker do localhost para que não interfiram no laboratório. No Chrome DevTools, você pode fazer isso clicando 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ê criará o laboratório.

Essa pasta contém:

  • images/ pasta contém imagens de amostra
  • styles/main.css é a folha de estilo principal
  • index.html é a página HTML principal do nosso site de exemplo
  • package-lock.json e package.json rastreiam dependências de apps. As únicas dependências neste caso 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çarmos a fazer alterações no site, vamos fazer uma auditoria com o Lighthouse para ver como isso pode ser melhorado.

Volte ao app (no Chrome) e abra a guia Auditorias das Ferramentas para Desenvolvedores. Você verá o ícone do Lighthouse e as opções de configuração. Selecione "Dispositivos móveis" em Dispositivo, selecione todas as Auditorias, escolha uma das opções de Limitação e escolha Limpar armazenamento:

Clique em Fazer auditorias. As auditorias levam alguns minutos para serem concluídas.

Explicação

Você verá um relatório com as pontuações nas Ferramentas para Desenvolvedores quando a auditoria for concluída. Ele deve 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 as mesmas mostradas aqui.

E a seção Progressive Web App vai ficar assim:

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

  • Progressive Web App
  • 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!

Reserve um momento para analisar a seção de PWA do relatório e ver 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 do service worker vazio em app/service-worker.js.

Adicione o script a seguir à parte inferior do 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

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

Pré-armazenar recursos em cache

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

Adicione o seguinte código ao arquivo do service worker (sw.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);
      })
    );
});

Volte ao navegador e atualize o site. Verifique o console para ver se o service worker:

  • registrados
  • instalado
  • Ativado

Observação: caso você já tenha registrado o service worker ou tenha problemas para disparar todos os eventos, cancele a inscrição deles e atualize a página. Se isso falhar, feche todas as instâncias do aplicativo e abra-o novamente.

Depois, encerre o servidor de desenvolvimento local na linha de comando executando Ctrl + c. Atualize o site novamente e observe que ele carrega mesmo que o servidor esteja 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. Este erro é exibido porque o navegador não conseguiu buscar o script do service worker (porque o site está off-line), mas isso era esperado porque não conseguimos usar o service worker para ser armazenado em cache. Caso contrário, o navegador do usuário ficará preso com o mesmo service worker para sempre!

Explicação

Depois que o service worker for registrado pelo script de registro em index.html, o evento install vai ocorrer. 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 "precaching" porque ocorre durante o evento install, que geralmente é a primeira vez que um usuário visita seu site.

Depois que um service worker for instalado e se outro service worker não estiver controlando a página, o novo service worker será "activated" (o listener de eventos activate será acionado no service worker) e começará a controlar a página.

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

O armazenamento em cache de recursos 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, além da geração de registros neste exemplo. O evento foi incluído para ajudar a depurar problemas no ciclo de vida dos service workers.

Opcional: você também pode ver os recursos armazenados em cache na guia Aplicativo das Ferramentas para desenvolvedores expandindo a seção Armazenamento de cache:

Reinicie o servidor de desenvolvimento com node server.js e atualize o site. Em seguida, abra a guia Auditorias em "Ferramentas do desenvolvedor" e execute novamente a auditoria do Lighthouse selecionando Nova auditoria (o sinal de adição no canto superior esquerdo). Quando a auditoria terminar, você verá que nossa pontuação de PWA é significativamente melhor, mas ainda pode ser melhorada. Vamos continuar melhorando nossa pontuação na seção a seguir.

Observação: esta seção é opcional porque o teste do banner de instalação de apps da Web está além do escopo do laboratório. É possível fazer o teste usando a depuração remota.

Nossa pontuação de PWA ainda não é muito boa. Algumas das falhas restantes listadas no relatório são que o usuário não recebe a solicitação para instalar nosso app da Web e não configuramos uma tela de apresentação ou cores de marca na barra de endereço. Para resolver esses problemas e implementar progressivamente a opção Adicionar à tela inicial, atenda a alguns critérios adicionais. O mais importante é criar um arquivo de manifesto.

Criar um arquivo de manifesto

Crie um arquivo em app/ com o nome 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á são fornecidas no app.

Em seguida, adicione o seguinte HTML à parte inferior 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 ao site. Na guia Aplicativo das Ferramentas para desenvolvedores, selecione a seção Limpar armazenamento e clique em Limpar dados do site. Depois atualize a página. Selecione a seção Manifest. Você verá os ícones e as opções de configuração definidos no arquivo manifest.json. Se as alterações não forem exibidas, abra o site em uma janela anônima e verifique novamente.

Explicação

O arquivo manifest.json informa ao navegador como definir o estilo e a formatação de alguns aspectos progressivos do seu app, como o Chrome, o ícone da tela inicial e a tela de apresentação. Ela também pode ser usada para configurar seu app da Web para ser aberto no modo standalone, como um app nativo (ou seja, fora do navegador).

No momento da criação deste recurso, ainda há suporte para alguns navegadores, e as tags <meta> configuram um subconjunto desses recursos para determinados navegadores que ainda não têm compatibilidade total.

Tivemos que Limpar dados do site para remover a antiga versão armazenada em cache de index.html, já que essa versão não tinha o link do manifesto. Tente executar outra auditoria do Lighthouse e veja se 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 a solicitação de instalação. O Chrome 67 solicitava os usuários automaticamente, mas, a partir do Chrome 68, a solicitação de instalação precisa ser ativada programaticamente em resposta a um gesto do usuário.

Adicione um botão e um banner da opção "Instalar app" na parte superior do 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 um dos seguintes estilos a styles/main.css:

.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ê verá o botão "Instalar aplicativo", que não será exibido em um computador. Portanto, teste-o em dispositivos móveis. Clique no botão para que o prompt "Adicionar à tela inicial" apareça. Siga as etapas para instalar o app no seu dispositivo. Após a instalação, você poderá abrir o app da Web no modo independente (fora do navegador) tocando no ícone de tela inicial recém-criado.

Explicação

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

Depois que o evento beforeinstallprompt é disparado, bloqueamos a experiência padrão (em que o Chrome 67 e versões anteriores solicitam que os usuários o instalem) e capturamos o beforeinstallevent na variável deferredPrompt global. O botão "Instalar app" é configurado para mostrar a solicitação com o método prompt() do beforeinstallevent. Depois que o usuário fizer uma escolha (para instalar ou não), a promessa do userChoice será resolvida com a escolha do usuário (outcome). Por fim, exibiremos o botão de instalação assim que 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 tem código aberto. É possível bifurcar, adicionar seus próprios testes e reportar bugs. O Lighthouse também está disponível como uma ferramenta de linha de comando para integração com processos de build.

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