Categorias
PWA - Progressive web apps

Novidades da Microsoft e Apple sobre PWA

Nos meses de Maio e Junho tivemos as conferências para desenvolvedores da Microsoft Build e Apple WWDC. Nelas tivemos alguns novidades importantes para as Progressive web Apps.

Primeiramente a Microsoft reforçou sua estratégia de investir em Progressive Web Apps para desktop e o suporte do time da Microsoft ao projeto Fugu, Durante a apresentação: Building rich app experiences with Progressive Web Apps foram exibidas as novas integrações de PWA com o Windows 10 como:

  • Executando uma PWA quando o OS é inicializado
  • App Shortcuts
  • Integração com Share target
  • Associação de extensões de arquivos com PWAs
  • Associação de protocolos com PWAs
  • Proposta para personalizar a barra de título da janela da aplicação.

A seguir irei listar alguns recursos com mais detalhes.

Executando uma PWA quando o OS é inicializado

Assim como aplicações nativas também será possível definir uma PWA auto inicializar quando o usuário realiza o login no sistema operacional. Para habilitar esse recurso no Windows 10 adicionamos a seguinte propriedade em nosso manifest.json:

request_on_install: [ "runonstartup", ]
Code language: JavaScript (javascript)

Quando requisitado a instalação o usuário terá a opção de habilitar o novo recurso como na imagem abaixo:

Esse recurso estará disponível no Edge canary em julho

Share Target API para desktop

PWAs agora podem registrar se registrar como destino de compartilhamento em aplicações desktop. A share target API já um recursos disponível on Android, agora também estará disponível para Windows 10.

App shortcuts

Confesso que é um dos recursos que estou animado para testar, app shortcuts permite criar atalhos nas ações rápidas de sua aplicação, geralmente acessado pelo ícone na barra de tarefas(Desktop) ou na home screen(Android), este recurso estará em breve disponível para Chrome/Edge para mobile/desktop como podemos ver nas imagens abaixo:

app shortcuts windows
App shortcuts android

Para adicionar shortcuts incluímos o seguinte código em nosso manifest.json:

"shortcuts": [ { "name": "Play Later", "description": "View the list of podcasts you saved for later", "url": "/play-later", "icons": [ { "src": "/icons/play-later.svg", "type": "image/svg+xml", "purpose": "any" } ] }, { "name": "Subscriptions", "description": "View the list of podcasts you listen to", "url": "/subscriptions?sort=desc" } ]
Code language: JavaScript (javascript)

Associação de arquivos com PWA

Esse recurso permite associar sua aplicação PWA com tipos de arquivos, URLs e protocolos específicos, por exemplo dentro de seu arquivo manifest.json você irá adicionar as seguintes informações:

"file_handlers": [ { "action": "/editimage", "accept": { "image/jpg": [".jpg"] } } ]
Code language: JavaScript (javascript)

Atualmente não conseguimos realizar isso com Windows mas na novas versões do Windows 10 será possível associar uma PWA a um tipo de arquivo. Em sua PWA você saberá quando o usuário tentar abrir um arquivo em sua aplicação com o seguinte código:

if('launchQueue' in window) { launchQueue.setConsumer(e => { let jpgFile = e.files[0]; }) }
Code language: JavaScript (javascript)

Seguindo a mesma lógica, também conseguimos associar protocolos a PWAs, por exemplo, uma URL para enviar um email. A previsão para esse recursos são para a versão 86 do Edge.

Privacidade

Além dos recursos que listei anteriormente no Windows 10 também será possível o gerenciamento de acesso de recursos do seu app como por exemplo, Câmera e geolocalização.

WWDC 2020

WWDC é a conferência anual de desenvolvimento da Apple, nesse ano tivemos o anuncio do iOS14 e as nova versão do safari, já no lado da Apple não temos nenhum momento o uso do termo PWA parece algo proibido internamente, mas com a exclusão do termo tivemos melhorias relacionadas Progressive Web App no safari e para apps de terceiros.

Por exemplo, apps iOS anteriormente não tinham acesso na webview acesso a service worker, como a nova versão do WKWebView do iOS isso será possível. Isso ajudará apps como Chrome e Firefox iOS implementarem o suporte a recursos que dependem do service Worker.

No Safari a versão 14 temos os seguintes items:

Também foram anunciadas melhorias referentes a CSS, Web Animations, Web Components, SVG e finalmente o suporte a WebP. Durante a apresentação sobre as novidades para desenvolvedores web, também mencionaram novidades já lançadas esse ano como Clipboard Async API.

Mas Pessoalmente o anuncio mais significante da semana foi O Anuncio de Jen Simmons como Web Technologies Evangelist, ela fez um trabalho maravilhoso na Mozilla e cria um pouco de esperança que o time do webkit priorize a padronização de alguns recurso no webkit.

Mais informações sobre os anúncios do webkit/safari https://developer.apple.com/news/?id=e4u1mtfu

web.dev Live

Esse ano não tivemos o Google I/O principal conferência para desenvolvedores do Google. Mas no final do mês de junho teremos o web.dev live evento focado totalmente em conteúdo web. Teremos algumas palestras relacionadas a PWA estou bastante ansioso para esse evento ele acaba fechando o ciclo de eventos das principais Empresas de tecnologias.

web.dev Live 2020

Mais conteúdo sobre PWA acesse a página do curso.

Categorias
PWA - Progressive web apps

Armazenamento de dados no Browser

Armazenamento de dados no Browser ou client-side é algo possível por diferentes APIs, no passado tivemos o uso de Cookies agora temos localStorage, sessionStorage, IndexedDB e CacheStorage API. Sem uso de back-end ou bibliotecas externas, suporte direto do Browser.

Nesse post iremos entender a diferença entre os tipos de armazenamento de dados no Browser e quando utiliza-los.

Cookies

Por muitos e muitos anos cookies foram utilizados para persistir dados na web, com o passar dos anos esse recurso evoluiu e passou por algumas melhorias mas também criou problemas relacionados a privacidade. Nos últimos anos os principais navegadores passaram a implementar restrições ao uso de Cookies. Recentemente Chrome, Firefox e Edge implementaram restrições de uso de cookies em aplicações com domínios diferentes.

Já é público por parte do time do Chrome a descontinuação de suporte ao uso de Cookies.

LocalStorage e SessionStorage

Posso fala que é o modo mais simples de armazenamento e dados na máquina do usuário. Introduzidos no HTML5 ambas permitem o armazenamento de texto no browser do usuário com um limite de até 5MB na maioria dos Browsers

SessionStorage

Como o nome sugere permite o armazenamento de dados na sessão do usuário(aba), quando a aba for fechada os dados serão removidos. Ela pode ser utilizadas em conjunto com outras API, com o armazenamento de dados temporário

LocalStorage

Sua utilização é simples mas atualmente seu uso é pouco recomendado já que a IndexedDB disponibiliza mais recursos para armazenamento a longo prazo. Além disso a localStorage possuí suas limitações:

  • Sua execução é síncrona
  • Tem uma limitação de armazenamento 5mb
  • E não possui acesso por service workers e web workers

IndexedDB

Banco de dados não relacional(noSQL) que realiza transações assíncronas. Com a IndexedDB conseguimos armazenar objetoos e realizar consultas através de índices. Com IndexedDB conseguimos trabalhar com, objetos, texto, arquivos e blobs.

Diferente da LocalStorage a IndexedDB possui acesso por web workers e service workers, nesse caso sendo um item crucial para armazenamento de dados offline em uma progressive web app.

Umas das desvantagens da IndexedDB é a complexidade de se trabalhar com essa API, seu comportamento pode ser simplificado por libs externas além da adição de recursos extras como, Promises.

CacheStorage API

A Cache API é um sistema de armazenamento de requisições e respostas realizadas a rede. Podemos armazenar requisições realizadas durante a execução da aplicação ou podemos armazenar requisições em background somente para uso de dados.

A API foi criada para permitir que service workers realizassem cache de requisições feitas a rede para permitir respostas rápidas ou reduzir um impacto na latências de conexões de dispositivos móveis.

O que podemos armazenar com cacheStorage API?

Apenas Requisições e respostas HTTP. Entretanto essas requisições podem conter qualquer tipo de dados transferido sobre o protocolo HTTP, por exemplo:

  • Imagens
  • Páginas
  • JavaScript
  • Arquivos CSS
  • Objetos JSON

Utilizando armazenamento de dados em uma PWA

Numa recomendação geral podemos classificar da seguinte forma:

  • Para recursos da aplicação como arquivos: Cache API Storage
  • Para dados e consulta: IndexedDB

Porque não é recomendado o uso de SessionStorage ou LocalStorage para PWAs? Simplesmente pela limitação de ter acesso através de service workers mas o recurso pode se utilizado como um fallback para navegadores que não possuem suporte a Service Worker.

Quanto eu posso armazenar?

Essa informação pode variar por navegadores mas em geral estamos falando de centenas de megabytes, para uma aplicação Web é bastante.

  • Chrome permite utilizar 60% do total do espaço em disco para diferentes domínios e APIs
  • Internet Explorer 10 permite 250MB no total mas irá avisar o usuário quando a aplicação utilizar mais de 10BM
  • Firefox tem um limite de 2GB, mas você pode determinar quanto espaço você terá disponível com a StorageManager API
  • Safari irá permitir até 1GB e irá requisitar permissão ao usuário quando atingir 200MB.

Esse post serviu como uma introdução mas já tinha feito um post no canal sobre LocalStorage a alguns anos você pode conferir aqui: LocalStorage: Armazenando dados com JavaScript

Categorias
PWA - Progressive web apps Tutoriais

Geolocation API

Vamos falar sobre geolocation esse post servirá como base para o conteúdo do curso de PWA que estou rodando no meu canal. Na versão 2020 do curso iremos abordar Web APIs disponíveis para iOS e nesse primeiro posts iremos abordar a Geolocation web API.

Umas das primeiras web APIs lançadas junto com o HTML5, para termos noção esse recurso estava disponível na versão 5 do Chrome e Safari em 2010. A Geolocation API possui uma série de funções e eventos que permitem a localização do usuário através doso dados: latitude e longitude.

Gráfico do website can I use mostrando o suporte a Geolocation API
Relatório do “can I use”

Suporte

Esse recurso é totalmente dependente do device do usuário vimos que o recurso é presente 94% dos browsers que suporte alto. Mas a sua precisão depende que o device que possua GPS, caso contrário essa informação será gerada através de cruzamento de dados, por exemplo, rede e endereço IP. Isso significa se o device não possuir suporte essa informações não serão tão precisas.

GPS

Outro ponto importante é o consumo de bateria, para o os dispositivos móveis o acesso ao GPS causa um custo maior de processamento assim aumenta o consumo de bateria, então é importante fazer o uso consciente desse recurso.

Privacidade

Na maioria dos devices esse recurso é dependente de uma conexão HTTPS, também antes do acesso a esse recurso o browser irá requisitar permissão de acesso a Geolocalização do usuário. Como podemos ver na imagem a seguir:

screenshot da tela do usuário quando o aplicativo requisita permissão para acessar a localização do usuário
Exemplo pop up de permissão da apple

Ideal caso armazene ou monitore a movimentação do usuário deixe claro o uso desse recurso. Esse recurso também só será acessado em uma aplicação que estiver rodando em um servidor web.

Código

Depois de uma introdução sobre a API agora vamos ver como utiliza-la em nossa aplicação:

navigator.geolocation.getCurrentPosition(geoSuccess, geoError); const geoSuccess = (position) => { console.log(position.coords.latitude, position.coords.longitude); }; const geoError = (error) => { console.log('Error occurred. Error code: ' + error.code); };
Code language: JavaScript (javascript)

No código acima fazem a chamada da api dentro de navigator, podemos conferir se o browser tem suporte a geolocation verificando se o atribulo é nulo mas em 2020 a api está presente na maioria dos browsers então não vou me preocupar como esse ponto.

Chamamos a função get position responsável por requisitar a latitude e longitude do usuário. Ela recebe como parâmetro duas funções uma parar executar caso sucesso ou falha de acesso ao recurso.

Se a requisição for bem sucedida teremos a chamada da função geoSuccess com o retorno da latitude(position.coords.latitude) e longitude(position.coords.longitude). Em caso de erro teremos a chamada da função geoError com os possíveis códigos:

  • 0: erro desconhecido
  • 1: permissão negada
  • 2: erro na resposta da fonte da informação
  • 3: timed out

Essa é uma introdução ao assunto em breve estarei postando um vídeo sobre o assunto em meu canal. Qualquer dúvida deixe um comentário ou continue lendo mais tutoriais na página da categoria.

Referência: MDN Geolocation API

Veja também as aulas sobre o assunto no curso de PWA

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
Code language: PHP (php)

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
Code language: CSS (css)

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" };
Code language: JavaScript (javascript)

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', }); }
Code language: JavaScript (javascript)

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()   ] };
Code language: JavaScript (javascript)

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>
Code language: HTML, XML (xml)

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)); })
Code language: PHP (php)

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); }) ); });
Code language: PHP (php)

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); }) ); });
Code language: PHP (php)

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; }) }) ); });
Code language: PHP (php)

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);
Code language: JavaScript (javascript)

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; }) }) ); });
Code language: JavaScript (javascript)

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'); }) ); });
Code language: PHP (php)

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.