État d'esprit des service workers

Fonctionnement des service workers

Les service workers sont puissants et valent vraiment la peine d'être appris. Elles vous permettent d'offrir une expérience sans précédent à vos utilisateurs. 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 vous offrir une expérience optimale, tout en bénéficiant de la portée et de la liberté du Web.

Mais les service workers sont différents de ceux auxquels la plupart d'entre nous les développeurs Web sont habitués. Ils sont soumis à une phase d'apprentissage raide et présentent quelques problèmes à prendre en compte.

Google Developers et moi avons récemment collaboré sur un projet : Service Workies. Il s'agit d'un jeu sans frais visant à comprendre les service workers. Lors de sa conception et de mon travail sur les tenants et les aboutissants des service workers, j'ai rencontré quelques problèmes. Ce qui m'a le plus aidé, c'est de trouver quelques métaphores représentatives. Dans ce post, 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 géniaux.

La même chose, mais différente

Lorsque vous codez votre service worker, de nombreuses choses vous sembleront familières. Vous pouvez utiliser vos nouvelles fonctionnalités de langage JavaScript préférées. Vous écoutez les événements de cycle de vie de la même manière que 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 trompent. en particulier lorsque vous actualisez la page et que les modifications apportées au code ne sont pas appliquées.

Un nouveau calque

Normalement, lors de la création d'un site, vous n'avez que deux couches à prendre en compte: le client et le serveur. Le service worker est une toute nouvelle couche intermédiaire.

Un service worker sert 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 installé, le service worker étend le navigateur à votre site en y ajoutant une couche intermédiaire puissante. Cette couche de service worker peut intercepter et gérer toutes les requêtes effectuées par votre site.

La couche Service Worker a son propre cycle de vie, qui est indépendant de l'onglet du navigateur. Il ne suffit pas d'actualiser une page pour mettre à jour un service worker, tout comme vous ne vous attendez pas à ce qu'une actualisation de page mette à jour le code déployé sur un serveur. Chaque couche possède ses propres règles de mise à jour.

Le jeu Service Workies couvre les nombreux détails du cycle de vie des service workers et vous permet de vous entraîner à l'utiliser.

Puissant, mais limité

La présence d'un service worker sur votre site offre des avantages incroyables. Votre site peut:

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

Tout ce que les service workers peuvent faire, est limité par leur conception. Ils ne peuvent effectuer aucune opération synchrone ni sur le même fil de discussion que votre site. Vous n'aurez donc 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 différentes manières, y compris via des postMessage directs, des canaux de messagerie privés et des canaux de diffusion "un à plusieurs".

Longue durée de vie, mais de courte durée

Un service worker actif reste actif même après qu'un utilisateur quitte votre site ou ferme l'onglet. Le navigateur conserve ce service worker pour qu'il soit prêt la prochaine fois que l'utilisateur reviendra sur votre site. Avant l'envoi de la toute première requête, le service worker a la possibilité de l'intercepter et de prendre le contrôle de la page. C'est ce qui permet à un site de fonctionner hors connexion : le service worker peut afficher lui-même 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 en montrant que Kolohe (un service worker sympathique) intercepte et traite les requêtes.

Arrêté

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

waitUntil

Étant donné la possibilité permanente de se mettre en veille, votre service worker doit pouvoir informer le navigateur lorsqu'il est en train d'effectuer une action importante et qu'il n'a pas l'impression de faire une sieste. C'est là que event.waitUntil() entre en jeu. Cette méthode prolonge le cycle de vie dans lequel elle est utilisée, en empêchant son arrêt et en l'empêchant de passer à la phase suivante de son cycle de vie jusqu'à ce que nous soyons prêts. Cela nous laisse le temps de configurer les caches, d'extraire les 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"]);
    })
  );
});

Méfiez-vous de l'état global

Lorsque cela 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, sinon vous serez 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;
});

Pour chaque requête, ce service worker consigne un numéro, par exemple 0.13981866382421893. La variable hasHandledARequest devient également true. Le service worker reste inactif pendant un certain temps, c'est pourquoi le navigateur l'arrête. La prochaine fois qu'une requête est envoyée, le service worker est de nouveau requis. Le navigateur l'active donc. Son script est réévalué. hasHandledARequest est désormais 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. De plus, la création d'instances telles que des canaux de message peut provoquer des bugs: vous obtenez une toute nouvelle instance chaque fois que le service worker s'arrête ou redémarre.

Dans le chapitre 3 de Service Workies, nous visualisons le service worker arrêté comme en train de perdre toute sa couleur en attendant son activation.

visualisation d'un service worker arrêté

Ensemble, mais séparez

Un seul service worker à la fois peut contrôler votre page. Mais 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 n'apportez aucune modification à votre service worker. Les service workers sont immuables. Vous en créez un tout nouveau. Ce nouveau service worker (appelé SW2) va s'installer, mais ne s'activera pas encore. Il doit attendre que le service worker actuel (SW1) s'arrête (lorsque l'utilisateur quitte votre site).

Exécuter des erreurs dans les caches d'un autre service worker

Lors de l'installation, le logiciel SW2 peut configurer les éléments, 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'êtes pas vigilant, le nouveau service worker en attente peut vraiment perturber votre service worker actuel. Voici quelques exemples susceptibles de vous causer des problèmes:

  • SW2 pourrait supprimer un cache que SW1 utilise activement.
  • SW2 peut modifier le contenu d'un cache utilisé par SW1, ce qui amène SW1 à répondre avec des éléments que la page n'attend pas.

Ignorer "sauteenattente"

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

Commencer le nettoyage

Pour éviter que vos service workers s'interfèrent entre eux, assurez-vous qu'ils utilisent des caches différents. Le moyen le plus simple d'y parvenir est de 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 faire remonter le version pour qu'il fasse ce dont il a besoin avec un cache entièrement distinct de celui de l'ancien service worker.

la visualisation d'un cache

Terminer le nettoyage

Lorsque votre service worker passe à l'état activated, vous savez qu'il a pris le relais et que l'ancien service worker est redondant. À ce stade, il est important d'effectuer un nettoyage après l'ancien service worker. Non seulement elle respecte les besoins les limites de stockage en cache, mais cela peut également éviter les bugs involontaires.

La méthode caches.match() est un raccourci fréquemment utilisé pour récupérer un élément de n'importe quel cache en cas de correspondance. Cependant, il effectue une itération dans les caches dans l'ordre dans lequel ils ont été créés. Supposons que vous disposez de 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 renvoyer l'ancien à partir de assets-1, ce qui aura probablement pour effet d'endommager votre site.

Une fois les service workers précédents nettoyés, il suffit de supprimer le cache dont le nouveau service 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 du service worker

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

Si vous voulez comprendre tout cela en jouant à un jeu, vous avez de la chance ! Jouez à Service Workies pour découvrir les méthodes du service worker pour vaincre les bêtes hors connexion.