Text
#BDXIO17
Emmanuel DEMEY
@EmmanuelDemey
Zenika Lille
Gautier de Saint Martin Lacaze
@darkjabberwock
Freelance
PWAs are the missing technology link to bring the best features of the web to solve the issues of native apps and closed marketplaces.
Chris Heilmann
Alibaba
(e-commerce/China)
- 76% de conversions en plus entre navigateurs;
- 14% en plus d'utilisateurs actifs/mois sous iOS; 30% en plus sous Android;
- 4X plus de taux d'itéraction à partir de "Add to Homescreen".
Source : https://developers.google.com/web/showcase/2016/pdfs/alibaba.pdf
FlipKart
(e-commerce/India)
- Temps passé sur Flipkart lite vs. expériences mobile précedentes: 3.5 minutes vs 70 seconds.
- 3x plus de temps passé sur le site;
- 40% en plus de taux d'engagement
- 70% en plus de taux de conversion entre ceux arrivant via "Add to Homescreen";
- 3x mois d'usage de données.
https://developers.google.com/web/showcase/2016/flipkart
Caracteristiques
Connectivity-independant
Linkable
Installable
Re-engageable
Discoverable
Fresh
App-Like
Les prérequis
Les prérequis
Vous avez dit Progressive ?
if("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(pos => {
});
} else {
}
- Feature Detection
- Balises ignorées
<meta property="og:title" content="titre"/>
<meta property="og:type" content="website"/>
Par où commencer?
Les fonctionnalités, que nous allons vous présenter, font parties juste d'un toolkit.
Audit
LightHouse
- Outil permettant d'auditer une application web
- Intégré à Chrome Devtools (onglet Audits) ou via un module NPM
- Retourne un score (sur 100) indiquant le taux de conformité avec les PWA
- Règles sur différents domaines : Performance, Sécurité, A11Y, RWD, ...
npm i -g lighthouse
lighthouse https://google.fr
LightHouse
LightHouse
PW1
Le Web App Manifest
Le Manifest c'est...
-
Méta-données associées à une web app (format json)
-
Permet "d'installer" une web app
-
Il nous donne un contexte de navigation "top-level"
- Il "fait autorité"
Lien vers le Manifest
<!doctype html>
<html lang="fr">
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8" />
<meta content="width=device-width, initial-scale=1.0"
name="viewport" />
<title>Ma première PWA</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
</body>
</html>
Conditions d'installation
- name meta-data
- Une icône de taille adequate
- Connexion sécurisée
- Doit "fonctionner" sans connexion
- Visites récurrentes
- Design Responsive
Conditions de validation d'un Manifest
- Service Worker enregistré
- name ou short_name
- L'url de départ : start_url
- Les icônes : icons (minimum 144px)
Style
Display
Manifest - Validation
https://manifest-validator.appspot.com
Support
Install banner
window.addEventListener(
'beforeinstallprompt', (e) => {
if (isLoggedIn()) {
e.preventDefault();
}
});
Manifest - Event
PW2
HTTP2
HTTP1
- 15 ans d'existence
- Applications de plus en plus lourdes
- Navigateur limitant le nombre de requêtes simultanées
- Utilisation de mécanismes permettant de réduire le temps de chargement
- Minification
- Concaténation
- Domain Sharding
HTTP2
- Compatible avec HTTP1
- Fonctionnement avec TLS
- Diminue le nombre d'allers retours
- Compression des Headers
- Limite le nombre de connexions
- Système de multiplexage
- Server Push
HTTP2
HTTP2 avec Express
const fs = require('fs')
const javascript = fs.readFileSync('./app/assets/script.js');
app.get('/', (req, res) => {
res.push('/script.js', {
response: {'content-type': 'application/javascript'}})
.end(javascript)
res.sendFile(__dirname + '/app/index.html');
})
const options = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt')
};
require('spdy').createServer(options, app).listen(3002);
PW3
Service Worker
Service Worker
Service Worker
- Proxy entre votre application et votre serveur
- Nécessite HTTPs
- Pas d'accès au DOM
- Fonctionnalité de Cache, Push, Geofencing, Background Sync
- Evenement d'un SW : install, activate, fetch et sync
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
.then(req => {
console.log('Success');
}).catch(error => {
console.log('Error');
});
};
Service Worker
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
);
});
Service Worker
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
if(response.status === 404) {
...
}
return response;
})
);
});
interface Request {
method
url
headers
context
referrer
referrerPolicy
mode
credentials
redirect
integrity
cache
bodyUsed
}
self.addEventListener('fetch', event => {
/*
let options = {
status: 200,
statustext: 'OK',
headers: { ... }
};
*/
event.respondWith(
new Response('Hello World', /*opts*/)
);
});
Support
PW4
App Shell
- Rendre le layout du site accessible offline
- Afficher la structure du site le plus rapidement possible
- Utilisation de l'API Cache
- Precache des ressources statiques durant l'événement install
- Réutilisation du Cache dans l'événement fetch
App Shell
- Interface permettant de stocker des paires Request / Response
- API se basant sur les Promise
- Plusieurs caches pour une même application
- Aucun cache implémenté par défaut
- Ajout, mise à jour et suppression à la charge du développeur
App Shell - Cache
App Shell - Cache
CacheStorage.open(cacheName)
Cache.match(request, options)
Cache.matchAll(request, options)
Cache.add(request)
Cache.addAll(requests)
Cache.put(request, response)
Cache.delete(request, options)
App Shell - install
const cacheName = 'codelab';
const filesToCache = [
'/',
'/script.js',
'/css/style.css'
];
self.addEventListener('install', e => {
e.waitUntil(
caches.open(cacheName).then(cache => {
return cache.addAll(filesToCache);
})
);
});
App Shell - fetch
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request)
.then(response => {
return response || fetch(e.request);
})
);
});
App Shell - activate
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(keyList => {
return Promise.all(keyList.map(key => {
if (key !== cacheName) {
return caches.delete(key);
}
}));
})
);
});
Prévenir vos utilisateurs
online / offline
if(!navigator.onLine) {
...
}
window.addEventListener("offline", () => {
...
}, false);
window.addEventListener("online", () => {
...
}, false);
PW5
IndexedDB
- API permettant de stocker des données côté client (clé => valeur)
- La donnée stockée peut être un objet complexe
- Base de données non relationnelle
- API asynchrone, utilisant des callbacks :(
- Syntaxe assez verbeuse
- Vendor Prefixes
- Cursor
- Indexes
- Transaction
- Utilisation de librairies externes pour simplifier l'utilisation
IndexedDB
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB;
// Open (or create) the database
const open = indexedDB.open("Codelab", 1);
// Create the schema
open.onupgradeneeded = function() {
let db = open.result;
let store = db.createObjectStore("Conference", {keyPath: "id"});
let index = store.createIndex("SpeakerIndex", ["speaker"]);
};
open.onsuccess = function() {
// Start a new transaction
let db = open.result;
let tx = db.transaction("Conference", "readwrite");
let store = tx.objectStore("Conference");
let index = store.index("SpeakerIndex");
// Add some data
store.put({id: 12345, speaker: "Manu", "title": "PWA");
store.put({id: 67890, speaker: "Gauthier", "title": "VueJS");
// Query the data
let getPWA = store.get(12345);
let getVueJS = index.get(["Gauthier"]);
getPWA.onsuccess = function() {
console.log(getPWA.result);
};
getVueJS.onsuccess = function() {
console.log(getBob.result);
};
// Close the db when the transaction is done
tx.oncomplete = function() {
db.close();
};
}
- Librairie plus simple à utiliser
- Fallback vers WebSQL et localStorage
- Support des callbacks et des promises
- API similaire à localStorage
LocalForage
localforage.setItem('somekey', {}).then(value => {
console.log(value);
})
localforage.getItem('somekey').then(value => {
console.log(value);
});
localforage.removeItem('somekey').then(() => {
console.log('Key is cleared!');
});
IndexedDB
Support
PW6
Background Sync
- Ajout d'un événement Sync aux Service Workers
- Assure l'envoie d'une requête lorsque l'utilisateur est offline
- Traitement exécuté tant que la Promise est rejetée
Background Sync
Background Sync
self.addEventListener('sync', event => {
if (event.tag == 'SYNC_SETTINGS') {
event.waitUntil(updateSettings());
}
});
navigator.serviceWorker.getRegistration()
.then(registration => {
registration.sync.register(
'SYNC_SETTINGS').then(() => {
console.log('Sync registered');
});
})
PW7
Pour aller plus loin...
- Trusted Web Activities
- Notifications
- Payment Request API
- One-Tap Sign-In / Sign-Up
- Web Share API
Emmanuel DEMEY
@EmmanuelDemey
Zenika Lille
Gautier de Saint Martin Lacaze
@darkjabberwock
Freelance
Codelab PWA
By Emmanuel Demey
Codelab PWA
- 2,078