Categorias
AMP Accelerated mobile pages

Convertendo uma aplicação AMP em uma PWA

Agora chegou o momento de conectar os meus dois assuntos favoritos, Progressive Web Apps(PWA) e AMP. Se você ainda não sabe o que é uma PWA podemos definir conjunto de tecnologias que nos permitem criar aplicações web com recursos que antes só possíveis em aplicações nativas se quiser saber mais sobre PWA tenho um curso dedicado em meu canal.

As PWA possui uma série de diferentes API que podem ser aplicadas a diferentes soluções, mas para o caso de nossa aplicação AMP vamos abordar dois items:

  1. Tonar nossa aplicação instalável
  2. Adicionar suporte a versão offline.

Criando um web app manifest para nossa PWA

Primeiro passo vamos criar um web app manifest arquivo json que permite que dispositivos que reconheçam que nossa aplicação pode se instalável. Para isso vamos adicionar em nosso projeto um arquivo chamado manifest.json e nele vamos adicionar o seguinte código:

{
"short_name": "Caipirinha",
"name": "How to prepare the best caipirinha",
"icons": [
{
"src": "./images/capi_icon_192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "./images/capi_icon_512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/index.html",
"background_color": "#00d1c3",
"display": "standalone",
"scope": "/",
"theme_color": "#00d1c3"
}

O código acima descreve as informações que iremos exibir para o usuário quando ele instalar nossa aplicação, que são elas:

  • short_name: o nome que sera exibido na tela inicial do nosso usuário.
  • icons: com uma lista de ícones que iremos exibir em diferentes plataformas.
  • start_url: o endereço nossa aplicação irá carregar quando acessada através do ícone na tela inicial do usuário.
  • background_color: a cor do background da splash screen do nosso app.
  • display: o modo em que nossa PWA será exibida
  • theme_color: as cor do tema da nossa aplicação, por exemplo, barra de controle.

Depois que criarmos nosso arquivo manifest.json temos adicionar uma referência dentro da tag head de nosso index.html ao arquivo de configuração, como podemos ver no código abaixo:

<link rel="manifest" href="/manifest.json">

Adicionando suporte a versão off-line

Agora que já definimos como nosso app será instalado é hora de adicionar a versão off-line de nossa aplicação para isso vamos trabalhar com service workers.

Service Worker um script que é executando por nosso browser em background, separado de nosso DOM onde funciona como proxy acompanhando as requisições no nosso app.

Caso queira saber mais sobre o assunto confira o post sobre service worker.

Mas com esse recurso conseguimos controlar quando nosso app faz requisições a rede e definir estratégias de cache e suporte a versão offline utilizando em conjunto com a cache API.

Atualmente existem bibliotecas que facilitam a implementação desses recursos, por exemplo, Workbox onde também tenho um post mais detalhado sobre o assunto aqui.

Mas AMP vai um passo além do Workbox e abstrai uma série de passos na configuração de um service worker para implantação de estratégias de cache e versão off-line. Isso tudo utilizando AMP Service Worker ele armazena automaticamente os scripts e os documentos de nossa aplicação.

Como criar um service worker com AMP?

Passo 1 – criar o arquivo que irá importar o AMP Service Worker e incializa-lo, para isso vamos criar um arquivo JavaScript chamado sw.js e adicionar os seguinte código:

importScripts('https://cdn.ampproject.org/sw/amp-sw.js');
AMP_SW.init();

Passo 2 – Incluímos o script que irá instalar o AMP Service Worker próximos as scripts que você já carrega em sua aplicação AMP.

<script async custom-element="amp-install-serviceworker" src="https://cdn.ampproject.org/v0/amp-install-serviceworker-0.1.js"></script>

Passo 3 – Adicionamos dentro do corpo da nossa página HTML o componente amp-install-servieworker:

<amp-install-serviceworker src="/sw.js"
           data-iframe-src="install-sw.html"
           layout="nodisplay">
</amp-install-serviceworker>

O componente amp-install-serviceworker irá registrar um service worker definido na propriedade src, dois pontos importante sobre o service worker:

  • Precisa estar na pasta raiz da aplicação
  • Precisa ser servido via HTTPS.

A segunda propriedade é o data-iframe-src é um atributo opicional que será responsável por verificar se o browser possui suporte a service worker e instalar-lo em caso possua.

Passo 4 – o último passo vamos criar o arquivo definida pela propriedade data-iframe-src:

<!doctype html>
<title>installing service worker</title>
<script type='text/javascript'>
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js');
};
</script>

Com esse 4 passos temos nossa primeira versao offline, o AMP Service Worker nos permite definir estratégias de cache para arquivos especificos. Caso queira saber mais sobre estrategias de cache eu escrevi um post aqui.

Vou criar mais um post de como modificar estratégias de cache e exibir uma página específica parar quando os usuários estiverem off-line.

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
JavaScript Tutoriais

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

Esse material faz parte do curso de PWA que estou rodando em meu canal do Youtube, para acompanhar as aulas sigam meu canal ou acessem a página do curso: https://blog.fellyph.com.br/curso-online-progressive-web-apps/

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