Ícone do site Taller

PWA com React

O que é PWA?

O PWA vem da sigla “Progressive Web App”, ele é um web app que usa as modernas funcionalidades dos navegadores para entregar uma experiência de um app na web para os usuários. Ele é muito similar a um sistema web, tem só que satisfazer alguns requisitos (que serão comentados abaixo). Fora isso o deploy é feito num servidor, acessíveis por uma url e indexados pelos motores de buscas.

Características de um PWA

Antes de mais nada, é preciso entender algumas características importantes que um PWA precisa ter. Confira abaixo algumas das mais importantes:

Porque desenvolver um PWA?

Existem muitos aplicativos que não são usados com tanta frequência e que poderiam ser uma simples página na web, por exemplo, um app de pagamento de estacionamento ou de companhias aéreas (queria poder usar este app com mais frequência -_- ). Além disso nem sempre temos o espaço para armazenar todos os aplicativos que desejamos e algumas vezes até queremos baixar mas o plano de dados já está no limite ou a conexão está lenta.

Por outro lado, um PWA resolve todos esses problemas, pois deixa mais acessível para todos usuários e também garante uma estabilidade melhor do sistema pois, quem estiver acessando, usará sempre a última versão do sistema.

Ao contrário do que alguns pensam, é possível “instalar” o PWA no smartphone. É criado um atalho na tela inicial, simulando um app nativo. É possível também enviar push notifications. Quer alguns exemplos de sucesso? A empresa eXtra Electronics migrou para PWA e conseguiu aumentar em 4x o engajamento dos usuários e seus clientes passaram a ficar no site o dobro do tempo. Outro exemplo foi a AliExpress que depois de adotar um PWA a aumentou a taxa de conversão em 104% em todos os browsers e 82% no IOS.

Se depois de tudo isso você não ficou interessado em aprender mais sobre PWA é uma pena, fiz o que pude :’(

Show me the code!

Quem ficou animado e doido para meter a mão no código, chegou a hora. Mas é preciso que você já tenha uma base de React pois não irei explicar essa parte (se você quiser, no blog tem uma seleção de posts sobre isso), o objetivo é transformar um app React em um PWA.

Vamos começar clonando um projeto que eu já deixei algumas coisas prontas, para focarmos somente no PWA:

$ git clone https://github.com/raivitor/pwa-taller.git
$ cd pwa-taller
$ yarn install
$ yarn start

Quem tiver algum problema com o yarn pode executar os mesmos comandos trocando por npm. Nesse momento deve abrir uma nova janela e o projeto já estará rodando nela.

Configuração do manifest

O web app manifest é um simples arquivo JSON que nos dá a habilidade para controlar como o nosso app aparece para o usuário nas áreas que eles esperariam ver aplicativos (como por exemplo a home screen), direcionar o que o usuário pode iniciar e mais importante como eles podem iniciá-lo.

Usando o web app manifest, nosso web app pode:

Quando se cria um arquivo pelo creat-reat-app já vem com um ./public/manifest.json e é ele que vamos alterar. Copie e cole o código abaixo.

{
  "short_name": "todo",
  "name": "Lista de tarefas",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "icon.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ],
  "start_url": "./index.html",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

Vamos lá para a explicação desses atributos:

Service Worker

Pré-requisitos

Criando o service worker

Criaremos um arquivo ./public/service-worker.js que será o nosso service worker. Após criar note que em ./src/registerServiceWorker.js, há um referência ao service-worker.js na linha 33: const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;. Se por algum motivo você precise mudar o nome do service-worker.js em public, lembre-se de mudar em registerServiceWorker.js.

Começaremos criando uma constante para salvar o nome do nosso cache e logo em seguida adicionaremos as urls que desejaremos que realize o cacheamento. Note que adicionamos o bundle.js pois é o bundle que o webpack cria. Em seguida tem o link do bootstrap que chamamos em ./public/index.html. Por último, mas muito importante, adicionamos o endpoint da API que estamos consumindo, sem esse link a PWA não vai funcionar corretamente offline.

const CACHE_NAME = 'pwa-cache-v1';

const urlsToCache = [
  "/",
  "static/js/bundle.js",
  'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css',
  'https://5a9dd5a1a65e7d0014436b95.mockapi.io/note'
]

Eventos de um service worker

Agora explicarei os eventos de um service worker. O primeiro a ser executado é o install, ele é executado somente uma vez, depois de instalado não é mais executado. A promessa waitUntil() sinaliza a duração e o êxito ou uma falha na instalação. Dentro dela usamos o caches.open(CACHE_NAME) onde criamos o cache da PWA e ele retorna uma promessa quando termina de criar o cache, dentro dela chamamos cache.addAll() e passamos o array urlsToCache com as urls que queremos cachear.

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function (cache) {
        return cache.addAll(urlsToCache);
      })
  );
});

Depois da instalação o próximo passo é o activate esse é bem simples: ele verifica se tem cache antigo, limpa e adiciona o novo. Se você alterar o CACHE_NAME para 'pwa-cache-v2' acontecerá uma exclusão dos caches antigos e será cacheado tudo novamente.

self.addEventListener("activate", event => {
  const cacheWhitelist = [CACHE_NAME];
  event.waitUntil(
    caches.keys().then(keyList =>
      Promise.all(keyList.map(key => {
        if (!cacheWhitelist.includes(key)) {
          return caches.delete(key);
        }
      }))
    )
  );
});

Finalmente, é aqui é onde a magia acontece. Toda vez que o PWA precisa de algum recurso o evento fetch dispara e ele busca no cache se já temos esse recurso e manda para o usuário. Caso não tenha, segue o fluxo normal. O método caches.match() é quem faz a verificação se o que a aplicação tá requisitando está no cache.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

Finalizando

Espero que tenham gostado deste post. Ele foi bem introdutório mas tentei explicar bem os conceitos básicos do PWA. O código final está na branch dev do projeto que clonaram, então qualquer dúvida podem olhar lá o código ou comentar no post.

Sair da versão mobile