Curso online Progressive web apps

Em meu canal no youtube iniciei um curso totalmente grátis sobre Progressive web apps(PWAs), inicialmente tinha a ideia de publicar alguns tutoriais sobre PWA mas o projeto foi evoluindo e agora posso chamar de curso. O conteúdo é publicado semanalmente com processo para a construção e teste de uma PWA do início ao fim. Atualmente temos os seguintes tópicos publicados:

Apresentação do projeto – Curso Progressive Web Apps #01

No primeiro vídeo série vamos apresentar o nosso projeto base que será utilizado nos próximos vídeos, além de entender porque trabalhar com #PWA e ter uma visão geral sobre o conteúdo que iremos abordar em nosso curso.

Analisando projeto com Lighthouse – Curso Progressive web apps #02

Nesse tutorial vamos analisar o nosso projeto com lighthouse e ver os recurso que precisamos implementar para converter nossa aplicação em uma #pwa

Instalando service worker – Curso Progressive Web Apps #03

Neste terceiro vídeo vamos ver como instalar um service worker em nossa aplicação.

Introdução a Service Worker – Curso Progressive Web Apps #3.1

Nesse vídeo vamos falar um pouco mais sobre Service Worker. No vídeo anterior da série tratamos de forma mais prática, agora vamos abordar o assunto num modo mais teórico. Como o service worker é instalado, como ele é executado em segundo plano e como funciona o seu ciclo de vida.

Post complementar : Introdução a service worker

Adicionando versão offline – Curso Progressive Web Apps #04

Nesse vídeo vemos como implementar uma versão offline para nossa aplicação utilizando a estratégia cache first.

Teoria sobre web app manifest – Curso Progressive Web Apps #05

Nesse tutorial iremos entender os conceitos sobre o web app manifest, os requisitos necessários para exibir o prompt para instalação da nossa PWA. Quais os passos devemos tomar o nosso código JavaScript.

Criando manifest.json – Curso Progressive web apps #06

Vamos abordar como adicionar a nossa aplicação o manifest.json arquivo responsável por definir a instruções para nossa Progressive Web App torna-se instalável.

Novidades do Chrome 73, ativando o pop-up de instalação e PWAs no Mac OS – Curso PWA #07

Nesse vídeo vamos ver as novidades no relatórios do Lighthouse no Chrome 73, os passos necessários para exibir o pop-up de instalação, os eventos JavaScripts que podemos utilizar nesses fluxo e por fim ver a instalação do de uma PWA no Mac OS.

Progressive web apps: 08 iOS vs. PWA – Curso Progressive web apps #08

Nesse vídeo iremos ver como o iOS trabalha com PWAs, os updates recentes feito na versão 12.2, como por exemplo, suporte a web share API e os principais bugs corrigidos. Por fim iremos ver como adicionar suporte a instalação do seu app a tela inicial do iOS.

Configurando ícones para tela inicial do iOS – Curso Progressive Web Apps #09

Nesse vídeo vamos ver como configurar os ícones e título para nossa aplicação ser adicionada na tela principal do iOS. A plataforma iOS na sua versão 12.2 ainda não dá suporte ao Web app manifest, mas desde a primeira versão do iOS podemos incluir em nosso HTML tags específicas para informar o sistema operacional como adicionar a nossa aplicação na tela inicial de um dispositivo iOS Vídeo também começo com um agradecimento especial para todos que estão compartilhando e apoiando este projeto.

Utilizando web share API – curso Progressive web apps #10

Saiba como utilizar a web share api recurso agora disponível na plataforma #Android e #iOS, a web share API faz a chamada do card nativo de compartilhamento do dispositivo móvel. Além de adicionar o recurso vemos quais são os pre-requisitos necessários para fazer o uso deste recurso.

Conteúdo complementar: Introdução a web share API

Criando media queries para PWAs – Curso Progressive web apps #11

Veja como criar media queries para tratar nossa interface quando o usuário estiver executando nossa aplicação em modo fullscreen. Além de identificar diferentes plataformas como iOS ou Android, por fim iremos utilizar javaScript para controlar o conteúdo que será exibido.

Conteúdo complementar: Media queries para Progressive web apps

Badging API – Curso Progressive Web Apps #12

Após uma série de lançamentos no último Google I/O 2019, nesse vídeo vamos continuar nossa série sobre PWAs, dessa vez vamos falar sobre como utilizar a Badging API, recurso atualmente em modo experimental mas serve como uma alternativa menos intrusiva que o push notifications.

Conteúdo complementar: Badging API

Estratégias de cache para PWA – Curso Progressive web apps #13

Agora vamos falar das principais técnicas de cache para PWA, O Service Worker tem a capacidade de interceptar as requisições de nossa aplicação além de possibilitar o suporte a aplicações offline. Com isso conseguimos aplicar estratégias de cache para diferentes casos onde iremos ver nesse vídeo.

Conteúdo complementar: Estratégias de cache para PWAs

Introdução a Workbox 4 – Curso Progressive web apps #14

Continuando nossa série sobre #PWA nesse vídeo vamos ver uma introdução sobre workbox, biblioteca que irá reduzir a complexidade para implementar estratégias de cache em nossa aplicação. Workbox é uma biblioteca mantida pelo Google, nesse vídeo vamos ver as diferente formas de utilizar workbox.

Conteúdo complementar: Introdução a workbox

Introdução a Workbox

Workbox é um conjunto de bibliotecas e módulos do node que simplifica o processo de cache de assets em nossa aplicação, assim agilizando o nosso trabalho na criação de uma Progressive Web Apps. Este tutorial será baseado na versão 4 da biblioteca. Workbox trabalha dois conceitos importantes sobre PWAs:

Performance: não espere por todos os arquivos de sua aplicação virem da internet, crie estratégias de cache para servir arquivos do armazenados em seu device.

Resiliência: Conexões móveis ou em regiões com fraca infra estrutura podem afetar a experiência do usuário, habilite sua aplicação administrar situações que a conectividade é limitada

Porquê Workbox?

Workbox é uma biblioteca reune as melhores práticas e toda a complexidade de trabalhar com service workers com por exemplo:

  • Precaching
  • Runtime caching
  • Estratégias de cache
  • Requisição de rotas
  • Background Sync
  • Ajuda no debug da aplicação

Opções de utilização

Para trabalhar com Workbox temos as seguintes alternativas:

  • Workbox CLI
  • node.js
  • webpack plugin

Lembrando que essas alternativas são independentes você deve escolher somente uma, então escolha a alternativa que melhor se adequa ao seu caso.

Workbox CLI

CLI nada mais é que uma acrônimo para Command line Interface(Interface de linha de comando) tem como objetivo trazer um grupo de comandos no terminal para possibilitar realizar uma terminada ação, seja criar novos projetos ou administrar recursos existentes. Mas tendo como foco principal reduzir o trabalho e a complexidade ao item aplicado.

Primeiro passo quando trabalhamos com um CLI é fazer sua instalação workbox-cli depende do node.js para funcionar, antes de rodar qualquer comando para o CLI precisamos instalar a versão mais recente do node.js, após a instalação do node rodamos os seguinte comando em nosso terminal:

npm install workbox-cli --global

Se tudo ocorrer bem seremos capazes de testar com o seguinte comando:

workbox --help

Se o terminal reconheceu o comando workbox sinaliza que a instalação foi bem sucedida, agora é hora de utilizar o comando:

workbox wizard

Ele irá nos dar um passo-a-passo para a configuração de nosso projeto com as seguintes perguntas:

  1. Qual é a pasta do nosso app onde se encontra a pasta em que iremos exportar nossa aplicação?
  2. Quais os tipos de arquivos em que gostaríamos de realizar o preache?
    1. svg, jpg, png, html, css, js, json
  3. Onde preferimos salvar o nosso service worker?
  4. Onde gostariamos de salvar nosso arquivo de configuração

Como podemos ver na imagem abaixo, no meu caso estou rodando a versão 4.3.1 do Workbox, versões anteriores ou futuras esse passos podem mudar um pouco mas o core sempre será quais arquivos queremos fazer o cache e qual será o nosso service worker.

Por fim ele exibe o comando necessário para gerar nosso service worker:

workbox generateSW workbox-config.js

Quando rodamos esse comando ele irá ler o arquivo de configuração gerado pelo wizard:

module.exports = {
  "globDirectory": "dist/",
  "globPatterns": [
    "**/*.{svg,jpg,png,html,css,js,json}"
  ],
  "swDest": "dist/sw.js"
};

Varrer e criar um cache para todos os arquivos com as extensões especificadas anteriormente. Você terá um retorno parecido com a mensagem a seguir.

The service worker was written to dist/sw.js
22 files will be precached, totalling 84.2 kB.

Esse comando será necessário toda a vezes em que alteramos os arquivos dentro de nossa aplicação, para agilizar esse processo podemos fazer uma integração com gulp ou webpack.

Gulp + workbox

Como mencionamos anteriormente podemos agilizar o processo de criação de service worker utilizando ferramentas de automatização de tarefas, basicamente vamos assistir as mudanças realizadas em nosso projeto quando algum arquivo for alterado o Gulp será responsável por gerar o nosso service worker

Instalação:

npm install workbox-build --save-dev

Exemplo

const workboxBuild = require('workbox-build');

// NOTE: Esse comando deve ser executado depois que os assets forem gerados
const buildSW = () => {
  // assim retornamos generateSW que retorna uma Promisse
  return workboxBuild.generateSW({
    globDirectory: 'build',
    globPatterns: [
      '**\/*.{html,json,js,css}',
    ],
    swDest: 'build/sw.js',
  });
}

webpack + workbox

Para webpack também temos um plugin especifico para trabalhar com workbox ele tem suporte precaching e runtime caching. Caso ainda não conheça webpack tenho um post de introdução aqui.

Para instalação do plugin na pasta de nosso projeto onde ficar o nosso arquico package.json executamos o seguinte comando em nosso terminal:

npm install workbox-webpack-plugin --save-dev

Depois de instalado precisamos configurar o nosso webpack, dentro do arquivo de configuração do webpack adicionamos o Plugin para workbox:

// dentro do webpack.config.js:
const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
  // Outras configurações...

  plugins: [
    // Outros plugins...
    new WorkboxPlugin.GenerateSW()
  ]
};

Após tudo configurado precisamos registrar nosso service worker:

<script>
// verificamos se o browser suporta service worker
if ('serviceWorker' in navigator) {
  // usamos o evento load para registrar nosso service worker
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js');
  });
}
</script>

Caso queira saber mais sobre service worker tenho um post de introdução a service worker também estou rodando um curso gratuito sobre PWA em meu canal no youtube. Qualquer dúvida só deixar um comentário e até o próximo post.

Estratégias de cache para PWAs

Continuando a série sobre PWA tivemos uma introdução sobre Service Worker. Vimos que o service worker permite o suporte a aplicações offline mas também podemos controlar as requisições feitas pelo o navegador isso nos dá a habilidade de pensar em diferentes estratégias de cache para a nossa aplicação e esse será o tema do nosso post.

Desenvolvendo uma aplicação web alguns dados dentro da nossa solução tem uma periodicidade maior ou menor de atualização, por exemplo a logo de nosso site é tipo de dado que pode passar anos sem alteração, já no caso de uma sessão de últimas notícias podem ter uma periodicidade de minutos. Com esse dois exemplo podemos ver situações distintas, assim com a partir desses exemplos vamos abordar as estratégias mais comum de cache.

Cache only

Nesse caso todos as requisições serão direcionadas para o cache.

  1. O service worker recebe a requisição
  2. Consulta se o conteúdo se encontra no cache
  3. Retorna o conteúdo requisitado para o usuário

Se não encontrado no cache a requisição irá falhar. Mas esse pattern assume que os conteúdos serão armazenados no cache durante a instalação do service worker.

Casos recomendados: esse padrão é ideal para assets que raramente são atualizados, por exemplo, logos, ícones de redes sociais e estilos. Mas isso não significa que eles nunca serão alterados, esse controle será feito pela versão do seu service worker.

Código:

self.addEventListener('fetch', event => {
  // todos os arquivos serão servidos pelo cache 
  event.respondWith(caches.match(event.request));
})

Cache first

Parecida com a estratégia anterior, mas com uma diferença, caso não ache o arquivo no cache ele irá realizar uma requisição na rede como podemos ver no código a seguir:

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

Network only

  1. O service worker irá analisar a requisição
  2. Irá requisitar na rede os arquivos requisitados
  3. O conteúdo requisitado é enviado para o usuário

Neste caso se a requisição a rede falhar não retornará nada.

Casos recomendados: Esse útil para requisições exemplo pings no servidor para análise de tráfego ou estatísticas.

Network first

Nessa situação primeiramente fazemos o request para a rede, caso a requisição falhe procuramos o arquivo no cache. Problema nessa requisição que perdemos muito tempo esperando um retorno de uma falha na requisição.

Casos recomendados Trabalhamos com essa estratégia quando temos a prioridade dos dados recentes. Ideal para áreas que tem atualizações constantes.

Código:

self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request).catch(() => {
      return caches.match(event.request);
    })
  );
});

Cache, then network

Essa estratégia carrega os dados imediatamente do cache enquanto verifica na rede se o conteúdo sofre alguma alteração. No momento que receber o retorno das informações da redes, o conteúdo será verificado caso precisa será atualizado.

Temos o benéfico de exibir o conteúdo imediatamente mas sempre fazemos duas requisições precisando ou não da informação. Por que nesse caso só sabemos se o conteúdo foi alterado se requisitamos a informação na rede, se nada mudou, fazemos uma requisição em vão. Outro desafio desse padrão é desenvolver uma interface que não pareça estranho a atualização do conteúdo.

Casos recomendados: Quando priorizamos a performance mas temos uma frequência constante de updates do conteúdo, aplicações com timeline de e game leader board.

Código (sw.js)

self.addEventListener('fetch', event => {
   event.respondWith(
     caches.open('mysite-dynamic').then(cache => {
        return fetch(event.request).then(response => {
          cache.put(event.request, response.clone());
          return response;
        })
     })
   );
 });

Código(main.js)

let networkDataReceived = false;
startSpinner()
const networkUpdate = fetch('/data.json').then(response => {
  if(!response) throw Error('no data');
  return response.json();
}).then(data => {
  if(!networkDataReceived);
  updatePage(data);
}).catch(() => {
  return networkUpdate;
}).catch(showErrorMessage).then(stopSpinner);

Stale while revalidate

Utilizamos essa técnica quando não temos prioridade do conteúdo mais recente mas queremos fazer atualizações periódicas. Essa estratégia é parecida com a cache first, mas com uma diferença os dados mais recentes não serão atualizados imediatamente, o cache será atualizado e a informação será exibida quando o usuário recarregar a página.

Casos recomendados: Logos, imagens do perfil do usuário.

Código

self.addEventListener('fetch', event => {
   event.respondWith(
     caches.open('mysite-dynamic').then(cache => {
        return cache.match(event.request).then(response => {
         const fetchPromise = fetch(event.request)
          .then(networkResponse => {
           cache.put(event.request, networkResponse.clone());
           return networkResponse;
          });
          return response || fetchPromise;
        })
     })
   );
 });

Generic fallback

Quando o usuário usuário não achar o conteúdo requisitado na rede ou no cache retomamos um conteúdo genérico. Por exemplo, quando não conseguimos encontrar uma imagem específica como um avatar, para não prejudicar nossa interface retornamos um avatar genérico, por exemplo, uma ilustração. Esse padrão pode ser combinado com as estratégias anteriores.

Código

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

Esse são as principais estratégias de cache, além delas podemos combinar estratégias de cache, mas para esse post vamos abordar os mais populares nos próximos posts iremos abordar uma ferramenta que irá facilitar nossa vida: Workbox, mas isso é assunto para um novo post.

Caso tenha alguma dúvida deixe um comentário e até o próximo post.

Introdução a Service Worker

Nesse posts vamos falar sobre service worker é um dos recursos chaves dentro das Progressive web Apps vamos entender como ele funciona e o seu ciclo de vida. Para quem ainda não viu, estou gravando uma série sobre PWA em meu canal do youtube, até o momento tenho mais de 12 aulas gravadas também estou adicionando conteúdo complementar no blog.

O que é um service worker?

Nada mais é que o coração das PWAs, não é nenhuma tecnologia especial e sim um arquivo JavaScript que roda em segundo plano em nossa aplicação o que isso significa? Ele não é executado na thread principal da aplicação junto com execução do HTML e CSS, ele não tem acesso ao DOM por exemplo. Isso abre uma porta para recursos rodarem independente do browser por exemplo push notifications e execução de código em segundo plano. Outro recurso extremamente importante é o suporte a experiência offline, dando ao usuários controle sobre a aplicação independente da conectividade.

Pontos importantes sobre Service Worker

  • Precisa estar rodando em HTTPS
  • Ele é uma JavaScript Worker, ele não tem acesso ao DOM
  • Permite controlar como serão feitos os requests a rede
  • Faz o uso intensivo de promises
  • Precisa ser instalado por outro JavaScript
  • Possui um escopo e esse escopo é baseado onde o arquivo do service worker se encontra.

Ciclo de vida

Quando registrado por outro JavaScript, o service worker passa a ter um ciclo de vida independente do conteúdo web em que foi registrado. Ele será movido para outra Thread como mencionamos anteriormente, quando registrado ele irá chamar o evento Install event, se durante o processo de instalação ocorrer um erro na transferência de algum arquivo o processo de instalação irá falhar. mas se tudo ocorrer bem ele irá disparar um segundo evento Active event.

Depois que o Active event é disparado o service worker está pronto para disparar funções em segundo plano e acompanhar os dados que estão sendo transferidos entre aplicação e a rede. Neste momento conseguimos definir estratégia de cache e suporte offline. A imagem abaixo resume os passos que acabamos de comentar.

Ciclo de vida de um service worker

Na imagem acima temos uma definição completa do ciclo de vida de um service worker, com adição a um ponto importante quando adicionamos uma nova versão ela fica em estado de espera, ela só será instalada quando todas as instâncias da nossa aplicação forem finalizadas.

Registrando um Service worker

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // service worker registrado com sucesso
      console.log('ServiceWorker registrado no seguinte escopo: ', registration.scope);
    }, function(err) {
      // falha no registro:(
      console.log('Falha no registro do ServiceWorker: ', err);
    });
  });
}

O código acima será implementado pelo o JavaScript responsável por registrar o nosso Service Worker, na primeira linha verificamos se o nosso browser possui suporte a tecnologia, caso tenha, o registramos com a função com navigator.serviceWorker.register e através de uma promise verificamos se o service worker foi instalado com sucesso. E dentro do nosso sw.js podemos adicionar listener para acompanhar o seu ciclo de vida:

self.addEventListener('install', function(event) {
  // acompanha a instalação do service worker
});

self.addEventListener('activate', function(event) {
  // acompanha quando o service worker está ativo
});

self.addEventListener('fetch', function(event) {
  // acompanha quando a aplicação faz transferência de arquivos
});

Os listeners serão importante para criar um cache, acompanha mensagens e definir estratégias de cache. Para esse post, vamos fechando por aqui, se ainda não segue o meu canal no Youtube, lá estou sempre postando conteúdo sobre PWA: https://www.youtube.com/user/fdangellys

Veja os posts sobre PWA

Veja também o mini curso sobre PWA

Badging API

A Badging API é uma das novas APIs criadas para fechar o gap entre aplicações nativas, a badging API foi incluída na versão 73 do Chrome em versão de teste, permite adicionar contador de notificações nos ícones de nossa PWA na barra de tarefas, diferente de push notification badging api é menos intrusiva apenas exibe um balão de notificação sobre o ícone de nossa aplicação, atualmente Window 7+ e MacOS possuem suporte a essa funcionalidade através de token especial para o recurso.

Google iniciou um projeto com foco na inclusão de novas funcionalidades e o mais legal que esse projeto será baseado em requisições feitas pela comunidade para saber mais sobre o projeto acesse o link:
https://developers.google.com/web/updates/capabilities

Casos sugeridos para utilizar a badging API

  • Aplicações de Chat, email e social apps que precisam notificar que o usuário recebeu novas mensagens
  • Aplicações precisa sinalizar que processos que estavam rodando em background foram concluídas
  • Jogos que estão aguardando uma ação do usuário.

Requisitos

  • Chrome 73 ou superior
  • Aplicação precisa estar instalada como PWA

Como utilizar

A partir da versão 73 a Badging API está disponível através de um recurso chamado Origin trials(Versões de avaliação) ele permite testar novas features e dar feedback de usabilidade, praticidade e eficiência. Caso queira saber mais sobre o projeto acesse o link.

Registrando para Versões de avaliações

  1. Requiste um token para sua origin
  2. Adicione o token em sua página, temos dois modos para prover esse recurso em qualquer página de sua versão de teste.
    1. Adicionando uma meta tag origin-trial no cabeçalho de sua aplicação
    2. Se você tiver acesso a configuração do seu servidor, você também pode prover um token em sua página adicionando a propriedade Origin-Trial em seu HTTP header.
    3. Se você quiser testar localmente também pode fazer o uso habilitando a flag #enable-experimental-web-platform-features acessando o endereço chrome://flags
Caso queira utilizar o token você pode essa será a página de requisição

Para esse tutorial vamos utilizar a flag do chrome, atualmente(maio 2019) estamos na versão 74 a previsão do uso definitivo desse recursos será na versão 78. Caso esteja lendo este post com a versão do Chrome 78 ou superior esse passo não será necessário. Mas para quem utiliza versão inferior a 78 vamos habilitar o recurso acessando o endereço chrome://flags:

Para facilitar nossa vida podemos pesquisar por “experimental web” para ir diretamente a flag. Após nossa flag habilitada precisamos reiniciar nosso Chrome para o recurso funcionar em nosso browser.

Agora que habilitamos a flag hora de testar o recurso, a badging API tem duas funções window.ExperimentalBadge.set() para definir o contador e window.ExperimentalBadge.clear() para limpar nosso contador.

Para os nossos testes vamos utilizar como base o projeto que estou desenvolvendo no mini curso de PWA que estou rodando no Youtube.

Inicialmente vamos adicionar dois botões em nossa página:

<button class="badingAPI__add">Add</button>
<button class="badingAPI__clean">Clean</button>

Nosso JavaScript ficará da seguinte forma:

const addBadge = document.querySelector('.badingAPI__add');
const cleanBadge = document.querySelector('.badingAPI__clean');
let counterBadge = 0;
let bagdeTimer;

if (addBadge) {
  addBadge.addEventListener('click', () => {
    if (counterBadge === 0) {
      bagdeTimer = window.setInterval(() => {
        counterBadge += 1;
        window.ExperimentalBadge.set(counterBadge);
        console.log('counter');
      }, 1000);
    }
    console.log('triggered');
  });

  if (cleanBadge) {
    cleanBadge.addEventListener('click', () => {
      counterBadge = 0;
      window.ExperimentalBadge.clear();
      clearInterval(bagdeTimer);
    });
  }
}

No Código acima realizamos uma simulação da atualização das notificações com um setInterval(), quando o usuário clica no botão “add” inicializamos o contador, passamos como parâmetro um número dentro da função set, caso queiramos apenas exibir o balão sem um número, chamamos a função set sem passar nenhum parâmetro. Numa aplicação real atualizamos o contador quando temos uma mensagem não lida ou precisamos completar alguma tarefa.

No macOS podemos ver o contador de notificações funcionando

Para o botão clean, quando o usuário clicar no botão, zeramos o contador e removemos a execução do nosso timer com clearInterval, e para remover a badge do ícone chamamos a função: window.ExperimentalBadge.clear().

Esse recurso está em modo de teste mas em breve será incorporado sem o uso de flag no Chrome para acompanhar o status e conferir mais informações acesse: https://developers.google.com/web/updates/2018/12/badging-api

Qualquer dúvida só deixar um comentário e até o próximo post!