Retour sur la conférence Google dédiée aux Progressive Web Apps


PWA

Le 17 mars dernier se tenait à Londres une conférence Google dédiée aux Progressive Web Apps (PWA). Le programme était alléchant, et proposait un bon apercu des différents aspects impliqués dans le concept des PWA. J’ai eu la chance d’y assister pour SFEIR et il est temps de vous proposer un résumé des différentes keynotes de la journée.

1. What are Progressive Web Apps (Dion Almaer)

Dion a commencé sa présentation en nous rappelant à quel point l’arrivée d’AJAX a révolutionné la philosophie de nos applications Web. Nous sommes maintenant confrontés à une nouvelle révolution avec l’avènement du mobile.

Et si une application mobile pouvait :

  • s’installer sur l’écran d’accueil ?
  • pousser des notifications ?
  • fonctionner offline ?

L’idée principale est de fournir à l’utilisateur un contenu le plus rapidement possible et dans cet esprit “il vaut mieux le résultat d’hier instantanément qu’attendre trop longtemps celui d’aujourd’hui.” La performance et l’instant loading sont donc au coeur de ce type d’applications et le chef d’orchestre est le Service Worker.

2. Security for all (Matt Gaunt)

L’objectif de cette présentation était de plébisciter l’utilisation de HTTPS pour :

  • protéger les données de l’utilisateur
  • protéger le contenu de notre application

Matt a d’ailleurs insisté sur l’ensemble des features qui maintenant nécessitent HTTPS pour fonctionner :

  • service worker
  • getUserMedia
  • les API de géolocalisation
  • l’ajout en écran d’accueil…

Matt nous proposa ensuite une série de Tips and Tricks pour développer en HTTPS :

  • le localhost est considéré comme HTTPS
  • si on doit déployer, on privilégiera une plate-forme nativement exposée en HTTPS comme Github, firebase, Google Cloud Platform…
  • let’s encrypt qui nous fournit des certificats gratuits valables 3 mois
  • un générateur de configuration de Mozilla pour faciliter la mise en place en fonction de votre serveur
  • SSL Labs pour tester votre serveur
  • les devTools de Chrome avec un nouvel onglet Security

3. Instant Loading (Surma)

Une superbe présentation, qui hors du contexte des PWA nous rappelle l’ensemble des bonnes pratiques qui vont nous permettre d’optimiser le chargement de notre page pour nous rapprocher du fameux Instant Loading.

En effet on veut :

  • De petits assets
  • Télécharger uniquement ce dont on a besoin
  • Télécharger uniquement ce qui a changé depuis notre dernière visite

Dans une optique progressive, nous disposons de plusieurs leviers. Tout d’abord, Surma insiste sur la compression qui est une méthode “gratuite” souvent peu utilisée et pourtant complètement implémentée par les navigateurs. En effet, en combinant la minification et la compression on arrive à des gains de 60 à 85 % sur les librairies les plus utilisées (jquery, etc.).

PWA - perf

Pour les images, les conseils sont :

  • de privilégier le nouveau format WebP (grâce aux entêtes, on sait côté serveur si le navigateur est compatible)
  • le tag Picture

L’un des axes d’améliorations reste le réseau avec ces fameux Round Trip qui peuvent pénaliser les requêtes à tous les niveaux. Suma insiste pour nous rappeler que l’indication de connexion de nos téléphones s’arrête aux antennes, mais que des ralentissements se produisent aussi dans le reste de la chaîne. Il faut donc optimiser la récupération :

  • du JavaScript en utilisant les attributs defer et asynch pour privilégier le parsing HTML avant l’exécution des scripts.
  • Le CSS avec LoadCSS. Attention à l’effet flash, il faut donc définir notre CSS critique à charger et le reste en asynchrones (Regioning). Par exemple ce tunner s’affiche avec un minimum de CSS pour visualiser le contour, le bouton… et s’enrichit par la suite
  • Pour les iframe on utilisera un attribut data-src pour l’URL afin de garder le contrôle du chargement en fonction de notre document. On pourra effectivement remplir l’attribut src au moment souhaité avec le contenu de data-src.

Un petit rappel sur les bonnes utilisations des entêtes pour optimiser les échanges avec :

  • etag et last-modified
  • cache-control et max-age
  • de longues périodes pour les ressources statiques
  • attention à ne pas cacher la page index.html qui contiendra le plus de changement possible (images, scripts, CSS…)
  • des numéros de révisions pour le JS (gulp-rev)
  • favoriser les CDN

Et enfin, nous avons vu en quoi HTTP2 va intervenir pour l’optimisation de notre chargement. Exit les 6 connexions parallèles : avec HTTP2 on a une seule connexion qui sera partagée par des streams. On oublie donc ce blocage, car la connexion sera disponible pour un nouveau stream pendant que les autres attendent la réponse. On a vu une démo du chargement d’une grille de 180 images en HTPP vs HTTP2 et le résultat est sans appel. Avec ce nouveau mode de fonctionnement, il devient inutile d’agréger les ressources en fichier unique (JS, sprite) et au contraire il faut privilégier les fichiers pour que si un contenu change, on ne charge que la bonne ressource.

4. High Performance Interactions (Paul Lewis)

Paul nous livre ici un talk très technique (difficile à retranscrire) pour expliquer le flux de rendu dans un navigateur. En partant du signal vsynch lancé par l’OS au navigateur toutes les 16.66ms, on a donc des frames dans lesquelles on va essayer de rendre la page sans dépasser ce délai.

Le processus de rendu c’est :

  • parser le HTML
  • recalculer les styles
  • créer le render Tree (LAYOUT)
  • le PAINT est en fait découplé en 2 phases :
    • le PAINT ou les éléments à redessiner sont enregistrés
    • le RASTERIZE ou les éléments sont réellement modifiés
  • mettre à jour le Layer Tree (z-index par exemple)
  • découper le rendu en layers (COMPOSITE)

PWA - anatomy-of-a-frame

Pour plus de détails je vous invite à lire cet excellent article de Paul.

Ce dernier nous a ensuite fourni plusieurs conseils pour essayer de garder nos traitements viables dans une frame :

  • éviter dans la frame les phases de LAYOUT et de PAINT : attention aux propriétés CSS (height, width, etc.) ou aux méthodes JS (getBoundingClientRect, getComputeStyle). Le site CSSTriggers vous donne justement les effets des différentes propriétés.
  • utiliser plutôt requestAnimationFrame que setTimeout ou setInterval
  • utiliser les CSS transform (joue sur la phase de COMPOSITE)

Paul proposer aussi un nouveau concept pour les animations : FLIP (First Last Invert Play) ou l’idée est de partir du résultat final de trouver la transformation pour arriver à l’état initial, de l’appliquer et de lancer l’animation qui restore l’état final dans la frame suivante. Oui, bon, j’avoue, il faut lire l’article pour bien comprendre ! :)

Pour ceux qui veulent aller plus loin, il y a un carrément un cours sur l’optimisation.

5. Instant and Offline Apps with Service Worker (Jake Archibald)

Jake nous a sorti LE talk sur les services worker, avec beaucoup de code et donc vous imaginez bien que la prise de note était un peu compliquée. Le constat c’est que personne n’aime attendre pour voir son application s’afficher. Jake nous parle du offline ou pour l’utilisateur, la réponse est immédiate. Mais dans le cas du LieFI – quand on pense avoir du réseau, mais en fait non – on a ce problème, où l’application essaie de se charger et c’est long, long, voire interminable…

Donc en partant d’un exemple d’applications de chat en emoji, Jake introduit les services worker en démontrant à chaque étape comment on résout les problèmes :

  • offline pour présenter rapidement du contenu
  • LieFI pour proposer immédiatement des données en attendant que les résultats arrivent
  • l’utilisation de IndexDB pour stocker les datas et un plugin npm idb pour faciliter l’accès à l’API

Le service worker a alors un cycle de vie :

PWA - sw-lifecycle

Il faut l’enregistrer pour pouvoir l’utiliser. Petit rappel, sur HTTPS :

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

On peut ensuite écouter l’événement install pour commencer à cacher des assets en utilisant l’accès au cache. Attention, ici on parle d’un cache dédié et non le cache navigateur.

var cacheName = 'weatherPWA-step-5-1';  
var filesToCache = [];
self.addEventListener('install', function(e) {  
  console.log('[ServiceWorker] Install');  
  e.waitUntil(  
    caches.open(cacheName).then(function(cache) {  
      console.log('[ServiceWorker] Caching app shell');  
      return cache.addAll(filesToCache);  
    })  
  );  
});

Et enfin pour traiter la requête, on utilise l’événement fetch couplé à la méthode respondWith :

self.addEventListener('fetch', function(e) {  
  console.log('[ServiceWorker] Fetch', e.request.url);  
  e.respondWith(  
    caches.match(e.request).then(function(response) {  
      return response || fetch(e.request);  
    })  
  );  
});

À partir de là, il existe un tas de stratégies pour gérer votre cache. Je vous conseille de jeter un oeil ici pour plus de détails.

Et en bonus, si si, ça me fait plaisir, un cours sur les applications web offline !

6. Making it Installable (Paul Kinlan)

Le talk de Paul était sur la capacité à installer nos applications sur l’écran d’accueil. Cette fonctionnalité, initiée par Apple en 2007, s’est généralisée sous Android en 2012, où il ne fallait pas moins de 9 étapes pour installer son site…

S’en suivra beaucoup d’expérimentations à coups de méta et d’attributs propriétaires, sans qu’aucun standard ne parviennent à s’imposer.

Il fallait donc imaginer un mécanisme plus simple. La solution proposée est le manifest.json, qui donne le détail de :

  • comment afficher notre application (icônes…)
  • quelle URL lancer
  • comment la lancer (portrait, paysage). On peut également indiquer le mode d’ouverture avec une barre de navigation (windows) ou sans (standalone). Cette information est d’ailleurs accessible par media query (display-mode).

Ce fichier propose un ensemble de paramètres. On peut par exemple indiquer la couleur du thème, la couleur du fond, le titre dans le splash screen, etc.

{  
  "name": "Demo",  
  "short_name": "Demo",  
  "icons": [{  
        "src": "push-icon.png",  
        "sizes": "111x111",
        "type": "image/png"
      }],  
  "start_url": "/index.html",  
  "display": "standalone",      
}

Alors comment ça se passe ?

Le navigateur détecte dans votre page la référence à ce manifest et si vous faites au moins 2 navigations sur ce site en 5 minutes, il vous propose un prompt pour vous demander d’installer l’application.

Vous avez accès à des callbacks JavaScript beforeinstallprompt pour prendre la main et pour, par exemple, l’annuler et le proposer uniquement en réponse à un click de l’utilisateur.

Pour tester, on dispose du flag by pass user engagement check et du menu request app banner dans Chromium.

7. Deeper Engagement with Push Notifications (Pete LePage)

Cette dernière fonctionnalité permet de réengager vos utilisateurs en leur proposant des notifications. Pete insiste sur l’intérêt de proposer ce service pour fidéliser vos utilisateurs, mais attention à ne pas les spammer. Pour cela une notification doit être :

  • personnalisée
  • opportune
  • pertinente
  • actionnable
  • concise
  • sans publicité

Techniquement, on propose à l’utilisateur de s’inscrire à nos notifications, le navigateur demande la permission et on récupère les détails d’un end point. On l’envoie au serveur pour qu’il soit sauvegardé et utilisé lors de l’envoi de notifications.

Pour envoyer un message, on utilise un end point centralisé (Google Cloud Messaging) qui permettra au navigateur de limiter le nombre de sites à écouter. Rendez-vous dans la Google Developper Console pour vous enregistrer et utiliser l’API. Le projectID doit être ajouté au manifest dans la propriété gcm_sender_id

{  
  ... 
  "gcm_sender_id": "224273183921"    
}

Et enfin, on traitera le message à l’aide du service worker en s’abonnant à l’événement push :

self.addEventListener('push', function(event) {  
  console.log('Received a push message', event);
  var title = 'Yay a message.';  
  var body = 'We have received a push message.';  
  var icon = '/images/icon-192x192.png';  
  var tag = 'simple-push-demo-notification-tag';
  event.waitUntil(  
    self.registration.showNotification(title, {  
      body: body,  
      icon: icon,  
      tag: tag  
    })  
  );  
});

Vous trouverez ici et un tas d’informations utiles.

J’ai malheureusement manqué le dernier talk mais j’ai passé une superbe journée, sur un sujet que j’affectionne tout particulièrement en ce moment.

Vivement la suite au Devoxx France !

Vous aimerez aussi...

  • Guillaume GARCIA

    Merci pour ce compte-rendu complet !
    J’ai suivi une conférence similaire chez Google Paris Mardi 29 Novembre intitulée « Progressive Web Apps ».

    J’aurais aimé savoir comment traiter les notifications en push (que le client s’engage à afficher à l’utilisateur) qui ne seraient pas pertinentes.

    Exemple :
    Je m’abonne aux alertes météo.
    Un ouragan s’approche de New-York et le serveur me pushe cette notification.
    Malheureusement, c’est mon device qui connaît ma géolocalisation et je suis sur la côte Ouest alors que l’alerte concerne la côte Est. (remplacer géolocalisation par tout traitement un peu évolué côté client).
    Je vais être obligé d’afficher la notification alors que c’est peu pertinent pour l’utilisateur.

    Une idée de comment traiter ce genre de scénario ?
    Merci !

    • cyril

      Guillaume,

      Le plus simple est en réponse à une notification, d’appeler le serveur avec le contexte. Vous aurez alors dans la réponse, les informations qui vous décideront à afficher ou pas la notification a l’utilisateur