Progressive Web
Applications
(PWA)
Temario
-
Concepto y estructura básica
-
App manifest.
-
Service Workers.
-
Estrategias de caching con Service Workers.
-
Web Push Notifications.
Introducción a las PWA
PWA
-
Diferencias entre aplicaciones nativas y webs
-
Ventajas de web y de apps nativas
-
Aplicaciones progresivas y multiplataforma
-
Aplicaciones web (HTML, CSS, JS)
-
Responsive
-
Rápidas
-
Trabajan offline
-
Un solo lenguaje
-
Fácil distribución
-
Enganchan al usuario
-
Aparecen en Google
-
Seguras
Entorno de desarrollo
Entorno de desarrollo
-
Editor: Visual Studio Code
-
Configurar:
-
Format on Paste y Format on Save
-
-
Node.js y npm
-
Extensión ColorZilla para el navegador
-
Android Studio para el emulador de Android
-
Xcode para el emulador de iOS
Git
Comandos básicos
-
Clonar un repositorio:
git clone URL
-
Descargar última versión del repositorio:
git pull origin master
Configuración proxy
git config --global http.proxy http://username:password@host:port
git config --global https.proxy http://username:password@host:port
Node.js y npm
npm
-
Instalar última versión después de instalar Node.js
(configurar proxy si es necesario): npm install -g npm -
Repositorio de módulos distribuibles
-
Módulos globales y módulos locales
-
La carpeta node_modules
-
El archivo package.json:
-
Registro de dependencias
-
Dependencias de desarrollo y de producción
-
Versiones (SEMVER)
-
Comandos npm
-
Instalar un paquete globalmente:
npm install -g paquete -
Instalar un paquete de producción:
npm install paquete -
Instalar un paquete de desarrollo:
npm install paquete --save-dev -
Instalar todas las dependencias:
npm install -
Instalar las dependencias de producción:
npm install --production -
Listar paquetes instalados:
npm list --depth=0 (locales)
npm list -g --depth=0 (globales)
Comandos npm
-
Lanzar el ejecutable de un paquete:
npx ejecutable
(aunque no esté instalado)
Configuración proxy
npm config set proxy http://username:password@host:port
npm config set https-proxy http://username:password@host:port
JavaScript
Funciones
-
Pasar funciones anónimas como parámetros
-
Funciones callback
-
Funciones arrow
-
map() y filter()
Promesas
-
Procesos asíncronos
-
Dos métodos principales: then() y catch()
-
Encadenado de promesas
-
El método finally()
-
Promise.all()
-
async / await
Fetch API
-
Request y Response
-
La función fetch()
-
Nos interesa del Request:
-
method
-
mode
-
destination
-
headers
-
url
-
body
-
Fetch API
-
Nos interesa del Response:
ok
status
headers
-
body
body.text()
body.json()
PWA
Lighthouse
-
Integrada en Chrome
-
También disponible como paquete npm
-
Nos guía para saber qué le falta a nuestra PWA
Web manifest
-
Información sobre la apariencia de la app instalada
-
Archivo JSON
-
<link rel="manifest">
Web manifest
-
Estructura:
{
"name": "Nombre de la app",
"short_name": "Nombre corto",
"theme_color": "#2196f3",
"background_color": "#2196f3",
"display": "standalone",
"start_url": "/",
"icons": [
{
"src": "images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Web manifest
-
Iconos para web
<link rel="icon" type="image/png" sizes="32x32" href="<ruta>/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="<ruta>/favicon-16x16.png">
<link rel="shortcut icon" href="<ruta>/favicon.ico">
Web manifest
-
Iconos para Android (mínimo 192x192 y 512x512)
"icons": [
{
"src": "images/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "images/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "images/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "images/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "images/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
Web manifest
-
iOS 🤯 (documentación)
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="apple-mobile-web-app-title" content="appTitle">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<link rel="apple-touch-icon" sizes="180x180" href="<ruta>/apple-touch-icon.png">
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" href="<ruta>/apple-launch-1125x2436.png">
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="<ruta>/apple-launch-750x1334.png">
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3)" href="<ruta>/apple-launch-1242x2208.png">
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="<ruta>/apple-launch-640x1136.png">
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)" href="<ruta>/apple-launch-1536x2048.png">
<link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)" href="<ruta>/apple-launch-1668x2224.png">
<link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)" href="<ruta>/apple-launch-2048x2732.png">
Service workers
-
JavaScript que corre en un hilo paralelo
-
No tienen acceso al DOM ni al objeto window
-
Se comunican con el hilo principal mediante postMessage()
-
Proxy entre la app y la red
-
Registro de un SW
-
Ciclo de vida de un SW
Service workers
-
Condiciones para que la PWA sea instalable:
-
Manifest con:
-
name o short_name
-
start_url
-
display: "standalone" | "fullscreen" | "minimal-ui"
-
icons: 512 y 192
-
-
Service Worker con evento fetch
-
HTTPS
-
Service workers
-
El prompt de instalación
-
El evento beforeinstallprompt:
-
preventDefault() para que no aparezca el de Android
-
Lanzar el prompt con un gesto del usuario
-
Leer userChoice
-
Comprobar si se está abriendo la app instalada
-
let eventoInstall;
window.addEventListener('beforeinstallprompt', e => {
eventoInstall = e;
e.preventDefault();
document.querySelector('.install').removeAttribute('hidden');
})
document.querySelector('.install').addEventListener('click', () => {
eventoInstall.prompt();
eventoInstall.userChoice.then(ch => {
if (ch.outcome === 'accepted') {
console.log('El usuario ha aceptado instalar');
} else {
console.log('El usuario no ha aceptado instalar');
}
document.querySelector('.install').setAttribute('hidden', true);
});
});
window.addEventListener('appinstalled', () => console.log("El usuario ha instalado la aplicación"));
if (window.matchMedia('(display-mode: standalone)').matches) {
console.log('Desde la app instalada');
}
Caché
-
CacheStorage API:
-
open()
-
addAll()
-
put()
-
match()
-
keys()
-
Estrategias de caching
-
Cache only
Estrategias de caching
-
Cache only
-
Network only
Estrategias de caching
-
Cache only
-
Network only
-
Cache with fallback to network
Estrategias de caching
-
Cache only
-
Network only
-
Cache with fallback to network
-
Network with fallback to cache
Estrategias de caching
-
Cache only
-
Network only
-
Cache with fallback to network
-
Network with fallback to cache
-
Stale while revalidate
Estrategias de caching
-
Cache only
-
Network only
-
Cache with fallback to network
-
Network with fallback to cache
-
Stale while revalidate
-
Generic fallback
Estrategias de caching
-
Cache only
-
Network only
-
Cache with fallback to network
-
Network with fallback to cache
-
Stale while revalidate
-
Generic fallback
-
Cache then network
Workbox
-
Instalar:
npm install [-g] [--save-dev] workbox-cli -
Generar configuración:
workbox wizard --injectManifest -
El SW fuente tiene que tener esta línea:
workbox.precaching.precacheAndRoute([]); -
workbox injectManifest
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');
workbox.routing.registerRoute(
/\.css$/,
new workbox.strategies.CacheFirst()
);
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg)$/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: '',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 100,
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
})
);
Notificaciones PUSH
-
Entran en juego:
-
La app
-
El Service Worker
-
El servicio PUSH del navegador
-
Un servicio de Google, Firefox...
-
Un servidor PUSH
-
Notificaciones PUSH
-
La app:
-
Puede lanzar el prompt para preguntar al usuario si da permisos para notificaciones:
Notification.requestPermission() -
Puede preguntar si el usuario ya ha dado permisos o no:
Notification.permission
('default', 'granted' o 'denied') -
Puede pedir una suscripción al servicio PUSH:
reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: <llave pública VAPID>
});
-
Notificaciones PUSH
-
La app:
-
Cuando obtiene una suscripción del servicio PUSH, la envía a nuestro servidor PUSH, que la almacenará.
-
Notificaciones PUSH
-
El servidor PUSH:
-
Genera las claves VAPID
-
Utiliza la librería web-push para enviar notificaciones PUSH.
-
Notificaciones PUSH
-
El Service Worker:
-
Escucha el evento 'push'
-
Emite una notificación al SO:
self.registration.showNotification(título, opciones) -
Puede capturar el click en la notificación escuchando al evento 'notificationclick'
-
Links
PWA Madrid
By mariogl
PWA Madrid
Curso PWA Madrid 14-17 octubre 2019
- 1,102