Progressive Web Apps

Progressive Web App
is PoWAh

Las PWA no son una tecnología o especificación concreta, sino más bien una filosofía de desarrollo web que se apoya en varias tecnologías web
Qué es exactamente una PWA?

Los tres pilares

Confiable

  • Servida por HTTPS
  • Soporte Offline
  • Caché controlada al 100% con Cache API

Rápida

  • Carga instantánea sin importar estado de red
  • Animaciones y scroll fluidos
  • .

Atractiva

  • Feeling nativo
  • Notificaciones push
  • Instalable desde la web

Capa de aplicación vs contenido

Dado que este tipo de web está más cerca de las aplicaciones nativas, debemos pensar en ella como tal. Separamos la capa de aplicación de la de contenido. Explicar un poco la estrategia.

Service worker

Web workers

  • Ofrecen una interfaz para ejecutar código en hilos en segundo plano.
  • Comunicación mediante mensajes.
  • Acceso limitado a las APIs de JS (y sin acceso al DOM).
  • Útil para trabajo que requiera cálculo o trabajo intensivo evitando afectar a la interfaz.
  • Nuevas características: Shared array buffer...
hablamos de Web workers, no de service workers!!

Service workers

Es un tipo especial de worker dedicado principalmente a controlar la navegación y el acceso a datos en una web.

Es asíncrono, por lo que no hay acceso a XHR síncrono o localStorage.

Ciclo de vida:

  • Registrado
  • Activado
  • Ocioso
  • Eliminado
Siguiente: Vamos a ver como añadir un SW a una web.

Service workers

app.js


if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {

    navigator.serviceWorker.register('/sw.js')
      .then(function(sw) {
        // Registrado!
      })
      .catch(function() {
        // No registrado :(
      });
  });
}
        

Service workers

sw.js


this.addEventListener('install', (event) => {

  event.waitUntil(
    ....
    // Gestión de caché, descarga de recursos de app shell, ...
  );

});

// Suscripción a otros eventos (fetch, activate,...)
        
event.waitUntil hace que se paren el resto de eventos hasta que termine esta tarea Siguiente: Cache API

Cache API

Cache API

  • Ofrece una interfaz para gestionar la caché de una web de forma manual.
  • Es la evolución del Cache Manifest.
  • Está orientado a su uso con Service workers, aunque no es obligatorio.
  • Permite gestionar varias cachés para una misma web.

Cache API

sw.js


const cacheName = 'cache-v1';            // Nombre de caché (para versionar)
const cacheFiles = [...];                // Ficheros de la app shell

this.addEventListener('install', (event) => {

  event.waitUntil(
    caches.open(cacheName)               // Abrimos la caché
      .then( (cache) => {
        return cache.addAll(cacheFiles); // Agregamos todos los ficheros necesarios
      });
  );

});
        
Evento install => Ocurre cuando se instala el SW En una actualización se modifica el nombre de la caché para la nueva. Esta no se usa hasta que el nuevo SW no se activa.

Cache API

sw.js


this.addEventListener('activate', (event) => {

  event.waitUntil(
    caches.keys()                       // Obtenemos todas las claves de caché
      .then( (keyList) => {

        return Promise.all(keyList.map( (key) => {
          if (key !== cacheName) {      // Si no pertenece a la caché nueva ...
            return caches.delete(key);  // ... la borramos
          }
        }));

      });
  );

  return self.clients.claim();    // Para tomar el control de cachés antiguas
});
        
Esto se ejecuta cuando el service worker se activa (por primera instalación o actualización)

Cache API

sw.js


this.addEventListener('fetch', (event) => {
  event.respondWith(

    caches.match(event.request)     // Consultamos si existe, si no, resuelve 'undefined'
      .then( (response) => {
        return response || fetch(event.request); // Devolvemos de la caché o descargamos
      })
      .catch( () => {                            // Si todo falla (no hay red, por ej.)...
        return caches.match('/assets/fallback.jpg');  // Podemos devolver algo por defecto
      });

  );
});
        
El catch es opcional. OJOCUIDAO! El diseño Cache-first necesita mucho control para evitar versiones ad-eternum.

Integración nativa

Application manifest


{
  "name": "My application",
  "short_name": "MyAPP",
  "icons": [{
    "src": "images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, ... ],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}
        

Notificaciones push

La interfaz pushManager


navigator.serviceWorker.ready.then( (serviceWorkerRegistration) => {

  // Opciones (no abligatorias) para subscribe o permissionState
  const opts = {
    userVisibleOnly: true,
    applicationServerKey: 'ABCD1234...'
  }

  // Subscribirse a un servidor Push
  serviceWorkerRegistration.pushManager.subscribe( opts )

  // Obtener suscripción activa
  serviceWorkerRegistration.pushManager.getSubscription()

  // Comprueba el permiso del usuario a usar notificaciones Push
  serviceWorkerRegistration.pushManager.permissionState( opts )
});
        
Todos los métodos devuelven una promesa Solicitamos permisos con Notification.requestPermission(); Opts: userVisibleOnly -> Subscripción push cuyos mensajes generaran efectos visibles por el usuario applicationServerKey -> Clave pública del servidor push

Notificaciones push

Escuchar notificaciones Push

sw.js


self.addEventListener('push', (event) => { // Escuchamos eventos push
  const obj = event.data.json();           // Podemos obtener los datos en json
  console.log(obj);                        // Y hacer con ellos lo que queramos
});
        

Deploy & install

Vuelta al mundo real

Soporte

Escritorio:

  • Google Chrome, Mozilla Firefox, Opera: Ok
  • Microsoft Edge, Apple Safari: Thinking Working
  • .

Móvil:

  • Google Chrome (Android): In love with
  • Mozilla Firefox, Opera: Ok
  • Apple Safari: Thinking Working
La aplicación Twitter Lite parece un WebAPK publicado en la Play Store. Google Chrome 60 en Android tiene webAPK activo por defecto. Apple sólo está trabajando en ServiceWorkers, no ha dejado nada claro del resto de tecnologías

Soporte

Y la cosa irá a mejor...

Aún no tenemos claro qué hará Apple, pero al final tendrá que ceder.

Herramientas y ayudas

Angular Mobile Toolkit

Paquete para crear/convertir una aplicación escrita en Angular en una PWA, basada en plugins y configuración

Elementos clave: Angular ServiceWorker Companion y ngsw-manifest.json

Instalación:


$ > ng new my-pwa
$ > npm install --save @angular/service-worker
$ > ng set apps.0.serviceWorker=true
        
Siguiente: Vamos a ver ejemplos de configuración

Angular Mobile Toolkit (Companion)

push.component.ts


import { NgServiceWorker } from '@angular/service-worker';
...

constructor(public sw: NgServiceWorker) { ... }
...

this.sw.registerForPush({
    applicationServerKey: 'A1B2C3...'
  })
  subscribe( (subscriptionObject) => {
    // ...
  });
        
Permite acceder al ServiceWorker para ejecutar acciones como registrarse a un servicio push o saber si se ha actualizado la PWA.

Angular Mobile Toolkit (Cache plugin)

ngsw-manifest.json


{
  "static": {
    // Autogenerado
    "urls": {
      "/index.html": "ae543...",
      "/app.js": "9ff18...",
      "/logo.png": "0e33a...",
      ...
    }
  },
  "external": {
    "urls": [ { "url": "https://fonts.gstatic.com/Roboto.ttf" }, ... ]
  }
}
        
Tienen métodos más avanzados de caching desde ngsw-manifest, por ejemplo para contenido dinámico, pero parecen estar en desarrollo aún.

Angular Mobile Toolkit (Push plugin)

ngsw-manifest.json


{
  "push": {
    "showNotifications": true,
    "backgroundOnly": false
  }
}
        

Recibe objetos JSON y pasa las propiedades a las opciones de la API showNotification().

Se basa en el plugin Push. Opciones soportadas: "actions", "body", "dir", "icon", "lang", "renotify", "requireInteraction", "tag", "title", "vibrate", "data".

Angular Mobile Toolkit (Custom plugins)

worker-custom.js


import { bootstrapServiceWorker } from '@angular/service-worker/worker';
import { ... } from '@angular/service-worker/plugins/...';
import { MyNGSWPlugin } from './plugins/my-ngsw-plugin';

bootstrapServiceWorker({
 manifestUrl: 'ngsw-manifest.json',
 plugins: [
   // Indicamos los plugins de Angular SW que usaremos
   ...
   MyNGSWPlugin()
 ],
});
        
Antes de nada, extendemos el worker. En versiones anteriores hay que meter código en el build del proyecto para cargar nuestro worker-custom.js

Angular Mobile Toolkit (Custom plugins)

my-ngsw-plugin.js


export function MyNGSWPlugin () {
  return (worker) => new MyNGSWPluginImpl(worker);
}
export class MyNGSWPluginImpl {
  setup (opts) {}

  constructor () {
    self.addEventListener('cualquier-evento-sw', (event) => {
      // Implementamos nuestro listener
    });
  }
}
        
Implementamos nuestro plugin usando self para acceder al SW (como en vanilla). Siguiente: Lighthouse

Lighthouse

Herramienta de Google para analizar el rendimiento de aplicaciones web y dar consejos para mejorarlo, con un apartado centrado en PWA.

Actualmente está metido dentro de las Chrome Developer Tools en la pestaña “Audits”

Mozilla serviceworke.rs

Un auténtico libro de recetas de service workers de la mano de Mozilla que incluye manejo de la Cache API o notificaciones Push:

https://serviceworke.rs/

Is serviceworker ready?

LocalForage

Futuro

Y ahora qué? - Mayor soporte - Gestos - Multiplataforma - Distribución abierta

¿Preguntas?

Gracias