118 |
119 |
120 |
121 |
122 |
215 |
216 |
217 |
--------------------------------------------------------------------------------
/pwa-intro.md:
--------------------------------------------------------------------------------
1 | # Introdução aos PWA's
2 |
3 | Um Progressive Web App (PWA) é uma aplicação que usa recursos modernos da Web para fornecer uma experiência de aplicativo nativo aos usuários. Esses aplicativos atendem a determinados requisitos, são implantados em servidores, acessíveis por meio de URLs e indexados por mecanismos de pesquisa.
4 |
5 | Segundo o google os PWA's são:
6 | experiências de usuários que têm o alcance da Web e são:
7 |
8 | * Confiáveis - Carregue instantaneamente e nunca mostre o downasaur, mesmo em condições de rede incertas.
9 |
10 | * Rápido - responda rapidamente às interações do usuário com animações suaves e sem rolagem.
11 |
12 | * Engajável - Sinta-se como em um aplicativo nativo no dispositivo, com uma experiência de usuário imersiva.
13 |
14 | Esse novo nível de qualidade permite que os Progressive Web Apps ganhem um lugar na tela inicial do usuário.
15 |
16 | Seguindo uma definição teórica do que seriam as PWA's
17 |
18 | > Os Progressive Web Apps são um conjunto de técnicas para desenvolver aplicações web, adicionando progressivamente funcionalidades que antes só eram possíveis em apps nativos.
19 |
20 | ## Requisitos obrigatórios para você ter um PWA
21 |
22 | * Progressivo - Trabalho para todos os usuários, independentemente da escolha do navegador, porque eles são criados com aprimoramento progressivo como um princípio central.
23 |
24 | * Responsivo - Adaptável a qualquer tipo de tela, desktop, celular, tablet e etc.
25 |
26 | * Conectividade independente - Aprimorado com os [Services Workers](https://developers.google.com/web/fundamentals/primers/service-workers/) para trabalhar offline ou em redes de baixa qualidade.
27 |
28 | * App-like - Ter um comportamento igual ao de uma aplicação nativa, tanto na navegação quanto na interação.
29 |
30 | * Atualizado - não é necessário baixar atualizações do aplicativo, o browser simplesmente irá detectar e atualizar automaticamente, caso necessário.
31 |
32 | * Seguro - veiculado via HTTPS para evitar espionagem e garantir que o conteúdo não tenha sido adulterado.
33 |
34 | * Detectável - São identificáveis como “aplicativos” graças aos manifestos do W3C e ao service workers registration, permitindo que os mecanismos de pesquisa os encontrem.
35 |
36 | * Reengajável - Facilite o reengajamento por meio de recursos como notificações por push.
37 |
38 | * Instalável - permite que os usuários “mantenham” os aplicativos que consideram mais úteis em sua tela inicial sem o incômodo de uma loja de aplicativos.
39 |
40 | * Linkable - Compartilhe facilmente via URL e não requeira instalação complexa.
41 |
42 | Podemos deduzir então que, se antes somente os aplicativos nativos tinham: push notifications, funcionamento offline, geolocalização e ícone na home screen, agora podemos usar tudo isso em uma aplicação 100% web.
43 |
44 | ## Por que construir um PWA ?
45 |
46 | Segundo o site [Developers Google](https://developers.google.com/web/progressive-web-apps/):
47 |
48 | > A criação de um Progressive Web App de alta qualidade traz benefícios incríveis, facilitando a satisfação de seus usuários, o aumento do engajamento e o aumento de conversões.
49 |
50 | * Digno de estar na tela inicial - Quando os critérios do Progressive Web App são atendidos, o Chrome solicita que os usuários adicionem o Progressive Web App à sua tela inicial.
51 |
52 | * Trabalhe de forma confiável, independentemente das condições da rede - Serivce Workers permitiram que o Konga enviasse 63% menos dados para carregamentos iniciais de página e 84% menos dados para concluir a primeira transação!
53 |
54 | * Maior engajamento - As web push notifications ajudaram a eXtra Electronics a aumentar o engajamento em 4 vezes. E esses usuários gastam o dobro do tempo no site.
55 |
56 | * Conversões melhoradas - A capacidade de proporcionar uma experiência incrível ao usuário ajudou o AliExpress a aumentar as conversões para novos usuários em todos os navegadores em 104% e no iOS em 82%.
57 |
58 | Com isso podemos perceber com esses cases descritos que a retenção se torna muito melhor quando construímos um PWA. Isso acontece por tirarmos muitos passos para que usuário possa de fato usar sua aplicação, não sendo mais necessário ir para uma store, pesquisar pela aplicação, baixar e só depois interagir.
59 |
--------------------------------------------------------------------------------
/pwa-notes.md:
--------------------------------------------------------------------------------
1 | # Pontos a considerar
2 |
3 | No final de março de 20'8 tivemos uma posição da Apple em relação aos PWA's no iOS, ela resolveu adotar, mas ainda está engatinhando, não é algo tão dinâmico como no chrome onde ele sugere o usuário a adicionar seu WebApp à tela principal. A maçã resolveu dificultar um pouco mais as coisas e só é possível a instalação através do safari.
4 |
5 | Podemos visualizar o suporte aos [Services Workers](https://caniuse.com/#feat=serviceworkers), no site [can i user](https://caniuse.com). Verificamos que nos navegodores mais usados, já temos suporte aos service workers e temos que pesar o nicho qual a prioridade de plataformas para o negócio, para tomarmos a decisão de usar ou não essa tecnologia.
--------------------------------------------------------------------------------
/pwa-service-workers.md:
--------------------------------------------------------------------------------
1 | # O que são os servicers workers
2 |
3 | Um service worker é um script que seu navegador executa em segundo plano, separado da página da Web, possibilitando recursos que não precisam de uma página da Web ou de interação do usuário. Atualmente, os service workers já incluem recursos como notificações push e sincronização em segundo plano. No futuro, os service workers permitirão outras ações como sincronização periódica ou geolocalização. O principal recurso discutido neste tutorial é a capacidade de interceptar e tratar solicitações de rede, incluindo o gerenciamento programático de um cache de respostas.
4 |
5 | Essa API é muito interessante porque permite experiências off-line, oferecendo aos desenvolvedores controle total sobre a experiência.
6 |
7 | Antes do service worker, havia outra API que proporcionava aos usuários uma experiência off-line na Web, denominada AppCache. Os principais problemas com AppCache são o número de armadilhas que existem, bem como o fato de que enquanto o design funciona particularmente bem para apps da Web de uma única página, ele não é tão bom em sites de várias páginas. Os service workers forma projetados para eliminar esses pontos negativos comuns.
8 |
9 | Características importantes de um service worker:
10 |
11 | É um JavaScript Worker. Portanto, não consegue acessar o DOM diretamente. Em vez disso, um service worker pode se comunicar com as páginas que controla respondendo a mensagens enviadas pela interface postMessage. Essas páginas podem manipular o DOM, se necessário.
12 | O service worker é um proxy de rede programável, permitindo controlar como as solicitações de rede da página são tratadas.
13 | Ele é encerrado quando ocioso e reiniciado quando necessário novamente. Isso significa que não se pode confiar no estado global dentro dos gerenciadores onfetch e onmessage de um service worker. Para informações que devem ser persistidas e reutilizadas entre reinícios, os service workers podem acessar a IndexedDB API.
14 | Os service workers fazem uso intensivo de promessas. Se você não está familiarizado com promessas, interrompa esta leitura e confira Promessas, uma Introdução.
15 | O ciclo de vida do service worker
16 | Um service worker tem um ciclo de vida totalmente separado da página da Web.
17 |
18 | Para instalar um service worker no site, é necessário registrá-lo, o que pode ser feito no JavaScript da página. O registro de um service worker faz com que o navegador inicie a etapa de instalação do service worker em segundo plano.
19 |
20 | Durante a etapa de instalação, normalmente alguns ativos estáticos são armazenados em cache. Se todos os arquivos forem armazenados em cache corretamente, o service worker estará instalado. Se houver falha no download e no armazenamento em cache de qualquer arquivo, a etapa de instalação falhará e o service worker não será ativado (ou seja, não será instalado). Se isso ocorre, não se preocupe. Haverá uma nova tentativa na próxima vez. Mas isso significa que, se instalado, você sabe que os ativos estáticos estão no cache.
21 |
22 | Após a instalação, a próxima etapa é a ativação e é uma ótima oportunidade para lidar com o gerenciamento de caches antigos, o que veremos na seção de atualização do service worker.
23 |
24 | Depois da etapa de ativação, o service worker controlará todas as páginas dentro de seu escopo, embora a página que registrou o service worker pela primeira vez não será controlada até ser carregada novamente. Quando o service worker assumir o controle, estará em um de dois estados: encerrado, para economizar memória, ou tratando eventos de recuperação e mensagem gerados pela página quando faz uma solicitação ou mensagem de rede.
25 |
26 | Veja a seguir uma versão muito simplificada do ciclo de vida do service worker em sua primeira instalação.
27 |
28 | ciclo de vida do service worker
29 |
30 | Pré-requisitos
31 | Compatibilidade de navegadores
32 | O número de opções de navegador está crescendo. Service workers são compatíveis com Firefox e Opera. O Microsoft Edge já está demonstrando suporte público. Até mesmo o Safari já divulgou alguns sinais sobre o desenvolvimento futuro. Você pode acompanhar o andamento de todos os navegadores no site is Serviceworker ready {: .external } de Archibald.
33 |
34 | Você precisa de HTTPS
35 | Durante o desenvolvimento, você poderá usar service workers por meio do localhost. No entanto, para implantá-lo em um site, será necessário ter o HTTPS instalado no servidor.
36 |
37 | O uso de um service worker permite sequestrar conexões, bem como fabricar e filtrar respostas. É uma ferramenta muito poderosa. Você pode usar esses recursos por um bom motivo, mas um intermediário não autorizado pode querer fazer algo diferente. Para evitar que isso aconteça, só é possível registrar service workers em páginas servidas usando HTTPS. Assim, sabemos que o service worker recebido pelo navegador não foi adulterado durante sua jornada pela rede.
38 |
39 | As páginas do Github são servidas usando HTTPS. Portanto, são um local perfeito para hospedar demonstrações.
40 |
41 | Se você quiser adicionar HTTPS ao servidor, será necessário obter um certificado TLS e configurá-lo no servidor. Esse processo varia de acordo com a configuração. Portanto, verifique a documentação do servidor e não deixe de conferir o gerador de configurações SSL do Mozilla para obter as práticas recomendadas.
42 |
43 | Registrar um Service Worker
44 | Para instalar um service worker, você precisa iniciar o processo registrando-o em sua página. Isso informa ao navegador onde reside o arquivo JavaScript do service worker.
45 |
46 | if ('serviceWorker' in navigator) {
47 | window.addEventListener('load', function() {
48 | navigator.serviceWorker.register('/sw.js').then(function(registration) {
49 | // Registration was successful
50 | console.log('ServiceWorker registration successful with scope: ', registration.scope);
51 | }).catch(function(err) {
52 | // registration failed :(
53 | console.log('ServiceWorker registration failed: ', err);
54 | });
55 | });
56 | }
57 | Este código verifica a disponibilidade da API de service worker. Se disponível, o service worker em /sw.js é registrado quando a página está carregada.
58 |
59 | Você pode chamar register() todas as vezes que uma página é carregada, sem se preocupar com isso. O navegador saberá se o service worker já está registrado ou não e se comportará adequadamente.
60 |
61 | Um ponto sutil do método register() é a localização do arquivo do service worker. Neste caso, você notará que o arquivo do service worker está na raiz do domínio. Isso significa que o escopo do service worker será a origem completa. Em outras palavras, este service worker receberá eventos fetch para tudo nesse domínio. Se registrarmos o arquivo do service worker em /example/sw.js, ele verá apenas os eventos fetch das páginas com URL iniciando com /example/ (ou seja, /example/page1/, /example/page2/).
62 |
63 | Agora, você já pode verificar se o service worker está ativado acessando chrome://inspect /#service-workers e procurando o seu site.
64 |
65 | Inspecionar service workers
66 |
67 | Na primeira implementação dos service workers, também era possível ver seus detalhes por meio de chrome://serviceworker-internals. Isso ainda pode ser útil, pelo menos para compreender o ciclo de vida dos service workers, mas não se surpreenda se esse recurso for substituído totalmente por chrome://inspect/#service-workers posteriormente.
68 |
69 | Pode ser útil testar o service worker em uma janela anônima, permitindo que você feche e abra janelas novamente sabendo que o service worker anterior não afetará a nova janela. Todos os registros e caches criados em uma janela anônima são eliminados quando a janela é fechada.
70 |
71 | Instalar um service worker
72 | Depois que uma página controlada inicia o processo de registro, vamos mudar para o ponto de vista do script do service worker, que trata o evento install.
73 |
74 | No exemplo mais básico, é necessário definir um retorno de chamada para o evento de instalação e decidir os arquivos que serão armazenados no cache.
75 |
76 | self.addEventListener('install', function(event) {
77 | // Perform install steps
78 | });
79 | Dentro do nosso retorno de chamada do install, precisamos executar as etapas a seguir:
80 |
81 | Abra um cache.
82 | Armazene os arquivos em cache.
83 | Confirme se todos os ativos necessários estão armazenados no cache.
84 | var CACHE_NAME = 'my-site-cache-v1';
85 | var urlsToCache = [
86 | '/',
87 | '/styles/main.css',
88 | '/script/main.js'
89 | ];
90 |
91 | self.addEventListener('install', function(event) {
92 | // Perform install steps
93 | event.waitUntil(
94 | caches.open(CACHE_NAME)
95 | .then(function(cache) {
96 | console.log('Opened cache');
97 | return cache.addAll(urlsToCache);
98 | })
99 | );
100 | });
101 | Aqui podemos ver que chamamos caches.open() com o nome do cache desejado e depois cache.addAll(), passando nossa matriz de arquivos. Essa matriz é uma cadeia de promessas (caches.open() e cache.addAll()). O método event.waitUntil() recebe uma promessa e a usa para saber o tempo de instalação e se foi a instalação bem-sucedida.
102 |
103 | Se todos os arquivos forem armazenados no cache corretamente, o service worker estará instalado. Se o download de qualquer dos arquivos falhar, a etapa de instalação também falhará. Isso permite confiar na disponibilidade de todos os ativos definidos, mas também significa que você precisa ser cuidadoso com a lista de arquivos que quer armazenar em cache na etapa de instalação. Se a lista de arquivos for longa, aumentará a chance de falha no armazenamento em cache de um dos arquivos, impedindo a instalação do service worker.
104 |
105 | Esse é apenas um exemplo. Você pode executar outras tarefas no evento install ou até evitar configurar um evento install.
106 |
107 | Cache e solicitações de retorno
108 | Agora que instalou um service worker, você provavelmente quer retornar uma das respostas armazenadas em cache, certo?
109 |
110 | Depois que um service worker é instalado e o usuário navega para uma página diferente ou atualiza a página, o service worker começa a receber eventos fetch. Veja um exemplo a seguir.
111 |
112 | self.addEventListener('fetch', function(event) {
113 | event.respondWith(
114 | caches.match(event.request)
115 | .then(function(response) {
116 | // Cache hit - return response
117 | if (response) {
118 | return response;
119 | }
120 | return fetch(event.request);
121 | }
122 | )
123 | );
124 | });
125 | Aqui, definimos nosso evento fetch e, em event.respondWith(), passamos uma promessa de caches.match(). Esse método examina a solicitação e encontra todos os resultados armazenados em qualquer um dos caches criados pelo service worker.
126 |
127 | Se tivermos uma resposta correspondente, retornaremos o valor do cache. Caso contrário, retornaremos o resultado de uma chamada para fetch, que criará uma solicitação de rede e retornará os dados se algo for recuperado da rede. Esse é um exemplo simples e usa todos os ativos armazenados em cache durante a etapa da instalação.
128 |
129 | Se quisermos armazenar novas solicitações em cache de forma cumulativa, poderemos fazê-lo tratando a resposta da solicitação de recuperação e adicionando-a ao cache, como mostrado a seguir.
130 |
131 | self.addEventListener('fetch', function(event) {
132 | event.respondWith(
133 | caches.match(event.request)
134 | .then(function(response) {
135 | // Cache hit - return response
136 | if (response) {
137 | return response;
138 | }
139 |
140 | // IMPORTANT: Clone the request. A request is a stream and
141 | // can only be consumed once. Since we are consuming this
142 | // once by cache and once by the browser for fetch, we need
143 | // to clone the response.
144 | var fetchRequest = event.request.clone();
145 |
146 | return fetch(fetchRequest).then(
147 | function(response) {
148 | // Check if we received a valid response
149 | if(!response || response.status !== 200 || response.type !== 'basic') {
150 | return response;
151 | }
152 |
153 | // IMPORTANT: Clone the response. A response is a stream
154 | // and because we want the browser to consume the response
155 | // as well as the cache consuming the response, we need
156 | // to clone it so we have two streams.
157 | var responseToCache = response.clone();
158 |
159 | caches.open(CACHE_NAME)
160 | .then(function(cache) {
161 | cache.put(event.request, responseToCache);
162 | });
163 |
164 | return response;
165 | }
166 | );
167 | })
168 | );
169 | });
170 | O que estamos fazendo é:
171 |
172 | Adicionar um retorno de chamada a .then() na solicitação fetch.
173 | Após obter uma resposta, executar as seguintes verificações:
174 |
175 | Verificar se a resposta é válida.
176 |
177 | Verificar se o status da resposta é 200.
178 | Verificar se o tipo de resposta é basic, o que indica que é uma solicitação de nossa origem. Isso significa que solicitações de ativos de terceiros não são armazenadas no cache.
179 | Se todas as verificações forem bem-sucedidas, clonaremos a resposta. O motivo para isso é que, como a resposta é um Stream, o corpo poderá ser consumido apenas uma vez. Como queremos retornar a resposta para uso pelo navegador, bem como passá-la para uso pelo cache, precisamos cloná-la para podermos enviá-la ao navegador e ao cache.
180 | Atualizar um Service Worker
181 | Em um determinado momento, será necessário atualizar o service worker. Quando isso ocorrer, siga estas etapas:
182 |
183 | Atualize o arquivo JavaScript do service worker. Quando o usuário navegar para o site, o navegador tentará baixar novamente o arquivo de script que definiu o service worker em segundo plano. Mesmo que apenas um byte seja diferente entre o arquivo do service worker e a versão carregada no momento, o navegador considerará que há um novo service worker.
184 | O novo service worker será iniciado e o evento install será acionado.
185 | Nesse momento, o service worker anterior ainda estará controlando as páginas atuais. Portanto, o novo service worker entrará em um estado waiting.
186 | Quando as páginas do site abertas nesse momento forem fechadas, o service worker anterior será finalizado e o novo assumirá o controle.
187 | Quando o novo service worker assumir o controle, o evento activate será acionado.
188 | Uma tarefa comum que ocorre no retorno de chamada activate é o gerenciamento do cache. O motivo para fazer isso no retorno de chamada activate é que, se for necessário apagar caches antigos na etapa de instalação, eventuais service workers antigos, que controlam todas as páginas atuais, não poderão mais servir arquivos usando esses caches.
189 |
190 | Vamos supor que temos um cache chamado 'my-site-cache-v1' e decidimos dividi-lo em um cache para páginas e outro para postagem do blog. Isso significa que a etapa de instalação criará dois caches, 'pages-cache-v1' e 'blog-posts-cache-v1' e a etapa de ativação excluirá o cache antigo 'my-site-cache-v1'.
191 |
192 | O código a seguir faz isso em loop que percorre todos os caches do service worker, excluindo os que não estão definidos na lista de permissões do cache.
193 |
194 | self.addEventListener('activate', function(event) {
195 |
196 | var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];
197 |
198 | event.waitUntil(
199 | caches.keys().then(function(cacheNames) {
200 | return Promise.all(
201 | cacheNames.map(function(cacheName) {
202 | if (cacheWhitelist.indexOf(cacheName) === -1) {
203 | return caches.delete(cacheName);
204 | }
205 | })
206 | );
207 | })
208 | );
209 | });
210 | Problemas e pontos a melhorar
211 | Esses recursos são realmente novos. Veja a seguir uma relação de problemas que pode atrapalhar o uso desses recursos. Esperamos que esta seção seja excluída em breve. Por enquanto, é bom considerar os problemas a seguir.
212 |
213 | Se uma instalação falhar, ainda não fornecemos informações suficientes sobre a falha
214 | Se um worker for registrado, mas não aparecer em chrome://inspect/#service-workers ou chrome://serviceworker-internals, é provável que tenha ocorrido uma falha na instalação devido a um erro ou a uma promessa rejeitada passada para event.waitUntil().
215 |
216 | Para contornar esse problema, acesse chrome://serviceworker-internals, marque a opção "Open DevTools window and pause JavaScript execution on service worker startup for debugging", e coloque uma instrução de depurador no início do evento de instalação. Essa modificação, juntamente com Pausar em exceções não detectadas, deve revelar o problema.
217 |
218 | Os padrões de fetch()
219 | Por padrão, sem credenciais
220 | Quando você usa o fetch, por padrão, a solicitação não terá credenciais, como cookies. Em vez disso, se você quiser credenciais, chame:
221 |
222 | fetch(url, {
223 | credentials: 'include'
224 | })
225 | Esse comportamento é intencional e é provavelmente melhor que o padrão mais complexo do XHR, que é enviar credenciais se o URL for da mesma origem e, caso contrário, omiti-las. O comportamento da recuperação é mais parecido com outras solicitações CORS, como igin>, que nunca enviam cookies, a menos que você aceite usando igin="use-credentials">.
226 |
227 | Por padrão, falha sem CORS
228 | Por padrão, a recuperação de um recurso de um URL de terceiros falhará se não for compatível com CORS. Você pode adicionar uma opção no-CORS à solicitação para evitar a falha, mas isso causará uma resposta "opaca", o que significa que você não saberá se a resposta foi bem-sucedida ou não.
229 |
230 | cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {
231 | return new Request(urlToPrefetch, { mode: 'no-cors' });
232 | })).then(function() {
233 | console.log('All resources have been fetched and cached.');
234 | });
235 | Tratar imagens responsivas
236 | O atributo srcset do elemento seleciona o ativo de imagem mais adequado em tempo de execução e faz uma solicitação de rede.
237 |
238 | Para o service worker, há algumas opções caso seja necessário armazenar uma imagem em cache durante a etapa de instalação:
239 |
240 | Instale todas as imagens solicitadas pelo elemento e pelo atributo srcset.
241 |
242 | Instale uma única versão da imagem em baixa resolução.
243 |
244 | Instale uma única versão da imagem em alta resolução.
245 | Na verdade, recomendamos a opção 2 ou 3 porque o download de todas as imagens seria um desperdício de espaço de armazenamento.
246 |
247 | Vamos supor que você escolha a versão de baixa resolução no momento da instalação, tente recuperar imagens de maior resolução da rede quando a página for carregada e, em caso de falha, faça o fallback para a versão de baixa resolução. Essa é uma opção refinada e elegante, mas há um problema.
248 |
249 | Se tivermos estas duas imagens:
250 |
251 | Densidade de tela Largura Altura
252 | 1x 400 400
253 | 2x 800 800
254 | Em uma imagem srcset, teríamos uma marcação como esta:
255 |
256 | Se estivermos em uma tela 2x, o navegador optará pelo download de image-2x.png. Se estivermos off-line, poderemos capturar essa solicitação com .catch() e retornar image-src.png , se estiver no cache. No entanto, o navegador esperará uma imagem que considere os pixels extras de uma tela 2x. Portanto, a imagem aparecerá como 200x200 pixels CSS em vez de 400x400 pixels CSS. A única forma de contornar isso é definir uma altura e largura fixas na imagem.
257 |
258 | Para elementos usados para direção de arte, isso fica consideravelmente mais difícil e depende em grande parte de como as imagens são criadas e usadas. No entanto, pode ser possível usar uma abordagem similar para srcset.
259 |
260 | referência para posterior lapidação.
--------------------------------------------------------------------------------
/refs.md:
--------------------------------------------------------------------------------
1 | # Referências
2 |
3 | * [pwa-mitos](http://tampa.slides.com/joselitojunior1/pwa-mitos#/50)
4 | * [pwa-launchpad](http://tampa.slides.com/joselitojunior1/pwa-launchpad#/)
5 | * Seems interesting [pwa-amp](https://www.ampproject.org/docs/integration/pwa-amp)
6 | * [Manifest Generator](https://tomitm.github.io/appmanifest/)
7 | * [Toolkit to build easily pwa](https://developers.google.com/web/tools/workbox/)
8 | * [sw miojo](https://joseli.to/seu-web-app-offline-mais-rapido-que-preparo-de-miojo/)
9 | * [Hacker News readers as Progressive Web Apps](https://hnpwa.com/)
10 | * [PWA to all chromes](https://medium.com/@kennethrohde/progressive-web-apps-coming-to-all-chrome-platforms-80e31272e2a8)
11 | * [Update notification with PWA](https://medium.com/progressive-web-apps/pwa-create-a-new-update-available-notification-using-service-workers-18be91618d717)
12 | * [PWA-Rocks](https://pwa.rocks/)
13 | * [show cases with pwa](https://developers.google.com/web/showcase/)
14 | * [Pagar.me Talk #8 PWA](https://www.youtube.com/watch?v=AOWtO51pmHo)
15 | * [Alura live com Sérgio Lopes PWA](https://www.youtube.com/watch?v=uzj3r04FewA)
16 | * [AMP + PWA: De Volta para o Futuro dos Apps](https://www.youtube.com/watch?v=PCiDbVMfeOg)
17 | * [Progressive Web Apps e a evolução da Web - Loiane Groner - FrontInPOA 2017](https://www.youtube.com/watch?v=AW-QR8F4RXs)
18 | * [Progressive Web Apps por Sergio Lopes - Devfest São Paulo 2016](https://www.youtube.com/watch?v=tECW-YJXV1o)
19 | * [Progressive Web Apps, Angular 2, Firebase, Performance e mais... O futuro da Web e Mobile](https://www.youtube.com/watch?v=BHKKx6Zvk0Q)
20 | * [Frontinsampa 2015 - Sergio Lopes: Progressive Web Apps: o melhor da web, "Appficada"APPFICADA"](https://www.youtube.com/watch?v=sH7dlRnuh-k)
21 | * [Segredos não ditos de PWA - Eduardo Matos](https://www.youtube.com/watch?v=z68C1yAaC0I)
22 | * [WebFundamentals](https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/)
23 | * [Hello World](https://medium.com/dev-channel/learn-how-to-build-a-pwa-in-under-5-minutes-c860ad406ed)
24 | * [Progressive Web Apps on iOS are here 🚀](https://medium.com/@firt/progressive-web-apps-on-ios-are-here-d00430dee3a7)
25 | * [Getting started with Progressive Web Apps](https://addyosmani.com/blog/getting-started-with-progressive-web-apps/)
--------------------------------------------------------------------------------
/webapp_test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 | It's not just a hello world
11 |