Convertir son blog en Progressive Web App

Featured image

Cet article est le second de la série dédiée aux retours d’expérience sur le sujet : Obtenir un site performant avec Accelerated Mobile Page, Progressive Web App et un content delivery network

Partie 1 : AMP Partie 2 : PWA Partie 3 : CDN & SSL : à venir …

PWA, c’est quoi ?

Là encore il s’agit d’une idée lancée par google. Si tout est expliqué ici https://developers.google.com/web/progressive-web-apps/, je vais tenter d’en résumer rapidement les principes.

L’objectif est d’avoir une application web qui puisse être directement installée sur mobile sans passer par un store, et qui puisse être indexée par les moteurs de recherche. Ces PWA devront se rapprocher le plus possible d’une application native en matière de fonctionnalités et donc s’adapter en fonction des capacités du périphérique utilisé : Géolocalisation Push-notification Audio / Vidéo … et plein d’autres Ce site vous permet de voir les capacités de votre navigateur : https://whatwebcando.today/camera-microphone.html

À la différence des applications natives, une PWA devra répondre rapidement. Autrement dit, on ne va pas télécharger 40Mo lors de la première ouverture ! Le premier chargement sera le plus souvent limité à un shell vide, ensuite l’application va télécharger de façon asynchrone le contenu que l’on veut afficher lorsque cela est nécessaire.

Le téléchargement et la mise en cache du shell ou des contenus se basent sur une nouvelle API, les “Service Workers”, pas encore complètement supportée par tous les navigateurs. cf. tableau de compatibilité des navigateurs sur MDN. Le service worker est simplement un background worker avec lequel on communique pour effectuer des tâches de fond.

AMP + PWA compatible ?

Ces deux technologies ne partagent pas exactement les mêmes objectifs. Si AMP a pour but d’accélérer le web sur mobile en le rendant très léger, PWA est plus lourd puisque son but est presque de remplacer les applications natives et le passage par un store.

Il y a encore quelques mois, ces deux technologies n’étaient donc pas compatibles entre elles, mais depuis, le composant <amp-install-serviceworker> est disponible et permet d’installer le service worker depuis une page APM. https://www.ampproject.org/docs/reference/components/amp-install-serviceworker

Mise en place du service worker

Un service worker est un script fonctionnant en tâche de fond, et qui est détaché du contexte UI. Autrement dit, le worker n’a pas d’accès direct au DOM ni aux interactions utilisateur. Toute la communication se fait au travers d’événements et de l’API postMessage. Le worker a cependant accès à toutes les requêtes/réponses Http ; il fonctionne un peu comme un proxy. De plus, les APIs de cache et de stockage sont disponibles, il est alors possible pour chaque requête de vérifier si une réponse existe dans le cache et dans le cas contraire on va pouvoir y ajouter la réponse associée à la requête pour la prochaine fois.

Le service worker possède un cycle de vie précis après son enregistrement :

Vous pouvez tester facilement avec ce script en regardant les logs dans la console :

self.addEventListener('install', event => {
  console.log('V1 installing…');
});

self.addEventListener('activate', event => {
  console.log('V1 now ready to handle fetches!');
});

self.addEventListener('fetch', event => {
});

Le composant AMP va donc référencer un script qui définit les handlers pour ces événements.

<amp-install-serviceworker
   src="/serviceworker.js"
   layout="nodisplay">
</amp-install-serviceworker>   

Dans mon cas, j’ai fait le choix de ne pas faire de shell, le contenu complet de chaque page sera mis en cache pour chaque requête. Le fichier serviceworker.js se trouve à la racine de mon site et ne contient que le code suivant :

self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.open('mysite').then(function (cache) {
            return cache.match(event.request).then(function (response) {
                var fetchPromise = fetch(event.request).then(function (networkResponse) {
                    if (event.request.method === "GET") {
                        cache.put(event.request, networkResponse.clone());
                    }
                    return networkResponse;
                })

                return response || fetchPromise;
            })
        })
    );
});

À chaque requête de type GET, je mets la réponse en cache si ce n’était pas fait.

Si vous préférez partir sur un design avec un shell, il est recommandé de précharger et mettre en cache les ressources nécessaires au shell dès l’installation du Service Worker.

Le script ressemblera alors à celui-ci :

var cacheName = 'shell-content';
var filesToCache = [
  '/css/styles.css',
  '/js/scripts.js',
  '/images/logo.svg',
  '/offline.html’,
  '/,
];

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

HTTPS ou rien

Les services workers fonctionnent uniquement avec le protocole HTTPS ou en localhost. Fonctionnant comme un proxy, pouvant modifier le contenu des requêtes et des réponses, il est évident que tout doit se passer dans un contexte sécurisé grâce à un certificat SSL. Si vous maîtrisez votre infra, installer un certificat sur votre serveur ne devrait pas être compliqué. Dans mon cas, je suis sur Github pages avec un nom de domaine personnalisé et dans ce cas, l’utilisation du protocole HTTPS n’est pas possible. Nous verrons dans la troisième partie de l’article comme obtenir un site en HTTPS grâce au service de CloudFare.

Manifest

Les mécanismes de mise en cache sont maintenant fonctionnels, il reste cependant une dernière partie de l’application à ajouter : le manifeste.

Le manifeste permet d’ajouter des informations de description de votre application web:

Il suffit de créer un document texte au format JSON et de le référencer dans votre site :

<link rel="manifest" href="/manifest.json">

Voici le contenu de mon fichier :

{
name: "Evilznet Blog",
short_name: "Evilznet",
description: "Personal blog about computing stuff",
start_url: ".",
display: "standalone",
background_color: "#FFFFFF",
theme_color: "#FFFFFF",
icons: [
{
src: "/android-icon-36x36.png",
sizes: "36x36",
type: "image/png",
density: "0.75"
},
{
src: "/android-icon-48x48.png",
sizes: "48x48",
type: "image/png",
density: "1.0"
},
{
src: "/android-icon-72x72.png",
sizes: "72x72",
type: "image/png",
density: "1.5"
},
{
src: "/android-icon-96x96.png",
sizes: "96x96",
type: "image/png",
density: "2.0"
},
{
src: "/android-icon-144x144.png",
sizes: "144x144",
type: "image/png",
density: "3.0"
},
{
src: "/android-icon-192x192.png",
sizes: "192x192",
type: "image/png",
density: "4.0"
},
{
src: "/android-icon-512x512.png",
sizes: "512x512",
type: "image/png"
}
]
}

Le but du manifeste est de permettre l’installation de l’application sur l’écran d’accueil d’un appareil. Si vous utilisez chrome sur android, lors de la première visite un menu va apparaître en bas de l’écran, vous proposant d’ajouter l’application sur votre écran d’accueil android.

DevTools

Pendant le développement de votre application, vous voudrez vérifier le fonctionnement et peut être déboguer l’application. On va alors utiliser l’onglet Application des devtools de chrome. On y retrouve les informations du service worker, des stockages et du manifeste. Vous pourrez notamment :

Conclusion

Si l’on peut facilement imaginer que les applications natives pourraient dans la plupart des cas être remplacées par une ‘simple Application Web’, l’aspect commercial va jouer énormément dans la démocratisation du concept. Google, qui mise principalement sur les pubs internet au travers de son moteur de recherche, va fortement pousser la technologie. Mais des géants comme Apple dont les revenus proviennent principalement de l’AppStore ne la priorisent pas. C’est assez paradoxal lorsque l’on se rappelle de la sortie du premier Iphone et des mots de Steve Jobs à l’époque :

The full Safari engine is inside of iPhone. And so, you can write amazing Web 2.0 and Ajax apps that look exactly and behave exactly like apps on the iPhone. And these apps can integrate perfectly with iPhone services. They can make a call, they can send an email, they can look up a location on Google Maps. And guess what? There’s no SDK that you need! You’ve got everything you need if you know how to write apps using the most modern web standards to write amazing apps for the iPhone today. So developers, we think we’ve got a very sweet story for you. You can begin building your iPhone apps today.

Il parlait clairement de ce genre de technologie. Quel visionnaire ce Steve.

Dans le prochain article, je vais vous montrer comment utiliser les services de CloudFare pour résoudre le problème du protocole HTTPS et profiter du cache sur son CDN.