Mettre à disposition une expérience offline riche, effectuer des synchronisations en arrière-plan ou encore envoyer des notifications push : toutes ces fonctionnalités sont aujourd'hui réalisables dans nos navigateurs Web grâce aux fondations mises à disposition par l'API Service Worker.

Cette API a cependant quelques pré-requis :

  • Bien que cela n'est pas nécessaire en développement avec localhost, en production il faudra utiliser HTTPS pour servir la page installant le worker.
  • Avoir un navigateur compatible, bien qu'aujourd'hui le support est plutôt bien répandu : https://jakearchibald.github.io/isserviceworkerready/

Qu'est-ce qu'un service worker ?

Un service worker est un script Javascript qui tourne en arrière-plan de la page web dans un web worker.

Bien qu'il permette de faire de nombreuses choses, sa principale fonctionnalité est la possibilité de se positionner comme proxy inversé entre l'application et le réseau, lui permettant d'intercepter et de traiter les requêtes tout en gérant de manière programmatique une mise en cache au niveau applicatif.

Au cours de sa vie, le script du service worker passe par plusieurs états ayant chacun leurs propres spécificités. Son cycle de vie est à part de celui de la page web.

Notes : le script  du worker se termine lorsqu'il n'est pas utilisé et redémarre lorsque cela est nécessaire. Le navigateur est libre de le terminer et de le redémarrer la prochaine fois qu'un événement est déclenché. Selon la mémoire du système, ce dernier pourra choisir de le laisser allumer pour gagner du temps d'initialisation ou bien de l'éteindre pour économiser des ressources.

On ne peut donc pas gérer d'état global dans un service worker, il faudra utiliser une API comme IndexedDB afin de persister un état.

Attention : toujours utiliser de l'asynchronicité et éviter les librairies synchrones comme localstorage ou XmlHttpRequest.

Le cycle de vie d'un service worker

Le cycle de vie d'un service worker

I — L'Installation

Le premier état est l'installation (install), pour installer un service worker il faut l'enregistrer programmatiquement.

Durant cette phase, on met généralement en cache des éléments essentiels permettant le fonctionnement de la page. Cela afin de bâtir les fondations de toute la stratégie de mise en cache que l'on incorporera par la suite.

Notes : si n'importe lequel des téléchargements ayant vocation à remplir le cache échoue, alors l'installation du service worker échouera également. Ce dernier essayera alors la prochaine fois ces téléchargements.

Cette fonctionnalité permet de garantir que lorsque le worker est installé on peut sereinement bâtir la suite des éléments de notre nouvelle expérience riche.

II — L'activation

Le second état est l'activation (activate), le worker rentre dans cette phase après la phase d'installation où après la phase waiting lors d'une mise à jour, nous verrons cela dans un prochain article.

Dans cette phase le worker prend le contrôle de la page et on peut exécuter d'autres instructions. C'est la phase idéale pour nettoyer d'anciens éléments du cache lors d'une mise à jour ou pour effectuer la migration entre deux versions de worker.

III — En attente, traitement, terminé

Dans cette phase le worker sera au repos (idle), il attendra que des requêtes passent par le réseau ou que des messages soient déclenchés par la page pour traiter les actions définies. C'est à ce moment-là qu'il peut passer à l'état terminated selon le bon vouloir du système et de l'utilisation de la mémoire.


Le schéma suivant décri la séquence d'exécution des instructions dans chaque thread : le navigateur (main) et le worker.

Séquence du cycle de vie d'un service worker

Installer un service worker

Définition du fichier du service worker

Voici le contenu d'exemple du fichier service-worker.js que nous utiliserons plus tard :

// On écoute l'évènement install pour effectuer les actions de démarrage
self.addEventListener("install", function(event) {
  // Ici on utilise waitUntil qui attends une promesse pour valider l'installation
  event.waitUntil(
    caches.open("mon-site-cache-v1").then(function(cache) {
      // On met en cache une liste d'URLs qui sont la fondation de notre app.
      return cache.addAll([
          "/",
          "/styles/main.css", 
          "/script/core.js"
      ]);
    })
  );
});

// On indique que pour chaque requêtes, si cela match nos URLs de cache défini plus haut, alors on servira le cache plutôt que d'effectuer la vrai requête par le réseau.
self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache HIT, on retourne la réponse en cache.
        if (response) {
          return response;
        }
        
        // Sinon on effectue la requête réellement et on retourne son contenu.
        return fetch(event.request);
      }
    )
  );
});

Bien qu'il ne serve actuellement qu'à peu de choses ce worker à le mérite de permettre de consulter la page d'accueil sans connexion internet. Pour plus de détails sur chacune des méthodes utilisées, je vous invite à consulter la documentation dédiée :

Passons maintenant à l'enregistrement de notre worker.

Enregistrement

On vérifie tout d'abord que le navigateur possède l'API service worker et on s'abonne ensuite à l'évènement load de la fenêtre. Une fois celui-ci déclenché, on précise l'URL à laquelle on peut accéder au script du service worker.

if ("serviceWorker" in navigator) {
  window.addEventListener("load", function() {
    navigator.serviceWorker.register("/service-worker.js").then(
      function(registration) {
        console.log("Enregistrement OK - Scope : ", registration.scope);
      },
      function(err) {
        console.log("Erreur d'enregistrement : ", err);
      }
    );
  });
}
Enregistrement d'un script de service worker

Attention, le fichier se trouve actuellement à la racine du serveur /service-worker.js ce qui lui donne accès à tout le domaine, si jamais il se trouvait dans un sous dossier comme par exemple dans /autre/service-worker.js, alors il ne pourrait intercepter que les requêtes dont l'URL match le pattern /autre/**/*.


Voilà ! Désormais vous avez accès à votre page même sans Internet.

Ceci conclu ce premier article sur les service worker, en espérant que ce dernier puisse vous éclairer un peu sur ce magnifique outil :)