Rendre un site disponible hors-ligne avec le Service Worker

JavascriptService WorkerPWA
Jérémie Loscos - 05/08/2018 à 11:32:190 commentaire

On pouvait déjà rendre une application web accessible hors ligne grâce à l’HTML 5 Application Cache, mais cette fonctionnalité à un certain nombre d’inconvénients et est maintenant dépréciée. Heureusement pour nous les Service Workers permettent de faire la même chose en mieux.

(https://developer.mozilla.org/fr/docs/Web/API/Service_Worker_API/Using_Service_Workers)


Comme un web worker, un service worker est un script qui s’exécute en arrière-plan d’une page web. En plus de réaliser des actions en arrières plan, le service worker est un proxy par lequel toutes les requêtes HTTP de la page vont passer.



Les services worker sont maintenant supportés sous Chrome, Firefox, Opera, Safari et Edge. (https://jakearchibald.github.io/isserviceworkerready/)


Un service worker est un fichier js. Le chemin d’accès de ce fichier par rapport à la racine de l’application détermine le scope du service worker. C’est-à-dire les pages à partir desquelles il va intercepter les requêtes http. Généralement le script du service worker est placé à la racine de l’application pour que son scope soit l’ensemble des pages de l’application. Les services workers ne peuvent s’utiliser que sur une page servie par HTTPS (à l’exception des pages venant de localhost)


Comment ça marche ?





Register


La première étape pour utiliser un service worker est de le déclarer avec la méthode register. Pour faire cela on ajoute dans notre page :

if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/serviceworker.js');
};


Comme précisé avant, le script du service worker est placé à la racine du site pour que son scope soit l’application entière.

Le fait de déclarer ce script comme service worker fait que le navigateur va le télécharger et l’installer.


Install


Lors de l’installation du service worker, l’événement install est déclenché. Généralement cet event est utilisé pour mettre en cache des ressources statiques dont notre application a besoin.

On peut gérer cet événement dans le script de notre service worker :

self.addEventListener("install", (event) => {
      console.log("service worker installed");
});


Activate

Une fois l’installation du service worker terminée, le navigateur va l’activer. Si un service worker est déjà présent dans la page le navigateur attend que l’ancien service worker s’arrête avant d’activer le nouveau. Quand le service worker est activé, l’événement activate est déclenché.


Lorsque cet événement est déclenché, la page est déjà chargée et affichée sans être passé par le service worker. Si on veut que le service worker s’applique immédiatement aux onglets déjà ouverts il faut utiliser la méthode clients.claim() :


self.addEventListener("activate", (event) => {
      console.log("service worker activated");
       self.clients.claim() ;
});


Fetch

Une fois le service worker activé, quand une requête est faite par le navigateur depuis la page, l’événement fetch du service est déclenché. Ces requêtes peuvent être des navigations, affichage d’image, script js, styles css, requêtes ajax, …


self.addEventListener("fetch", (event) => {
      console.log("request to : " + event.request.url);
      if (navigator.onLine)
            event.respondWith(fetch(event.request));
      else
            event.respondWith(new Response("Vous n'êtes pas connecté à internet"));
});


Depuis l’événement fetch du service worker on peut faire un traitement avec la requête comme modifier les headers, logger la requête, … On peut aussi choisir comment répondre à la requête via la méthode respondWith. Dans l’exemple ci-dessus, on log les requêtes dans la console, puis si le navigateur est connecté à internet on fait la requête au serveur sinon on renvoi un message.

Cela peut nous permettre de facilement afficher une page d’erreur personnalisée pour notre application dans le cas où l’utilisateur est hors-ligne.


Le navigateur va interrompre l’exécution du service worker dès qu’il n’y a pas de requête à traiter, si on a besoin de faire un traitement dans le service worker, comme précharger des ressources dans le cache, ou le nettoyer, il faut utiliser event.waitUntil pour faire ce traitement.



La gestion du cache


Afficher une page d’erreur c’est bien, mais on ne peut pas dire que c’est rendre une application disponible hors-ligne. Mais on va pouvoir facilement le faire grâce à l’API Cache.

(https://developer.mozilla.org/fr/docs/Web/API/Cache)

A ne pas confondre avec le cache standard du navigateur, ou avec l’HTML 5 Application cache. L’API cache est une API javascript qui nous permet de manipuler une base de données de paires clés/valeurs où les clés sont des requêtes http et les valeurs sont des réponses http.


Cette API se manipule via l’objet caches qui est accessible depuis la page ou depuis le service worker :

 

caches.open("mon-cache").then(cache => {   
   cache.add("/index.html"); //Ajoute dans le cache la reponse à la requete vers index.hmtl
   ...
   let response = cache.match(request); //récupère dans le cache la réponse associé à une requête

});


On commence par ouvrir un cache nommé. Une fois ce cache ouvert, on peut ajouter des paires requêtes / réponses et chercher des réponses associées à des requêtes.

On va généralement avoir deux caches dans une application. Un pour les ressources statiques et un pour les ressources dynamiques.



Les stratégies de mise en cache


https://serviceworke.rs/caching-strategies.html


Maintenant qu’on peut utiliser l’API cache pour enregistrer les réponses aux requêtes http, on va pouvoir répondre aux requêtes depuis le service worker.

Il est important de déterminer quelle stratégie de mise en cache on veut mettre en place dans notre application. Cette stratégie dépendra de si on veut afficher des données à jours ou si on veut une réponse rapide.

Dans le service worker on va généralement regarder l’url de la requête et répondre avec la stratégie de mise en cache adaptée.


Stratégie de mise en cache Cache-First


Cache-Only

Cette stratégie consiste à ne répondre qu’avec le contenue du cache. Cela permet d’avoir un temps de réponse très rapide. Cette stratégie est généralement utilisée pour les ressources statiques (images, scripts, styes, …)


self.addEventListener("fetch", (event) => 
   event.respondWith(caches.open("cache-statique").then(cache =>
           cache.match(event.request)
       )
   )
);


Network only

Cette stratégie consiste à ne répondre qu'avec des données provenant du serveur. Cette stratégie est adaptée quand on veut n’afficher que des données à jours. Mais cela ne marchera pas si l’utilisateur est déconnecté.



Network-first

Cette stratégie consiste à répondre avec une des données provenant du serveur et à mettre cette réponse en cache si l’utilisateur est en ligne. S’il est hors ligne on répond avec des données provenant du cache. Cette stratégie s’assure que les données affichées sont à jour tout en permettant à l’utilisateur de revenir sur la page en mode hors-ligne.

L’inconvénient de cette méthode est que la page s’affiche plus rapidement en mode hors-ligne qu’en mode connecté.



Cache first

Cette stratégie consiste à aller d’abord chercher dans le cache, et si la réponse n’est pas trouvée à interroger le serveur et mettre la réponse du serveur en cache.


  self.addEventListener("fetch", (event) => {
       event.respondWith(caches.open("cache-dynamique").then(cache =>
           cache.match(event.request).then(cResponse => {
               if (cResponse)
                   return cResponse;
               return fetch(event.request).then(fResponse =>
                   cache.put(event.request, fResponse.clone())
                       .then(() => fResponse)
               );
           })
       ));
   });


Cette stratégie est recommandée dans la plupart des cas. Elle permet d’avoir un temps de réponses rapide pour les utilisateurs ayant déjà visité la page et permet à la page de s’afficher en mode hors-ligne.



Cache and Update

La stratégie cache and update est une variante de cache first, on répond en priorité en utilisant le cache pour assurer un temps de réponse rapide, mais après avoir répondu on met à jour ce cache pour s’assurer que la prochaine fois que la requête est faite les données seront à jour.


self.addEventListener("fetch", (event) => {
   event.respondWith(caches.open("mon-cache").then(cache =>
       cache.match(event.request).then(cResponse => {
           if (cResponse) {
               event.waitUntil(fetch(event.request).then(fResponse =>
                   cache.put(event.request, fResponse)
               ));
               return cResponse;
           } else {
               return fetch(event.request).then(fResponse =>
                   cache.put(event.request, fResponse.clone())
                       .then(() => fResponse)
               );
           }
       })
   ));
});



Avec le service worker et l'API cache il est très simple de rendre une application web disponible en mode hors ligne en choisissant précisément comment répondre à chaque requête en fonction des besoins de l'application. Le service worker étant un fichier javascript à part on peut facilement en ajouter un à une application existante avec très peu de modification à faire sur l'application.


Commentaires :

Aucun commentaires pour le moment


Laissez un commentaire :

Réalisé par
Expaceo