Skip to content
Aprende a hacer apps móviles con Ionic 2 

Descubriendo las Progressive Web Apps

Ya tenía ganas de hablar de uno de los hottest topics que hay ahora mismo en mobile: las Progressive Web Apps (PWA). No voy a andarme con rodeos, para mi las PWA son el futuro del desarrollo móvil. Te explicaré por qué:

Básicamente una PWA es como tener una app nativa, pero con los beneficios de la web. Este concepto lo acuñó Google en 2015 para webs que aprovechaban el potencial de los navegadores modernos.

Las PWA igualan a las apps nativas* en temas como funcionamiento offline, velocidad y seguridad, mientras que las superan en otras facetas, como la fácil distribución y la baja fricción de instalación.

*Nota: Al decir “apps nativas” en este artículo, me refiero a todas las apps que requieren instalación en tu smartphone, ya sean nativas o híbridas.
 

Y ahí está la gracia. Créeme, llevo mucho tiempo metido en startups enfocadas al desarrollo de apps y sé de lo que hablo. Las apps tienen una enorme barrera de entrada: el proceso de instalación.

La mayoría de usuarios se descargan unas pocas apps al comprar el móvil y unas pocas más a lo largo de su vida útil. El mero tiempo que lleva instalar una app ya es un freno. Súmale problemas de espacio, temor a los virus, posicionamiento de la app… Convencer a un usuario de que instale y use tu app es muy difícil.

Evidentemente si eres Twitter, Instagram o Candy Crash, éste no es tu problema, pero de lo contrario… empeñarte en tener tu propia app sin contemplar las alternativas puede ser un error muy caro.

La CEO de un e-commerce de éxito me lo comentaba recientemente: “No nos interesa tener una app, el coste de adquisición de usuario es demasiado alto”

Beneficios de las Progressive Web Apps

En contraposición con las apps, las PWA son en realidad webs y acceder a ellas es tan fácil como abrir su enlace, o clickar un resultado de google. Una web está disponible en segundos.

Además de ahorrarte la fricción de instalación, las PWA te dan otras características atractivas, como por ejemplo:

  • funcionamiento offline
  • menor tiempo de respuesta que en web
  • “instalabilidad” (abrirla mediante icono de escritorio)
  • notificaciones push
  • look nativo (eliminar barra de navegación)
  • fácil distribución
  • re-deploy inmediato

¿Que necesita una Web para convertirse en PWA?

Pues básicamente tiene que cumplir estos requisitos:

  • Funcionar offline: Tiene que haber un mínimo de contenido que se guarde para mostrar siempre, incluso sin conexión.
  • Tener un service worker: Es un archivo JS que se ejecuta en background y permite a la página funcionar “más allá” del browser, gestionando entre otras cosas el funcionamiento offline.
  • Tener un manifest file: Permite definir como se va a ver la app (fullscreen, icono de la app para lanzarla desde el escritorio, etc).
  • Funcionar en HTTPS: Dado que el service worker puede manipular el acceso a la red, sólo se permite su uso bajo el protocolo https, para evitar potenciales riesgos de hacking.
  • Funcionar en todos los navegadores: Lo de “progressive” viende de aquí justamente. La idea de las PWA es tener funcionalidades extendidas en los navegadores que lo soportan y funcionar igualmente (sin estas características) en navegadores más antiguos.

Arquitectura de una PWA

Uno de los objetivos de las PWA es el de mejorar su rendimiento, lo que nos fuerza a separar el diseño en:

  • App Shell: Los archivos HTML, CSS y Javascript mínimos para que la interfaz de usuario funcione. Lo que tiene que cargarse inmediatamente.
  • Contenido: El resto de información, aquello que cambia puede cambiar (como los productos de un catálogo) o no es imprescindible.

El objetivo es descargar y cachear la app shell lo primero, para que se cargue de inmediato en las siguientes visitas a la página y en cambio, cargar el contenido de forma dinámica.

appShell vs content

A partir de aquí, lo básico es utilizar el Service Worker para cachear también las llamadas a contenido y desde la app recuperar el contenido cacheado cuando no hay conexión.

El contenido de la llamada inicial no debe estar incrustado en el HTML (nos fastidiaría la caché), sino que tiene que inyectarse desde Javascript.

Archivo de Manifest

El manifest es un simple archivo JSON que incorpora la PWA, con unos campos tal que estos:

{
    "name": "My whole app name",
    "short_name": "short app name",
    "start_url": "/index.html",
    "icons": [  
        {
            "src":"/images/icons/icon-48x48.png",
            "sizes": "48x48",
            "type": "image/png"
        }, 
        "...some more icons..."
    ],
    "background_color": "#27292B",
    "theme_color": "#2eb398",
    "display": "standalone",
    "orientation": "portrait"
}

Aquí es donde defines el nombre de la app (el que se verá debajo del icono si la instalo en el escritorio de mi smartphone), el archivo de entrada, los iconos (recomendable resoluciones habituales de 48×48 px hasta 512×512), el color de fondo (lo usará para la splash screen), el color de la barra de navegación (tema), si tiene que mostrar barra o no cuando se lanza desde el escritorio, e incluso si quieres cambiar la orientación en que se muestra.

Este archivo (lo llamaremos manifest.json), sencillamente se incluye en el index.html del siguiente modo:
<link rel="manifest" href="manifest.json">

Actualmente lo soporta Chrome (y Android), así como Firefox (y IE edge lo está considerando). Para Safari (iOS), hay otro mecanismo para declarar las PWA.

“Manifest” para iOS

Para iOS se pueden declarar una serie de propiedades meta para especificar el icono que debe usar la app si se instala en el escritorio, esconder la barra de navegación, etc. Aquí tienes las principales:

  <meta name="apple-mobile-web-app-capable" content="yes"><!-- hide browser UI -->

  <meta name="apple-mobile-web-app-status-bar-style" content="black-stranslucent"><!-- minimize status bar-->  

  <meta name="apple-mobile-web-app-title" content="my app name"><!-- app name-->  

  <link rel="apple-touch-icon" sizes="60x60" href="images/icons/icon-60x60-ios.png"><!-- define apple-touch-icon links with icons for different sizes-->

¿Qué son los service workers?

Son archivos JS que se ejecutan de forma separada con respecto a tu página web y gestionan eventos lanzados bien por el navegador, bien por tu propia web. No necesitan que tu web esté abierta y no tienen ningún template (o DOM) que mostrar.

Los service workers se sitúan entre el cliente y la red, actuando como un proxy en el lado del cliente. Es decir permiten interceptar las peticiones web de tu página, con lo que se pueden cachear las llamadas localmente.

Otro detalle interesante es que aunque cierres el navegador, se quedan dormidos en background y los puedes despertar a través de acciones, como una push del servidor.

Puedes gestionar tus service workers cuando estás trabajando con ellos desde el Chrome inspector y también desde chrome://serviceworker-internals/

Lo habitual es que los Service Workers utilicen la Web Cache API para cachear tanto los datos de la app shell como los de contenido. En todo caso, te recomiendo mirar la API de Service Workers para ampliar información sobre lo que puedes hacer con ellos.

Registrando un Service Worker

Si el navegador soporta service workers, lo único que tienes que hacer es llamar a su método register con el path al archivo javascript donde está definido. Se hace así:

//app.js

/*... some code ...*/

if('serviceWorker' in navigator){
    navigator.serviceWorker.register('/service-worker.js').then(function(){
        console.log("service worker registered");
    })
}

Declarando un Service Worker

El ciclo de vida de un service worker tiene básicamente 3 estados:

  • Instalación: La primera vez que se ejecuta un service worker. Aquí es donde se cachea la app shell.
  • Activación: Si la fase de instalación se completa con éxito, se pasa a la de activación, donde típicamente borramos la caché obsoleta (imagina que has actualizado tu app shell).
  • En espera: Tras la activación, el service worker se queda a la espera de recibir eventos que gestionar.

Un Service Worker es un simple archivo JS. Te voy a mostrar un ejemplo muy sencillo:

Ejemplo de Service Worker

En primer lugar, le doy un nombre a la cache con la app shell y otro para la caché de contenido. Además, también defino los archivos necesarios para la app shell, y la url base a los contenidos, con el objetivo de diferenciarlos.

/* service-worker.js*/

var cacheName = 'myAppShell-v1';
var dataCacheName = 'myAppData-v1';

var appShellFiles = [
  '/',
  '/index.html',
  '/scripts/app.js',
  '/styles/styles.css',
  '/images/img1.png'
];

var contentURLBase = 'https://www.my-server.com/api';

A partir de aquí, defino el comportamiento durante la fase de instalación. Básicamente, quiero que cachée todos los archivos que necesita la app shell.

/* service-worker.js*/

/* install stage: cache app shell required files*/
self.addEventListener('install', function(e) {
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(appShellFiles);
    })
  );
});

También defino el comportamiento para la fase de activación: Eliminar todos los datos en caché obsoletos. Fíjate que en cacheName y dataCacheName identifico la versión de trabajo. Así, si hago cambios de código, solo con actualizar la versión en estas variables se borrará toda la caché obsoleta.

/* service-worker.js*/

/* activate stage: remove old cache*/
self.addEventListener('activate', function(e) {
  e.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (key !== cacheName && key !== dataCacheName) {
          return caches.delete(key);
        }
      }));
    })
  );
});

Finalmente, escucho al evento fetch (que se lanza cuando la app realiza cualquier petición web) para cachear lo que me interesa.

En el caso del contenido, lo pido al servidor y si recibo datos los cacheo (desde laa app puedo mostrar de entrada los datos de caché y hacer la petición al servidor para intentar tener la última versión).

Para el resto de casos (los recursos de la app shell), intento devolverlo desde caché y en caso contrario, lo devuelvo haciendo la petición al servidor.

/* service-worker.js*/

/* idle stage: intercept request and do whatever you want*/
self.addEventListener('fetch', function(e) {

    /*fetch and cache CONTENT data before returning it*/
  if (e.request.url.startsWith(contentURLBase)) {
    e.respondWith(
      fetch(e.request)
        .then(function(response) {
          return caches.open(dataCacheName).then(function(cache) {
            cache.put(e.request.url, response.clone());
            return response;
          });
        })
    );
  } 

  /*try to return APP SHELL files from cache, otherwise get from network*/
  else {
    e.respondWith(
      caches.match(e.request).then(function(response) {
        return response || fetch(e.request);
      })
    );
  }
});

¿Qué más puedo hacer con el Service Worker?

Otras cosas que puedes hacer con tu Service Worker es suscribirte a eventos push o a eventos sync para recibir notificaciones push o sincronizar la app en background respectivamente, pero eso ya te lo contaré otro día…

Resumiendo

Sin duda, las PWA son una aproximación atractiva para las mobile apps, es solo cuestión de tiempo que empiecen a ganarle la partida a todas aquellas apps que realmente no tienen un motivo de peso detrás para existir como apps nativas.

¿Dime… ya conocías las PWA? ¿Te parece interesante está aproximación a las mobile apps?

Published inES6Ionic 2

4 Comments

  1. Maximo Miranda Maximo Miranda

    Buen post, sería interesante un curso sobre este tema completo, me parece intezante gracias por tu aporte!

  2. Enrique gracias por estos árticulos. Yo tengo desarrollada en Ionic un app de audios podcast que permite la reproducción online y offiline al descarar los audios en el dispositivo. Tengo en mente poder brindar un servicio similar con PWA pero el mayor problema que veo es la reproducción de los audios de manera offiline. Una gran utilidad de mi app es que los usuarios que descargan los audio lo pueden escuchar de manera offline desde la misma app. ¿Cómo podría desarrollar algo similar con una PWA, el cache de los navegadores móviles es muy muy reducido?. Mucha gracias

  3. Enrique Oriol Enrique Oriol

    Tendrías que gestionar la cuota de almacenamiento. En función del tipo de almacenamiento que uses tendrás una cuota por defecto (suele ser muy baja), pero puedes solicitar más (puede ser que le pida permiso al usuario). En todo caso, todo lo que almacenes en caché tienes que poderlo recuperar del servidor, por que el navegador en cualquier momento puede liberar la caché de tu web app si considera que necesita ese espacio para otras cosas.

    Aquí tienes más detalles sobre las cuotas de almacenamiento según navegador y tecnología: https://www.html5rocks.com/en/tutorials/offline/quota-research/

    ¡Saludos!

  4. Lucas Lucas

    Me pareció muy interesante y muy útil! Estaría bueno tener un curso sobre este tema, por lo menos de una app básica como para tener una idea! Muchas gracias por el aporte.
    Saludos.

Deja un comentario