La vie d'un service worker

Il est difficile de savoir ce que font les service workers sans comprendre leur cycle de vie. Leur fonctionnement interne semblera opaque, voire arbitraire. Rappelez-vous que, comme pour toute autre API de navigateur, les comportements des service workers sont bien définis, et rendre possibles les applications hors connexion, tout en facilitant les mises à jour sans perturber l'expérience utilisateur.

Avant de vous plonger dans Workbox, il est important de comprendre le cycle de vie du service worker pour que Workbox soit logique.

Définition des termes

Avant d'aborder le cycle de vie d'un service worker, il est utile de définir quelques termes sur le fonctionnement de ce cycle de vie.

Contrôle et portée

L'idée de contrôle est essentielle pour comprendre le fonctionnement des service workers. Une page décrite comme contrôlée par un service worker est une page qui permet à un service worker d'intercepter les requêtes réseau en son nom. Le service worker est présent et peut effectuer des opérations sur la page dans le cadre d'un champ d'application donné.

Champ d'application

Le champ d'application d'un service worker est déterminé par son emplacement sur un serveur Web. Si un service worker s'exécute sur une page située sous /subdir/index.html et se trouve à l'adresse /subdir/sw.js, le champ d'application du service worker est /subdir/. Pour voir le concept de portée en action, consultez cet exemple:

  1. Accéder à https://service-worker-scope-viewer.glitch.me/subdir/index.html. Un message s'affiche pour indiquer qu'aucun service worker ne contrôle la page. Cependant, cette page enregistre un service worker depuis https://service-worker-scope-viewer.glitch.me/subdir/sw.js.
  2. Actualisez la page. Étant donné que le service worker a été enregistré et qu'il est désormais actif, elle contrôle la page. Un formulaire contenant le champ d'application du service worker son état actuel, et son URL sera visible. Remarque: l'actualisation de la page n'a rien à voir avec la portée, mais plutôt le cycle de vie d'un service worker, qui sera expliqué plus tard.
  3. Accédez maintenant à https://service-worker-scope-viewer.glitch.me/index.html. Même si un service worker a été enregistré sur cette origine, un message indique qu'il n'y a pas de service worker actuel. Cela est dû au fait que cette page n'entre pas dans le champ d'application du service worker enregistré.

Le champ d'application limite les pages contrôlées par le service worker. Dans cet exemple, cela signifie que le service worker chargé depuis /subdir/sw.js ne peut contrôler que les pages situées dans /subdir/ ou dans sa sous-arborescence.

Ce qui précède illustre le fonctionnement du champ d'application par défaut. Toutefois, le champ d'application maximal autorisé peut être ignoré en définissant l'en-tête de réponse Service-Worker-Allowed, ainsi qu'une scope à la méthode register.

À moins qu'il n'y ait une très bonne raison de limiter le champ d'application d'un service worker à un sous-ensemble d'une origine, charger un service worker à partir du répertoire racine du serveur Web afin que son champ d'application soit aussi étendu que possible ; sans vous soucier de l'en-tête Service-Worker-Allowed. C'est beaucoup plus simple pour tout le monde de cette façon.

Client

Lorsqu'on dit qu'un service worker contrôle une page, il contrôle en fait un client. Un client est une page ouverte dont l'URL relève du champ d'application de ce service worker. Plus précisément, il s'agit d'instances de WindowClient.

Cycle de vie d'un nouveau service worker

Pour qu'un service worker contrôle une page, elle doit d'abord être créée, pour ainsi dire. Commençons par voir ce qui se passe lorsqu'un nouveau service worker est déployé pour un site Web sans service worker actif.

Inscription

L'enregistrement est la première étape du cycle de vie d'un service worker:

<!-- In index.html, for example: -->
<script>
  // Don't register the service worker
  // until the page has fully loaded
  window.addEventListener('load', () => {
    // Is service worker available?
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js').then(() => {
        console.log('Service worker registered!');
      }).catch((error) => {
        console.warn('Error registering service worker:');
        console.warn(error);
      });
    }
  });
</script>

Ce code s'exécute sur le thread principal et effectue les opérations suivantes:

  1. Étant donné que la première visite de l'utilisateur sur un site Web a lieu sans service worker enregistré, attendez que la page soit entièrement chargée avant d'en enregistrer une. Cela permet d'éviter les conflits de bande passante si le service worker met en cache quoi que ce soit.
  2. Bien que le service worker dispose d'une bonne assistance, une vérification rapide permet d’éviter les erreurs dans les navigateurs où elle n’est pas prise en charge.
  3. Une fois la page entièrement chargée, et si le service worker est compatible, enregistrez /sw.js.

Voici quelques points essentiels à retenir:

  • Les service workers sont uniquement disponible via HTTPS ou localhost.
  • Si le contenu d'un service worker contient des erreurs de syntaxe, l'enregistrement échoue et le service worker est supprimé.
  • Rappel: Les service workers opèrent dans un périmètre donné. Ici, le champ d'application correspond à l'origine complète, puisqu'il a été chargé depuis le répertoire racine.
  • Lorsque l'enregistrement commence, l'état du service worker est défini sur 'installing'.

Une fois l'enregistrement terminé, l'installation commence.

Installation

Un service worker déclenche sa install après votre inscription. install n'est appelé qu'une seule fois par service worker et ne se déclenchera qu'une fois qu'il aura été mis à jour. Un rappel pour l'événement install peut être enregistré dans le champ d'application du worker avec addEventListener:

// /sw.js
self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v1';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v1'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.bc7b80b7.css',
      '/css/home.fe5d0b23.css',
      '/js/home.d3cc4ba4.js',
      '/js/jquery.43ca4933.js'
    ]);
  }));
});

Cette opération crée une instance Cache et met en pré-cache les éléments. Nous aurons de nombreuses occasions de parler de la pré-mise en cache plus tard, Concentrons-nous donc sur le rôle event.waitUntil event.waitUntil accepte une promesse, et attend que cette promesse soit résolue. Dans cet exemple, la promesse fait deux choses asynchrones:

  1. Il crée une instance Cache nommée 'MyFancyCache_v1'.
  2. Une fois le cache créé, Un tableau d'URL d'éléments est mis en pré-cache à l'aide de la méthode Méthode addAll.

L'installation échoue si les promesses transmises à event.waitUntil sont refusé. Dans ce cas, le service worker est supprimé.

Si les promesses sont résolues, l'installation réussit et l'état du service worker passe à 'installed' avant l'activation.

Activation

Si l'enregistrement et l'installation réussissent, le service worker s'active, et son état devient 'activating'. Le travail peut être effectué pendant l'activation dans le service worker Événement activate. Une tâche typique dans cet événement consiste à élaguer les anciens caches, mais pour un nouveau service worker, ce n'est pas pertinent pour le moment, et que nous développerons lorsque nous parlerons des mises à jour des service workers.

Pour les nouveaux service workers, activate se déclenche immédiatement après la réussite de install. Une fois l'activation terminée, l'état du service worker devient 'activated'. Notez que, par défaut, le nouveau service worker ne commencera à contrôler la page qu'à la prochaine navigation ou actualisation de la page.

Gérer les mises à jour des service workers

Une fois le premier service worker déployé, il devra probablement être mis à jour ultérieurement. Par exemple, une mise à jour peut être requise si des modifications sont apportées à la logique de gestion des requêtes ou de mise en cache préalable.

À quel moment les mises à jour sont-elles effectuées ?

Les navigateurs recherchent les mises à jour d'un service worker dans les cas suivants:

  • L'utilisateur accède à une page appartenant au champ d'application du service worker.
  • navigator.serviceWorker.register() est appelé avec une URL différente de celle du service worker installé, mais ne modifiez pas l'URL d'un service worker.
  • navigator.serviceWorker.register() est appelé avec la même URL que le service worker installé. mais avec une portée différente. Là encore, évitez cela en conservant si possible le champ d'application à la racine d'une origine.
  • Lorsque des événements tels que 'push' ou 'sync' ont été déclenchés au cours des dernières 24 heures, mais ne vous inquiétez pas pour le moment.

Comment les mises à jour sont-elles effectuées ?

Il est important de savoir quand le navigateur met à jour un service worker, mais il en va de même pour le "comment". En supposant que l'URL ou le champ d'application d'un service worker n'est pas modifié, un service worker installé n'effectue la mise à jour vers une nouvelle version que si son contenu a été modifié.

Les navigateurs détectent les modifications de différentes manières:

  • Toute modification octet par octet des scripts demandée par importScripts, le cas échéant
  • Toute modification du code de premier niveau du service worker ce qui affecte l'empreinte que le navigateur l'a générée.

Dans ce cas, le navigateur effectue une grande partie du travail. Pour s'assurer que le navigateur dispose de tout ce dont il a besoin pour détecter de manière fiable les modifications apportées au contenu d'un service worker, ne dites pas au cache HTTP de le conserver et ne changez pas son nom de fichier. Le navigateur effectue automatiquement des vérifications de mises à jour lorsqu'une navigation vers une nouvelle page du champ d'application d'un service worker est lancée.

Déclencher manuellement la vérification des mises à jour

Concernant les mises à jour, la logique d'enregistrement ne devrait généralement pas changer. Cependant, il existe une exception à cette règle si les sessions d'un site Web ont une longue durée de vie. Cela peut se produire dans les applications monopages Les requêtes de navigation sont rares, car l'application rencontre généralement une requête de navigation au début de son cycle de vie. Dans ces situations, une mise à jour manuelle peut être déclenchée sur le thread principal:

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

Pour les sites Web traditionnels, ou dans tous les cas où les sessions utilisateur n'ont pas une longue durée de vie, déclencher des mises à jour manuelles n'est probablement pas nécessaire.

Installation

Lorsque vous utilisez un bundler pour générer des éléments statiques, ces éléments contiendront des hachages dans leur nom, comme framework.3defa9d2.js. Supposons que certains de ces éléments soient mis en pré-cache pour un accès hors connexion ultérieur. Cette opération nécessite une mise à jour du service worker pour mettre en pré-cache les éléments mis à jour:

self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v2';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v2'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.ced4aef2.css',
      '/css/home.cbe409ad.css',
      '/js/home.109defa4.js',
      '/js/jquery.38caf32d.js'
    ]);
  }));
});

Deux choses sont différentes de l'exemple d'événement install précédent:

  1. Une instance Cache avec une clé 'MyFancyCacheName_v2' est créée.
  2. Les noms des éléments mis en pré-cache ont changé.

Notez qu'un service worker mis à jour est installé en même temps que le précédent. Cela signifie que l'ancien service worker contrôle toujours les pages ouvertes. Après l'installation, le nouveau passe en état d'attente jusqu'à ce qu'il soit activé.

Par défaut, un nouveau service worker s'active lorsqu'aucun client n'est contrôlé par l'ancien. Cela se produit lorsque tous les onglets ouverts du site Web concerné sont fermés.

Activation

Lorsqu'un service worker mis à jour est installé et que la phase d'attente se termine, il est activé, et l'ancien service worker est supprimé. Une tâche courante dans l'événement activate d'un service worker mis à jour consiste à éliminer les anciens caches. Supprimez les anciens caches en obtenant les clés de toutes les instances Cache ouvertes avec caches.keys et supprimer les caches qui ne figurent pas dans une liste d'autorisation définie avec caches.delete:

self.addEventListener('activate', (event) => {
  // Specify allowed cache keys
  const cacheAllowList = ['MyFancyCacheName_v2'];

  // Get all the currently active `Cache` instances.
  event.waitUntil(caches.keys().then((keys) => {
    // Delete all caches that aren't in the allow list:
    return Promise.all(keys.map((key) => {
      if (!cacheAllowList.includes(key)) {
        return caches.delete(key);
      }
    }));
  }));
});

Les anciens caches ne s'organisent pas tout seuls. Nous devons le faire nous-mêmes sous peine de dépasser quotas de stockage. Comme 'MyFancyCacheName_v1' du premier service worker est obsolète, la liste d'autorisation du cache est mise à jour pour spécifier 'MyFancyCacheName_v2', ce qui supprime les caches sous un nom différent.

L'événement activate prendra fin une fois l'ancien cache supprimé. Le nouveau service worker prend alors le contrôle de la page, et de remplacer enfin l'ancien !

Le cycle de vie ne change pas

Si Workbox est utilisé pour gérer le déploiement et les mises à jour d'un service worker, ou si vous utilisez directement l'API Service Worker, comprendre le cycle de vie d'un service worker. Les comportements des service workers devraient donc sembler plus logiques que mystérieux.

Pour ceux qui souhaitent approfondir le sujet, cela vaut la peine de consulter cet article de Jake Archibald. Il y a des tonnes de nuances dans l’ensemble du cycle de vie du service, mais elles sont connues, et ces connaissances vous seront très utiles si vous utilisez Workbox.