État d'esprit des service workers

Comment réfléchir aux service workers.

Les service workers sont puissants et valent la peine d'être appris. Elles vous permettent d'offrir une expérience utilisateur inédite. Votre site peut se charger instantanément. Elle peut fonctionner hors connexion. Elle peut être installée en tant qu'application spécifique à une plate-forme et parfaitement soignée, tout en bénéficiant de la portée et de la liberté du Web.

Mais les service workers ne sont pas comme la plupart de nous, développeurs Web. La phase d'apprentissage est parfois difficile, et vous devez faire attention à quelques problèmes.

Avec Google Developers, nous avons récemment collaboré sur le projet Service Workies, un jeu sans frais visant à comprendre les service workers. En le construisant et en travaillant avec les tenants et les aboutissants complexes des service workers, j'ai rencontré quelques problèmes. Ce qui m'a le plus aidé, c'est de trouver quelques métaphores descriptives. Dans cet article, nous allons explorer ces modèles mentaux et nous concentrer sur les caractéristiques paradoxales qui rendent les service workers à la fois délicats et extraordinaires.

Identiques, mais différents

Lors du codage de votre service worker, de nombreux éléments vous seront familiers. Vous allez pouvoir utiliser vos nouvelles fonctionnalités préférées en langage JavaScript. Vous écoutez les événements de cycle de vie de la même manière que pour les événements d'interface utilisateur. Vous gérez le flux de contrôle avec des promesses comme vous en avez l'habitude.

Mais d'autres comportements de service worker vous empêchent de penser. en particulier lorsque vous actualisez la page et que les modifications que vous avez apportées au code n'ont pas été appliquées.

Une nouvelle couche

Normalement, lorsque vous créez un site, vous n'avez que deux couches à prendre en compte: le client et le serveur. Le service worker est une toute nouvelle couche au milieu.

Un service worker joue le rôle de couche intermédiaire entre le client et le serveur

Considérez votre service worker comme une sorte d'extension de navigateur, que votre site peut installer dans le navigateur de l'utilisateur. Une fois l'installation effectuée, le service worker extends le navigateur de votre site à l'aide d'une couche intermédiaire puissante. Cette couche de service worker peut intercepter et gérer toutes les requêtes envoyées par votre site.

La couche du service worker possède son propre cycle de vie, qui est indépendant de l'onglet du navigateur. Une simple actualisation de page ne suffit pas pour mettre à jour un service worker, de la même manière que vous ne vous attendez pas à ce qu'une actualisation de page mette à jour le code déployé sur un serveur. Chaque calque possède ses propres règles de mise à jour.

Dans le jeu Service Workies, nous abordons de nombreux détails sur le cycle de vie des service worker et nous vous proposons de vous entraîner à les utiliser.

Performant, mais limité

Avoir un service worker sur votre site vous offre des avantages considérables. Votre site peut:

  • fonctionner parfaitement, même lorsque l'utilisateur n'est pas connecté à Internet.
  • bénéficier d'améliorations considérables des performances grâce à la mise en cache ;
  • utiliser les notifications push ;
  • Être installée en tant que PWA

Malgré tout ce que les service workers peuvent faire, leur conception est limitée. Elles ne peuvent pas effectuer d'opérations synchrones sur votre site ni sur le même thread. Par conséquent, vous n'avez plus accès aux éléments suivants:

  • localStorage
  • le DOM
  • la fenêtre

La bonne nouvelle, c'est que votre page peut communiquer avec son service worker de plusieurs façons, par exemple via un postMessage direct, des canaux de message un à un et des canaux de diffusion un à plusieurs.

Longue durée, mais éphémère

Un service worker actif continue de vivre même après qu'un utilisateur a quitté votre site ou fermé l'onglet. Le navigateur conserve ce service worker de sorte qu'il soit prêt la prochaine fois que l'utilisateur reviendra sur votre site. Avant l'envoi de la première requête, le service worker peut l'intercepter et prendre le contrôle de la page. C'est ce qui permet à un site de fonctionner hors connexion : le service worker peut afficher une version mise en cache de la page, même si l'utilisateur n'est pas connecté à Internet.

Dans Service Workies, nous visualisons ce concept avec Kolohe, un service worker convivial, qui intercepte et traite les requêtes.

Arrêté

Même si les service workers semblent être immortels, ils peuvent être arrêtés à tout moment. Le navigateur ne veut pas gaspiller des ressources sur un service worker qui n'est pas actif. Arrêter n'est pas la même chose que arrêter : le service worker reste installé et activé. Il est simplement mis en veille. La prochaine fois que cela est nécessaire (pour traiter une requête, par exemple), le navigateur le réactive.

waitUntil

Comme il est possible en permanence de se mettre en veille, votre service worker a besoin d'un moyen d'informer le navigateur lorsqu'il fait quelque chose d'important et qu'il n'a pas envie de faire une sieste. C'est là que event.waitUntil() entre en jeu. Cette méthode étend le cycle de vie dans lequel elle est utilisée, en évitant qu'elle soit arrêtée et en passant à la phase suivante de son cycle de vie jusqu'à ce que nous soyons prêts. Cela nous laisse le temps de configurer des caches, d'extraire des ressources du réseau, etc.

Cet exemple indique au navigateur que l'installation de notre service worker n'est pas terminée tant que le cache assets n'a pas été créé et rempli avec l'image d'une épée:

self.addEventListener("install", event => {
  event.waitUntil(
    caches.open("assets").then(cache => {
      return cache.addAll(["/weapons/sword/blade.png"]);
    })
  );
});

Faites attention à l'état mondial

Lorsque ce démarrage/Arrêt se produit, le champ d'application global du service worker est réinitialisé. Veillez donc à ne pas utiliser d'état global dans votre service worker. Vous risquez d'être triste la prochaine fois qu'il se rallumera avec un état différent de celui attendu.

Prenons l'exemple suivant, qui utilise un état global:

const favoriteNumber = Math.random();
let hasHandledARequest = false;

self.addEventListener("fetch", event => {
  console.log(favoriteNumber);
  console.log(hasHandledARequest);
  hasHandledARequest = true;
});

À chaque requête, ce service worker enregistre un numéro, par exemple 0.13981866382421893. La variable hasHandledARequest passe également à true. Le service worker reste inactif pendant un certain temps, et le navigateur l'arrête. La prochaine fois qu'il y aura une requête, le service worker est de nouveau nécessaire. Le navigateur l'active donc. Son script est évalué à nouveau. hasHandledARequest est maintenant réinitialisé sur false, et favoriteNumber est complètement différent : 0.5907281835659033.

Vous ne pouvez pas compter sur l'état stocké dans un service worker. La création d'instances telles que les canaux de messagerie peut également provoquer des bugs: une nouvelle instance est créée chaque fois que le service worker s'arrête ou redémarre.

Dans le chapitre 3 des Workies de service, nous remarquons que notre service worker arrêté perd toute la couleur en attendant d'être réveillé.

visualisation d'un service worker arrêté

Ensemble, mais séparé

Votre page ne peut être contrôlée que par un service worker à la fois. Toutefois, deux service workers peuvent être installés en même temps. Lorsque vous modifiez le code de votre service worker et que vous actualisez la page, vous ne modifiez en réalité aucun service. Les service workers sont immuables. Au lieu de cela, vous en créez un tout nouveau. Ce nouveau service worker (appelons-le SW2) effectue l'installation, mais ne s'active pas encore. Il doit attendre que le service worker actuel (SW1) soit arrêté (lorsque l'utilisateur quitte votre site).

Interagir avec les caches d'un autre service worker

Lors de l'installation, SW2 peut effectuer la configuration, généralement en créant et en remplissant des caches. Attention: ce nouveau service worker a accès à tous les éléments auxquels le service worker actuel a accès. Si vous n'y faites pas attention, votre nouveau service worker en attente risque de faire obstacle à votre service worker actuel. Voici quelques exemples qui pourraient vous causer des problèmes:

  • SW2 peut supprimer un cache que SW1 utilise activement.
  • SW2 peut modifier le contenu d'un cache utilisé par SW1, entraînant ainsi la réponse de SW1 avec des éléments que la page n'attend pas.

Ignorer "Ignorer en attente"

Un service worker peut également utiliser la méthode risquée skipWaiting() pour prendre le contrôle de la page dès que l'installation est terminée. C'est généralement une mauvaise idée, sauf si vous essayez de remplacer intentionnellement un service worker qui présente des bugs. Le nouveau service worker utilise peut-être des ressources mises à jour inattendues, ce qui peut entraîner des erreurs et des bugs.

Commencer le nettoyage

Pour éviter que vos service workers s'interdisent les uns les autres, assurez-vous qu'ils utilisent des caches différents. Pour ce faire, le moyen le plus simple consiste à gérer les versions des noms de cache qu'ils utilisent.

const version = 1;
const assetCacheName = `assets-${version}`;

self.addEventListener("install", event => {
  caches.open(assetCacheName).then(cache => {
    // confidently do stuff with your very own cache
  });
});

Lorsque vous déployez un nouveau service worker, vous devez remplacer version par un cache entièrement distinct de celui du service worker précédent.

visualisation d'un cache

Nettoyage de fin

Une fois que votre service worker atteint l'état activated, vous savez qu'il a pris le contrôle et que le service worker précédent est redondant. À ce stade, il est important d'effectuer un nettoyage après l'ancien service worker. Non seulement il respecte les limites de stockage du cache de vos utilisateurs, mais il empêche également les bugs involontaires.

La méthode caches.match() est un raccourci fréquemment utilisé pour récupérer un élément dans tous les caches où il existe une correspondance. Mais il parcourt les caches dans l'ordre dans lequel ils ont été créés. Imaginons que vous ayez deux versions d'un fichier de script app.js dans deux caches différents : assets-1 et assets-2. Votre page attend le script le plus récent stocké dans assets-2. Toutefois, si vous n'avez pas supprimé l'ancien cache, caches.match('app.js') va l'afficher de assets-1, ce qui risque d'endommager votre site.

Après avoir effectué un nettoyage après un service worker précédent, il suffit de supprimer le cache dont le nouveau service worker n'a pas besoin:

const version = 2;
const assetCacheName = `assets-${version}`;

self.addEventListener("activate", event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== assetCacheName){
            return caches.delete(cacheName);
          }
        });
      );
    });
  );
});

Empêcher vos service workers de se bloquer les uns les autres demande un peu de travail et de discipline, mais en vaut la peine.

État d'esprit des service workers

En adoptant le bon état d'esprit lorsque vous réfléchissez à vos service workers, vous pouvez créer le vôtre en toute confiance. Une fois que vous maîtriserez ces outils, vous serez en mesure de créer des expériences incroyables pour vos utilisateurs.

Si vous voulez comprendre toutes ces choses en jouant à un jeu, vous avez de la chance ! Découvrez les exercices de service sur la façon dont les service worker peuvent faire l'affaire.