Fonctionnement des service workers
Les travailleurs de service sont puissants et méritent absolument d'être appris. Elles vous permettent d'offrir une expérience sans précédent à vos utilisateurs. Votre site peut se charger instantanément. Il peut fonctionner hors connexion. Elle peut être installée en tant qu'application spécifique à une plate-forme et offrir une expérience tout aussi soignée, mais avec la couverture et la liberté du Web.
Cependant, les services workers ne ressemblent à rien de ce à quoi la plupart des développeurs Web sont habitués. Elles nécessitent une courbe d'apprentissage abrupte et comportent quelques écueils à surveiller.
J'ai récemment collaboré avec Google Developers sur un projet : Service Workies, un jeu gratuit pour 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 d'imaginer quelques métaphores descriptives. Dans cet article, nous allons explorer ces modèles mentaux et nous familiariser avec les traits paradoxaux qui font des services workers à la fois des éléments difficiles 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 laissent perplexe. En particulier lorsque vous actualisez la page et que vous ne voyez pas vos modifications de code appliquées.
Un nouveau calque
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 située au milieu.
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 de votre site avec 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 Service Worker a son propre cycle de vie, qui est indépendant de l'onglet du navigateur. Une simple actualisation de la page ne suffit pas pour mettre à jour un service worker, tout comme vous ne vous attendriez pas à ce qu'une actualisation de la page mette à jour le code déployé sur un serveur. Chaque couche possède ses propres règles de mise à jour.
Dans le jeu Service Workers, nous vous expliquons en détail le cycle de vie des service workers et vous donnons de nombreuses occasions de les utiliser.
Puissant, mais limité
Un service worker sur votre site vous 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 ;
- être installée en tant que PWA ;
Bien que les service workers puissent effectuer de nombreuses tâches, ils sont limités par leur conception. Ils ne peuvent rien faire de synchrone ni dans le même thread que votre site. Vous n'aurez donc pas 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, mais courte durée
Un service worker actif continue de fonctionner même après qu'un utilisateur a quitté votre site ou fermé l'onglet. Le navigateur conserve ce service worker afin qu'il soit prêt la prochaine fois que l'utilisateur reviendra sur votre site. Avant 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 diffuser une version mise en cache de la page elle-même, même si l'utilisateur n'est pas connecté à Internet.
Dans Service Workers, nous visualisons ce concept avec Kolohe (un service worker amical) qui intercepte et gère les requêtes.
Arrêté
Bien que les services workers semblent immortels, ils peuvent être arrêtés presque à tout moment. Le navigateur ne veut pas gaspiller de ressources sur un service worker qui ne fait rien pour le moment. L'arrêt n'est pas la même chose que l'arrêt : le service worker reste installé et activé. Il est simplement mis en veille. La prochaine fois qu'il en aura besoin (par exemple, pour traiter une requête), le navigateur le réactivera.
waitUntil
Étant donné la possibilité constante d'être mis en veille, votre service worker doit pouvoir indiquer au navigateur quand il effectue une tâche importante et qu'il n'a pas envie de faire la sieste. C'est là que event.waitUntil()
entre en jeu. Cette méthode étend le cycle de vie dans lequel elle est utilisée, ce qui l'empêche d'être arrêtée et de passer à la phase suivante de son cycle de vie tant que nous ne sommes pas prêts. Cela nous laisse le temps de configurer des caches, de récupérer des ressources sur le 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"]);
})
);
});
Attention à l'état global
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, 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;
});
À chaque requête, ce service worker consigne un nombre (mettons 0.13981866382421893
). La variable hasHandledARequest
est également remplacée par true
. Le service worker est maintenant inactif pendant un certain temps, de sorte que le navigateur l'arrête. La prochaine fois qu'une requête est envoyée, le service worker est à nouveau nécessaire. Le navigateur le réveille 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 vous fier à 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 sur les service workers, nous avons vu que notre service worker arrêté perdait toute couleur en attendant d'être réveillé.
Ensemble, mais séparés
Votre page ne peut être contrôlée que par un seul 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 actualisez la page, vous ne modifiez pas du tout votre service worker. Les services workers sont immuables. Vous en créez un tout nouveau. Ce nouveau service worker (appelons-le SW2) s'installe, mais il ne s'active pas encore. Il doit attendre la fin du service worker actuel (SW1) (lorsque l'utilisateur quitte votre site).
Interférer avec les caches d'un autre service worker
Lors de l'installation, SW2 peut configurer des éléments, généralement en créant et en remplissant des caches. Attention : ce nouveau service worker a accès à tout ce à quoi 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 peut 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 skipWaiting
Un service worker peut également utiliser la méthode skipWaiting()
à risque pour prendre le contrôle de la page dès qu'elle est installée. Ce n'est généralement pas une bonne idée, sauf si vous essayez intentionnellement de remplacer un service worker défectueux. 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 par un nettoyage
Pour éviter que vos services workers ne se détruisent mutuellement, assurez-vous qu'ils utilisent des caches différents. Le moyen le plus simple d'y parvenir consiste à versionner les 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.
Nettoyage de fin
Une fois que votre service worker atteint l'état activated
, vous savez qu'il a pris le relais et que le service worker précédent est redondant. À ce stade, il est important de nettoyer l'ancien service worker. Non seulement il respecte les limites de stockage du cache de vos utilisateurs, mais il permet également d'éviter les bugs involontaires.
La méthode caches.match()
est un raccourci souvent utilisé pour récupérer un élément à partir de n'importe quel cache où il existe une correspondance. Cependant, il effectue une itération dans les caches dans l'ordre dans lequel ils ont été créés. Supposons que vous disposiez 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 plus récent stocké dans assets-2
. Toutefois, si vous n'avez pas supprimé l'ancien cache, caches.match('app.js')
renverra l'ancien de assets-1
et votre site risque de ne pas fonctionner.
Pour nettoyer les anciens services workers, il suffit de supprimer tout 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 s'entrechoquer demande un peu de travail et de discipline, mais cela en vaut la peine.
État d'esprit des service workers
Adopter le bon état d'esprit lorsque vous réfléchissez aux services workers vous aidera à créer le vôtre en toute confiance. Une fois que vous aurez compris comment les utiliser, vous pourrez créer des expériences incroyables pour vos utilisateurs.
Si vous souhaitez comprendre tout cela en jouant à un jeu, vous avez de la chance. Jouez à Service Workers pour découvrir les secrets des service workers et vaincre les bêtes hors connexion.