Categorias
JavaScript PWA - Progressive web apps

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.

Categorias
JavaScript PWA - Progressive web apps

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 queira saber mais sobre Progressive web apps confira o curso que gravei em meu canal do youtube. Caso tenha alguma dúvida deixe um comentário e até o próximo post.

Categorias
JavaScript Tutoriais Web

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 aqui

Veja também o mini curso sobre PWA

Categorias
PWA - Progressive web apps

Media queries para Progressive Web Apps

Se você me segue no Twitter já viu que estou criando uma série sobre Progressive Web Apps, neste mini curso estou abordando os principais recursos na construção de um PWA. Para esse post vamos falar sobre um conteúdo complementar como adicionar um estilo específico através media queries para identificar uma PWA.

Se você ainda não ouviu falar sobre Progressive web apps ou aplicações web progressivas, PWA não é uma biblioteca ou framework, mas uma filosofia. Uma definição rápida seria o desenvolvimento de uma aplicação com uso de técnicas e recursos para proporcionar progressivamente uma melhor experiência para o usuário com foco em três pontos:

  • Confiabilidade
  • Velocidade
  • Engajamento

Quer saber mais acesse acompanhe a playlist do mini curso:

Também me siga no youtube para acompanhar mais vídeos

Voltando ao tema principal, quando construímos nossa PWA utilizamos o manifest.json, arquivo responsável por configurar nossa PWA, nome da aplicação(name e short_name), URL de inicialização(start_url), ícones(icons) e modo de exibição(display). O modo de exibição é o ponto chave deste tutorial e ele possui os seguintes modos:

  • fullscreen – como o nome sugere exibe em modo full screen sem nenhum elemento de interface do browser
  • standalone – Exibe como uma aplicação independente do browser, mas dependendo do sistema operacional podendo exibir alguns elementos de controle de navegação, por exemplo, iOS não exibe itens de navegação mas permite o controle de navegação através de gestos.
  • minimal-ui – Esse modo é exibido como uma aplicação independente mas forca a exibição de elementos de navegação
  • browser – Esse modo exibe como um browser convencional

Fullscreen e standalone

Agora que a gente teve uma introdução sobre os modos disponíveis, dois modos precisam de atenção especial fullscreen e standalone eles precisam prover uma navegação dentro da aplicação pois os elementos de navegação do browser são removidos, com isso o usuário pode ficar preso em sua aplicação e isso não é uma boa experiência.

Para isso podemos implementar media query para controlar a exibição controle de navegação ou apenas estilizar nossa aplicação. Atualmente temos um seletor para display-mode dentro de media queries, com isso podemos implementar media queries para diferentes modos, mas lembrando que a media query precisa seguir o parâmetro definido em seu manifest.json.

Media query para desktop e mobile

//media query para o modo de exibicao standalone presente tanto em mobile e desktop 
@media (display-mode: standalone) {
 
}

No código acima temos uma media query para o formato standalone, suportado por PWAs que rodam no desktop ou mobile, mas poderíamos utilizar um dos quatros padrões que vimos anteriores.

Um fato relevante neste momento até a versão 12.2 do iOS não temos suporte ao web app manifest em outras palavras iOS não utiliza o manifest.json, a definição de ícones e títulos para nossa PWA é feito através de meta tags dentro de nosso HTML, e elas apenas suportam o formato standalone.

Caso utilize um formato diferente de standalone precisamos ter duas media queries uma para android e outra para iOS

//media query para MacOS e iiOS
@media (display-mode: standalone) {
 
}

//media query para Android caso definido no manifest.json
@media (display-mode: fullscreen) {
 
}

Media query para iOS

Mas o código acima pode perder sua funcionalidade quando iOS começar a suportar web app manifest, mas caso queiramos ter uma estilização estritamente para iOS, por simples motivo cada sistema operacional tem seu guia de interface, por exemplo, Android geralmente exibe seus items de navegação na base da tela enquanto iOS exibe os elementos de navegação no topo da aplicação.

Com media query podemos incluir mais uma regra dentro de nosso media query o “@supports” com ele podemos realizar filtros através de features suportada pelo browser, por exemplo, suporte a display grid podemos utilizar o seguinte código:

@supports (display: grid) {
  div {
    display: grid;
  }
}

Agora que sabemos desse recurso como podemos implementar essa regra com PWA? Temos algumas features apenas suportada pelo iOS no caso temos “-webkit-overflow-scrolling” presente apenas no safari mobile. Com isso podemos da seguinte forma:

/* 
 - podemos utilizar display mode em: standalone, fullscreen, minimal-ui e browser
*/

@media (display-mode: standalone) {
  /*To das as pwas instaladas desktop e mobile */
}

@media (max-width: 576px) and (display-mode: standalone) {
  /* Filtro para pwas instalada apenas em dispositivos moveis */
  
  @supports (-webkit-overflow-scrolling: touch) {
    /* PWAs instaladas apenas no iOS */
  }
  
  @supports not (-webkit-overflow-scrolling: touch) {
    /* PWAs instaladas em dispositivos não iOS */
  }
}

Com o código acima cobrimos os principais casos para PWA, em breve estarei postando um vídeo sobre esse assunto para seguir o conteúdo dessa série confira os post sobre Progressive web apps. Caso tenha alguma dúvida só deixar um comentário.

Categorias
JavaScript

Web Share api

Web Share API permite o usuário compartilhar conteúdo através do card de compartilhamento nativo do sistema operacional em que está sendo executado, nos dispositivos Android esse suporte surgiu na versão 61 do Chrome em 2017, já para para iOS o suporte foi incluído no Safari em 2019 na versão 12.2 do iOS.

Para fazer a chamada no card nativo de compartilhamento chamamos a função navigator.share() em nosso browser, esse método faz parte da Web Share API, ele dá o controle ao usuário de como ele quer compartilhar esse conteúdo. Seu uso é baseado em Promise, o método aceito um objeto com as informações de título, texto de descrição e url como podemos ver no código abaixo:

if (navigator.share) { navigator.share({ title: 'Blog Fellyph Cintra', text: 'Como compartilhar conteúdo com web share API', url: 'https://blog.fellyph.com.br/', }) .then(() => console.log('Compartilhamento realizado com sucesso')) .catch((error) => console.log('Erro no compartilhamento', error)); }

No exemplo acima, inicialmente utilizamos uma condicional para verificar se o suporte a web share api existe, caso positivo definimos o conteúdo que será compartilhado e adicionamos um then para tomar uma ação caso o compartilhamento seja realizado com sucesso ou catch em caso de error.

Requisitos

Para invocar o card nativos precisamos de alguns requisitos:

  • A URL deve ser servida por HTTPS
  • O método share deve ser invocado por uma resposta do usuário, por exemplo, um evento de click. Você não pode chamar a share API em um evento de page load.
  • Você só pode compartilhar uma URL dentro do escopo da aplicação
  • Sempre verifique se o usuário tem suporte a essa feature antes de chamar o método

Resultado

No lado esquerdo temos o resultado no iOS e a versao Android no lado direito

Na imagem acima temos dois exemplos de como o card de compartilhamento será exibido no Android e iOS. Para a plataforma iOS o recurso está disponível para Chrome e Safari. Vamos finalizamos por aqui caso tenha alguma dúvida só deixar um comentário.