GefyMarcos

Web app to PWA!

O que é PWA?

PWA não é um novo framework javascript ou uma tecnologia nova, basicamente ele pode ser definido como:

Progressive Web Apps são experiências que combinam o melhor da Web e o melhor dos aplicativos.

Mas para que sua aplicação web tradicionais ganhe os “super-poderes” necessários para se tornar uma PWA ela precisa reunir os seguintes atributos:

  • Progressivo: Deve funcionar em qualquer navegador.
  • Responsivo: Desktop, celular, tablet…
  • Independente de conectividade: ~service workers~ para funcionar off-line ou com redes de baixa qualidade.
  • Semelhante a aplicativos: Não tem barra de url e possui interações e navegação no estilo de aplicativos, pois é compilado no modelo de shell de aplicativo.
  • Atual: Sempre atualizado devido ao service worker.
  • Seguro: Fornecido via HTTPS.
  • Descobrível: Pode ser identificado como “aplicativo” graças ao manifesto W3C e ao escopo de registro do service worker, que permitem que os mecanismos de pesquisa os encontrem.
  • Reenvolvente: Facilita o reengajamento com recursos como push notification.
  • Instalável: Permite que os usuários “guardem” os aplicativos mais úteis em suas iniciais sem precisar acessar uma loja de aplicativos.
  • Linkável: Compartilhe facilmente por URL, não requer instalação complexa.

Tais é louco, tudo isso? Já cansei antes de começar. Mas fique tranquilo, vou te mostrar como ele parece bem mais complexo do que realmente é.

Checklist

A google fornece uma checklist para te guiar nesse caminho e também o excelente lighthouse, que serve para, entre outras coisas, auditar nossa app e gerar métricas mostrando quais principios já cobrimos.

1 - HTTPS, sempre HTTPS

HTTPS é uma implementação do protrocolo HTTP que utiliza o protocolo SSL/TLS. Dessa forma é possível trafegar dados de forma criptografada e segura, através de certificados digitais no browser.

O que fazer?

  • Habilitar o SSL no seu domínio, isso é obrigatório.
  • Redirecionar o status 301 também para HTTPS.

Como fazer?

2 - Site responsivo e rápido

Responsividade já é fundamental há algum tempo e também é um dos pricipios base para as PWAs.

O que fazer?

  • Sempre definir corretamente a viewport no browser.
  • Utilizar @MediaQuery para realizar as adaptações necessárias as diferentes resoluções.
  • Sempre se preocupar com performance.

Como fazer?

  • Definindo a viewport
<meta name="viewport" content="width=device-width, initial-scale=1">
  • Utilizar @MediaQuery e criar os breakpoints de acordo com a sua necessidade, não se prenda aos breakpoints pré-definidos por frameworks.
@media only screen and (min-width: 600px)
  • Execute o lighthouse e verifique onde a performance da sua aplicação pode melhorar.

3 - Cor de tema no site

É a definição da sua cor principal de tema, serve pra definir a cor da barra de status.

O que fazer?

  • Escolher uma cor e buscar o código hexadecimal.

Como fazer?

  • Basta adicionar a seguinte meta tag:
  <meta name="theme-color" content="#FAFAFA">

4 - Manifesto

É ele o responsável por permitir a “instalação” do seu site, nele devem ser adicionadas informações básicas da sua app.

O que fazer?

  • Podemos utilizar um gerador de manifesto como esses ou criar o nosso próprio, algumas sugestões de gerenciadores:
  • Nesse arquivo nós vamos definir o ícone que vai ser usado após a instalação e também uma splash screen após a abertura do app.

Como fazer?

  • Um exemplo de manifest.json:

    {
      "name": "Gefy Marcos",
      "short_name": "Gefy",
      "theme_color": "#333333",
      "background_color": "#000000",
      "display": "standalone",
      "scope": "/",
      "start_url": "https://gefy.com.br/index.html",
      "lang": "pt-BR",
      "orientation": "portrait",
      "icons": [
        {
            "src": "/img/logo-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
      ]
    }
    
  • Algumas regras importantes:
    • o short_name não pode ter mais que 12 letras, este é o nome que vai para a tela inicial após instalado.
    • O background color é obrigatório.
    • O array icons precisa ter pelo menos um ícone com tamanho 512x512.
    • O icone precisa ser PNG.
  • Após criar o manifest.json é só adiciona-lo no nosso head.
<link rel="manifest" href="/manifest.json">

5 - Service Worker

Agora é que fica interessante! Service worker é um script executado em segundo plano pelo navegador, ele é executado separado da página web, dessa forma ele possibilita a utilização de recursos que não precisam de uma página ou mesmo interação do usuário.

Através de service workers já é possível utilizar algumas funcionalidades que só eram possíveis em aplicativos, como push notifications e sincronização em segundo plano.

Nesse tutorial vamos nos ater a uma funcionalidade do service worker, a capacidade de interceptar e tratar solicitações de rede, respondendo com um cache caso exista.

Importante
  • O service worker funciona numa thread separada do browser, ele NÃO tem acesso ao DOM.
  • Ele sempre deve ter o mesmo nome e ficar no mesmo local para não gerar duplicação.
  • O service worker não pode cachear, nesse caso ele pode gerar um cache infinito na máquina do usuário. O certo é você definir no seu servidor o max-age e colocar pra sempre carregar de novo, sem cache.
  • Lembre-se sempre de deletar o cache antigo quando o site for atualizado.
  • Aqui nós podemos verificar quais navegadores já permitem o uso de quais funcionalidades.

Como fazer?

  • Vamos a um passo a passo de como criar um service worker, iniciamos registrando o nosso service worker, para isso precisamos adicionar o seguinte código no nosso head, tem que ser lá pra que ele seja carregado antes de tudo.

    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('sw.js')
        .then(reg => console.info('registered sw', reg))
        .catch(err => console.error('error registering sw', err));
    }
    

O ciclo de vida do service worker possui as seguintes etapas:

  • install
  • activate
  • fetch
  • message
  • sync
  • push

Para que nosso site funcione off-line só precisamos dos 3 primeiros, então vamos criar nosso arquivo sw.js.

Install

O evento install é ativado só uma vez, ao registrar a versão do sw.js. se o sw.js for alterado ele é chamado novamente, esse evento é o local onde vamos armazenar em cache os ativos da página.

var filesToAdd = ['/']

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('gefy-pwa')
      .then(function(cache) {
        return cache.addAll(filesToAdd);
      })
  );
});

Importante

A função addAll é tudo ou nada, caso um dos arquivos não seja encontrado toda a operação falha.

Activate

O evento activate também é ativado apenas uma vez, quando uma nova versão do sw.js for instalada e não possuir nenhuma versão anterior rodando em outra aba. Então você irá utilizado basicamente para deletar coisas antigas de versões anteriores.

this.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames
          .filter(cacheName => (cacheName.startsWith('gefy-')))
          .filter(cacheName => (cacheName !== CACHE_NAME))
          .map(cacheName => caches.delete(cacheName))
      );
    })
  );
});

Nesse código verificamos se o nosso cacheName inicia com o nosso prefixo gefy- ou ainda se nosso cacheName é diferente do nosso atual cache, caso seja diferente nós deletamos os arquivos antigos, isso é fundamental para não mostrar informações desatualizadas para o usuário.

Fetch

Um recurso poderoso dos service workers é a capacidade de interceptar as solicitações que a página faz e decidir o que fazer com tal solicitação, esse monitoramento é feito com o evento fetch, ele é ativado toda vez que uma página é requisitada e funciona como um proxy, ele é o responsável por verificar se o arquivo solicitado existe no cache e também por fazer os redirecionamentos.

// adicinamos o listener do evento fetch
self.addEventListener('fetch', function(event) {
  // sempre que esse evento for disparado, responda da seguinte forma:
  event.respondWith(
    // verique se a nossa request existe e está online
    checkResponse(event.request)
      .catch(function() {
        // se não for encontrada procure no nosso cache se ela existe
        return returnFromCache(event.request)}
      ));
  // aguarde a resposta, quando ela existir adicione ao nosso cache
  event.waitUntil(addToCache(event.request));
});

// verifique se o response existe
var checkResponse = function(request) {
  return new Promise(function(fulfill, reject) {
    // tente resolver a promise fazendo o fetch (online)
    fetch(request)
      .then(function(response) {
        // verifica se o status retornado é diferente de 404
        if(response.status !== 404) {
          // se não foi 404, então achou algo!
          fulfill(response)
        } else {
          // não encontrou nada
          reject()
        }
      // fetch foi rejeitado, possívelmente está offline
      }, reject)
  });
};

// se o fetch foi rejeitado, vamos ao cache
var returnFromCache = function(request){
  // abra o cacha 'gefy-pwa'
  return caches.open('gefy-pwa')
    // a abertura retorna uma promise com o cache
    .then(function (cache) {
      // verifica se a request existe no nosso cache
      return cache.match(request)
        .then(function (matching) {
          // não encontrou nada
          if(!matching || matching.status == 404) {
            // redireciona pra página de offline
            // pode ser qualquer página que não precise de conexão.
            return cache.match('/404.html')
          } else {
            // retorna a request com o resultado do cache.
            return matching
          }
        });
    });
};

// tudo feito, vamos atualizar o cache
var addToCache = function(request) {
  // abra o cacha 'gefy-pwa'
  return caches.open('gefy-pwa')
    // a abertura retorna uma promise com o cache
    .then(function (cache) {
      // já sabemos que ele encontrou, entrou faz o fetch
      return fetch(request)
        .then(function (response) {
          // pega o retorno dessa request e adiciona no nosso cache
          return cache.put(request, response);
        });
    });
};

Conclusão

Nesse artigo vimos:

  • O que é e como uma funciona uma PWA, além de seus princípios básicos.
  • Maneiras de testar sua PWA.
  • Como ativar HTTPS no seu servidor.
  • Dicas para um site responsivo.
  • Adicionar cor de tema.
  • Criar um manifesto.
  • Criar um service worker.
  • Cachear e redirecionar requests mesmo off-line.

Mas então as PWAs vão matar os Aplicativos?

Minha opnião é que sim! mas apenas aqueles apps que nem deveriam ser apps. Explico, apps que são muitas vezes instaladas e desinstaladas ou apps de baixa complexidade poderão perfeitamente serem PWAs.

Segundo levantamento realizado pelo Adjust, a maioria dos apps são desinstalados depois de 5,8 dias, na mesma pesquisa também foi observado que 40% dos usuários reinstalam os aplicativos depois de um tempo e os apps que duram mais tempo antes da “morte” são e-commerce com 11 dias e viagens com 10 dias, pra finalizar, a Adjust ainda cita que uma das maiores motivações para essa rotatividade é a falta de espaço nos smartphones.

É nesse ponto que os PWAs despontam, uma alterativa mais leve, mais fácil de usar e muitas vezes mais rápida, então sim, alguns apps vão morrer.