Text

Emmanuel DEMEY

@EmmanuelDemey

GDE Web Technologies

 

 

 

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 présentées 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

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 "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
  • Service Worker enregisté

Style

Display

Manifest - Validation

https://manifest-validator.appspot.com

Support

Install banner

window.addEventListener(
    'beforeinstallprompt', (e) => { 
      if (isLoggedIn()) {
        e.preventDefault();
      } 
    });

Manifest - Event

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

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

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);

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

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');
            });
    })  

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);

Pour aller plus loin...

  • Trusted Web Activities
  • Notifications
  • Payment Request API
  • One-Tap Sign-In / Sign-Up
  • Web Share API

Emmanuel DEMEY

@EmmanuelDemey

 

 

 

Copy of Codelab PWA

By Emmanuel Demey

Copy of Codelab PWA

  • 2,085