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,091