PWAs: Progressive Web Apps

Loiane Groner

Java, JavaScript + HTML5, Sencha, Cordova/Ionic, Angular, RxJS + all things reactive

Evolução da Web

Sites Estáticos

Sites Dinâmicos

AJAX

Design Responsivo

PWAs

uma nova metodologia de desenvolvimento de software

um Progressive Web App pode ser visto como uma evolução híbrida entre as páginas da web regulares (ou sites) e um aplicativo móvel

Web

Progressiva

10 características

Progressivo

Descobrível

Linkável

Responsivo

App-like

Sempre Atualizado

Instalável

Engajável

Seguro

Independente

Conexão

APIs

  • Service Worker API

  • Cache API

  • Fetch API

  • Notifications API

  • Push API

  • IndexedDB API

  • Promises

Desenvolvendo uma PWA

  1. Web Application Manifest

  1. Web Application Manifest

  2. App Shell

  3. Cache (Service Worker)

manifest.json

{
    "name": "Loiane",
    "short_name": "Loiane",
    icons: [{
        src: "images/touch/icon-96x96.png",
        sizes: "96x96",
        type: "image/png"
    }, {
        src: "images/touch/icon-128x128.png",
        sizes: "128x128",
        type: "image/png"
    }, {
        src: "images/touch/apple-touch-icon.png",
        sizes: "152x152",
        type: "image/png"
    }, {
        src: "images/touch/chrome-touch-icon-192x192.png",
        sizes: "192x192",
        type: "image/png"
    }],
    "start_url": "/index.html",
    "theme_color": "#ffffff",
    "background_color": "#ffffff",
    "display": "standalone",
    "orientation": "portrait"
}

manifest.json II

{
  "short_name": "Users",
  "name": "Users PWA",
  "icons": [
    {
      "src": "icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "scope": "/",
  "start_url": "/index.html",
  "orientation": "portrait",
  "display": "standalone",
  "theme_color": "#ffffff",
  "background_color": "#ffffff"
}

Informamos o browser

Declaração do manifest dentro do <head>

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

Protip #1

Customize o manifest.json

Android, iOS / Safari, Windows

Só adicionar no projeto!

Nem precisa criar o manifest.json na mão!

Verificando no browser...

  1.  

  2. App Shell

  1. Web Application Manifest

  2. App Shell

  3. Cache (Service Worker)

App Shell: "Casca" da aplicação

HTML, CSS, JS

Estático

App Shell: "Casca" da aplicação

HTML, CSS, JS

Estático

Conteúdo Dinâmico

Desenvolvendo o App Shell

<link rel="stylesheet" href="assets/mdl/material.min.css" />
...
<body>
  <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
    <header class="mdl-layout__header">
      <div class="mdl-layout__header-row">
        <span class="mdl-layout-title">Ramdom Users</span>
        <div class="mdl-layout-spacer"></div>
      </div>
    </header>
    <main class="mdl-layout__content">
      <div id="first-load" class="center">
        <!-- svg: ícone dos usuários -->
        <p>Loading users...</p>
      </div>
      <div id="connection-status" class="center"></div>

      <div class="mdl-grid">
        <!-- Conteúdo Dinâmico-->
      </div>

      <div class="center">
        <button onclick="pageEvents.loadMore()" class="mdl-button">
          Load More
        </button>
      </div>
    </main>
  </div>
 ...
</body>

Arquivos estáticos são cacheados

var cacheFiles = [
  'dist/app.js',
  'index.html',
  'assets/mdl/material.min.css',
  'assets/mdl/material.min.js'
];
  1.  

  2.  

  3. Cache (Service Worker)

  1. Web Application Manifest

  2. App Shell

  3. Cache (Service Worker)

"Proxy" client side

Passo 1: Registrar

// Registrar o ServiceWorker na aplicação
if ('serviceWorker' in navigator) {
  navigator.serviceWorker
  .register('/sw.js', {scope:''})
  .then((registration) => {
    // SW registrado!
  });
}

Passo 2: Ciclo de Vida

// http://bit.ly/sw-lifecycle

self.addEventListener('install', (event) => {
  // SW foi baixado e instalado
  // Passo 1 é cachear o App Shell
  // Preparar a app para funcionar offline
});

self.addEventListener('activate', (event) => {
  // SW está instalado e ativo
  // Podemos terminar o setup
  // Ou limpar cache antigo
});

self.addEventListener('fetch', (event) => {
  // Escuta cada evento
  // E faz alguma coisa para cada request
  // feito da app para API server
});

Verificando no browser...

Cachear o App Shell

var CACHE_NAME = 'usersCacheV1';

var CACHE_FILES = [
  'dist/app.js',
  'index.html',
  'assets/js/localForage/localforage.min.js',
  'assets/js/localForage/localforage-getitems.js',
  'assets/js/localForage/localforage-setitems.js',
  'assets/mdl/material.min.css',
  'assets/mdl/material.min.js'
];

self.addEventListener('install', (event) => {
  // abre o cache
  event.waitUntil(
    caches.open(CACHE_NAME)
      // e add arquivos offline no cache
      .then((cache) => {
          return cache.addAll(CACHE_FILES));
      }) 
      // Arquivos já cacheado!
      .then(() => self.skipWaiting())
  );
});

Verificando no browser...

Passo 3: fetch

self.addEventListener('fetch', function(event){
    var requestUrl = new URL(event.request.url);
    var requestPath = requestUrl.pathname;

    // se for imagem, request online
    if(requestPath == imagePath){
        event.respondWith(networkFirstStrategy(event.request));

    // se for request, tenta cache primeiro e depois online
    } else{
        event.respondWith(cacheFirstStrategy(event.request));
    }
});

Estratégias: Cache first ou Online first

Estratégias

function cacheFirstStrategy(request){
    return caches.match(request).then(function(cacheResponse){
        return cacheResponse || fetchRequestAndCache(request);
    });
}

function networkFirstStrategy(request){
    return fetchRequestAndCache(request).catch(function(response){
        return caches.match(request);
    });
}

function fetchRequestAndCache(request){
    return fetch(request).then(function(networkResponse){
        caches.open(getCacheName(request)).then(function(cache){
            cache.put(request, networkResponse);
        });
        return networkResponse.clone();
    });
}

Pro tip #2

Exemplo: webpack

let plugins = [
    new WorkboxPlugin({
      globDirectory: DIST_DIR,
      globPatterns: ['**/*.{html,js,css,json,png}'],
      swDest: path.join(DIST_DIR, 'sw.js')
    })
  ],
  ...;
const workboxSW = new WorkboxSW();
const networkFirst = workboxSW.strategies.networkFirst();
workboxSW.router.registerRoute('/users', networkFirst);

App precisa estar preparada

  fechData() {
    const me = this;
    me.page++;
    return new Promise(function(resolve, reject) {
      fetch(me.getUrlRequest())
        .then(function(response) {
          return response.json();
        })
        .then(function(data) {
          me.clientStorage
            .addUsers(data.results, me.page, me.resultsQtd).then(function() {
              data.results.forEach(me.preCacheUserDetails);
              resolve('The connection is OK, showing results from server');
            });
        })
        .catch(function(e) {
          resolve('No connection, showing offline results');
        });
      setTimeout(function() {
        resolve('Bad connection, showing offline results');
      }, 3000);
    });
  }

IndexedDB

Modo Offline

Protip #3

Lighthouse

Transforme seu produto PROGRESSIVAMENTE

Transforme seu produto PROGRESSIVAMENTE

APIs WEB!

Obrigada!

Links e referências

Progressive Web Apps e a evolução da Web

By Loiane Groner

Progressive Web Apps e a evolução da Web

Vamos desbravar o mundo das PWAs (Progressive Web Apps) e entender o que podemos fazer hoje na web! Há 5 anos, não podíamos tirar foto com a câmera usando o browser, e nem enviar notificações push. Hoje já podemos usar APIs com essas funcionalidades graças à evolução da web! Vamos entender o que são as PWAs e como podemos trazer a experiência de aplicações mobile para web, além de entender o que precisamos fazer para transformar aplicações web existentes em aplicações progressivas (ou como criar uma do zero). Como exemplo, usaremos o bom e velho JavaScript (ES2015+) com dicas de ferramentas para facilitar o nosso trabalho!

  • 2,482