Écrire le script du service worker

Cet atelier de programmation fait partie du cours de formation "Développer des progressive web apps", développé par l'équipe de formation Google Developers. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre.

Pour en savoir plus sur le cours, consultez la présentation du développement de progressive web apps.

Introduction

Dans cet atelier, vous apprendrez à créer un service worker simple et découvrirez son cycle de vie.

Points abordés

  • Créer un script de service worker de base, l'installer et effectuer un débogage simple

À savoir

  • Connaissances de base en JavaScript et HTML
  • Concepts et syntaxe de base des Promesses ES2015
  • Activer la console pour les développeurs

Ce dont vous avez besoin avant de commencer

Téléchargez ou clonez le dépôt pwa-training-labs depuis GitHub et installez la version LTS de Node.js, si nécessaire.

Accédez au répertoire service-worker-lab/app/ et démarrez un serveur de développement local :

cd service-worker-lab/app
npm install
node server.js

Vous pouvez arrêter le serveur à tout moment avec Ctrl-c.

Ouvrez votre navigateur et accédez à localhost:8081/.

Remarque : Annulez l'enregistrement de tous les service workers et effacez tous les caches de service workers pour localhost afin qu'ils n'interfèrent pas avec l'atelier. Dans les outils pour les développeurs Chrome, vous pouvez y parvenir en cliquant sur Effacer les données du site dans la section Effacer le stockage de l'onglet Application.

Ouvrez le dossier service-worker-lab/app/ dans l'éditeur de texte de votre choix. Le dossier app/ est celui dans lequel vous allez créer l'atelier.

Ce dossier contient :

  • below/another.html, js/another.js, js/other.js et other.html sont des exemples de ressources que nous utilisons pour tester le champ d'application du service worker.
  • Le dossier styles/ contient les feuilles de style en cascade pour cet atelier.
  • Le dossier test/ contient des fichiers permettant de tester votre progression.
  • index.html est la page HTML principale de notre exemple de site/application.
  • service-worker.js est le fichier JavaScript utilisé pour créer notre service worker.
  • package.json et package-lock.json suivent les packages de nœuds utilisés dans ce projet.
  • server.js est un simple serveur express que nous utilisons pour héberger notre application.

Ouvrez service-worker.js dans votre éditeur de texte. Notez que le fichier est vide. Nous n'avons pas encore ajouté de code à exécuter dans le service worker.

Ouvrez index.html dans votre éditeur de texte.

Dans les balises <script>, ajoutez le code suivant pour enregistrer le service worker :

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('Service Worker is registered', registration);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

Enregistrez le script et actualisez la page. La console doit renvoyer un message indiquant que le service worker a été enregistré. Dans Chrome, vous pouvez vérifier qu'un service worker est enregistré en ouvrant les outils de développement (Ctrl+Maj+I sous Windows et Linux, ou ⌘+alt+I sous Mac), en cliquant sur l'onglet "Application", puis sur l'option Service workers. Le résultat qui s'affiche doit ressembler à ceci :

Facultatif : Ouvrez le site sur un navigateur non compatible et vérifiez que la condition de vérification de la compatibilité fonctionne.

Explication

Le code ci-dessus enregistre le fichier service-worker.js en tant que service worker. Elle vérifie d'abord si le navigateur est compatible avec les service workers. Vous devez le faire chaque fois que vous enregistrez un service worker, car certains navigateurs ne sont pas compatibles avec les service workers. Le code enregistre ensuite le service worker à l'aide de la méthode register de l'API ServiceWorkerContainer, qui est contenue dans l'interface Navigator de la fenêtre.

navigator.serviceWorker.register(...) renvoie une promesse qui se résout avec un objet registration une fois que le service worker a été enregistré avec succès. Si l'enregistrement échoue, la promesse sera rejetée.

Les modifications de l'état du service worker déclenchent des événements dans le service worker.

Ajouter des écouteurs d'événements

Ouvrez service-worker.js dans votre éditeur de texte.

Ajoutez les écouteurs d'événements suivants au service worker :

self.addEventListener('install', event => {
  console.log('Service worker installing...');
  // Add a call to skipWaiting here
});

self.addEventListener('activate', event => {
  console.log('Service worker activating...');
});

Enregistrez le fichier.

Annulez manuellement l'enregistrement du service worker et actualisez la page pour installer et activer le service worker mis à jour. Le journal de la console doit indiquer que le nouveau service worker a été enregistré, installé et activé.

Remarque  : Il est possible que le journal d'enregistrement ne soit pas dans le même ordre que les autres journaux (installation et activation). Le service worker s'exécute en même temps que la page. Nous ne pouvons donc pas garantir l'ordre des journaux (le journal d'enregistrement provient de la page, tandis que les journaux d'installation et d'activation proviennent du service worker). L'installation, l'activation et d'autres événements de service worker se produisent dans un ordre défini à l'intérieur du service worker et doivent toujours apparaître dans l'ordre attendu.

Explication

Le service worker émet un événement install à la fin de l'enregistrement. Dans le code ci-dessus, un message est consigné dans l'écouteur d'événements install, mais dans une application réelle, ce serait un bon endroit pour mettre en cache les éléments statiques.

Lorsqu'un service worker est enregistré, le navigateur détecte s'il est nouveau (soit parce qu'il est différent du service worker précédemment installé, soit parce qu'aucun service worker n'est enregistré pour ce site). Si le service worker est nouveau (comme c'est le cas ici), le navigateur l'installe.

Le service worker émet un événement activate lorsqu'il prend le contrôle de la page. Le code ci-dessus enregistre un message ici, mais cet événement est souvent utilisé pour mettre à jour les caches.

Un seul service worker peut être actif à la fois pour une portée donnée (voir "Explorer la portée du service worker"). Par conséquent, un service worker nouvellement installé n'est activé que lorsque le service worker existant n'est plus utilisé. C'est pourquoi toutes les pages contrôlées par un service worker doivent être fermées avant qu'un nouveau service worker puisse prendre le relais. Comme nous avons annulé l'enregistrement du service worker existant, le nouveau service worker a été activé immédiatement.

Remarque : Il ne suffit pas d'actualiser la page pour transférer le contrôle à un nouveau service worker, car la nouvelle page sera demandée avant le déchargement de la page actuelle, et il n'y aura pas de moment où l'ancien service worker ne sera pas utilisé.

Remarque : Vous pouvez également activer manuellement un nouveau service worker à l'aide de l'outil de développement de certains navigateurs et de manière programmatique avec skipWaiting(), comme nous l'expliquons dans la section 3.4.

Mettre à jour le service worker

Ajoutez le commentaire suivant n'importe où dans service-worker.js :

// I'm a new service worker

Enregistrez le fichier et actualisez la page. Consultez les journaux dans la console. Vous remarquerez que le nouveau service worker s'installe, mais ne s'active pas. Dans Chrome, vous pouvez voir le service worker en attente dans l'onglet Application des outils pour les développeurs.

Fermez toutes les pages associées au service worker. Rouvrez ensuite localhost:8081/. Le journal de la console doit indiquer que le nouveau service worker est désormais activé.

Remarque : Si vous obtenez des résultats inattendus, assurez-vous que votre cache HTTP est désactivé dans les outils de développement.

Explication

Le navigateur détecte une différence d'octet entre le nouveau fichier de service worker et celui existant (en raison du commentaire ajouté). Le nouveau service worker est donc installé. Étant donné qu'un seul service worker peut être actif à la fois (pour une portée donnée), même si le nouveau service worker est installé, il n'est pas activé tant que le service worker existant n'est plus utilisé. En fermant toutes les pages contrôlées par l'ancien service worker, nous pouvons activer le nouveau.

Écourter la phase d'attente

Il est possible qu'un nouveau service worker s'active immédiatement, même si un service worker existant est présent, en ignorant la phase d'attente.

Dans service-worker.js, ajoutez un appel à skipWaiting dans l'écouteur d'événements install :

self.skipWaiting();

Enregistrez le fichier et actualisez la page. Notez que le nouveau service worker s'installe et s'active immédiatement, même si un service worker précédent était en contrôle.

Explication

La méthode skipWaiting() permet à un service worker de s'activer dès qu'il a terminé l'installation. L'écouteur d'événements d'installation est un endroit courant pour placer l'appel skipWaiting(), mais il peut être appelé n'importe où pendant ou avant la phase d'attente. Pour savoir quand et comment utiliser skipWaiting(), consultez cette documentation. Pour le reste de l'atelier, nous pouvons désormais tester le nouveau code du service worker sans le désenregistrer manuellement.

Pour en savoir plus

Les service workers peuvent servir de proxy entre votre application Web et le réseau.

Ajoutons un écouteur de récupération pour intercepter les requêtes de notre domaine.

Ajoutez le code suivant à service-worker.js :

self.addEventListener('fetch', event => {
  console.log('Fetching:', event.request.url);
});

Enregistrez le script et actualisez la page pour installer et activer le service worker mis à jour.

Vérifiez la console et constatez qu'aucun événement de récupération n'a été enregistré. Actualisez la page et vérifiez à nouveau la console. Cette fois, vous devriez voir des événements de récupération pour la page et ses éléments (comme le CSS).

Cliquez sur les liens Autre page, Une autre page et Retour.

Vous verrez des événements de récupération dans la console pour chacune des pages et leurs ressources. Tous les journaux sont-ils cohérents ?

Remarque : Si vous accédez à une page et que le cache HTTP n'est pas désactivé, les ressources CSS et JavaScript peuvent être mises en cache localement. Dans ce cas, vous ne verrez pas d'événements de récupération pour ces ressources.

Explication

Le service worker reçoit un événement de récupération pour chaque requête HTTP effectuée par le navigateur dans son champ d'application. L'objet fetch event contient la requête. Écouter les événements de récupération dans le service worker est semblable à l'écoute des événements de clic dans le DOM. Dans notre code, lorsqu'un événement de récupération se produit, nous enregistrons l'URL demandée dans la console (en pratique, nous pourrions également créer et renvoyer notre propre réponse personnalisée avec des ressources arbitraires).

Pourquoi aucun événement de récupération n'a-t-il été enregistré lors de la première actualisation ? Par défaut, les événements de récupération d'une page ne passent pas par un service worker, sauf si la requête de page elle-même est passée par un service worker. Cela garantit la cohérence de votre site. Si une page se charge sans le service worker, ses sous-ressources le font également.

Pour en savoir plus

Code de solution

Pour obtenir une copie du code fonctionnel, accédez au dossier 04-intercepting-network-requests/.

Les service workers ont un champ d'application. La portée du service worker détermine à partir de quels chemins d'accès le service worker intercepte les requêtes.

Trouver le champ d'application

Mettez à jour le code d'enregistrement dans index.html avec :

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('SW registered with scope:', registration.scope);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

Actualisez le navigateur. Notez que la console affiche le champ d'application du service worker (http://localhost:8081/ dans ce cas).

Explication

La promesse renvoyée par register() est résolue en objet d'enregistrement, qui contient le champ d'application du service worker.

Le champ d'application par défaut correspond au chemin d'accès au fichier du service worker et s'étend à tous les répertoires inférieurs. Ainsi, un service worker dans le répertoire racine d'une application contrôle les requêtes de tous les fichiers de l'application.

Déplacer le service worker

Déplacez service-worker.js dans le répertoire below/ et mettez à jour l'URL du service worker dans le code d'enregistrement de index.html.

Annulez l'enregistrement du service worker actuel dans le navigateur et actualisez la page.

La console indique que le champ d'application du service worker est désormais http://localhost:8081/below/. Dans Chrome, vous pouvez également afficher le champ d'application du service worker dans l'onglet "Application" des outils pour les développeurs :

De retour sur la page principale, cliquez sur Autre page, Autre page, puis sur Retour. Quelles requêtes de récupération sont enregistrées ? Lesquelles ne le sont pas ?

Explication

La portée par défaut du service worker correspond au chemin d'accès au fichier du service worker. Étant donné que le fichier du service worker se trouve désormais dans below/, il s'agit de son champ d'application. La console n'enregistre désormais que les événements de récupération pour another.html, another.css et another.js, car ce sont les seules ressources dans le champ d'application du service worker.

Définir un champ d'application arbitraire

Déplacez le service worker dans le répertoire racine du projet (app/), puis mettez à jour l'URL du service worker dans le code d'enregistrement de index.html.

Utilisez la référence sur MDN pour définir le champ d'application du service worker sur le répertoire below/ à l'aide du paramètre facultatif dans register().

Désinscrivez le service worker et actualisez la page. Cliquez sur Autre page, Autre page, puis sur Retour.

La console indique à nouveau que le champ d'application du service worker est désormais http://localhost:8081/below/ et enregistre les événements de récupération uniquement pour another.html, another.css et another.js.

Explication

Il est possible de définir un champ d'application arbitraire en transmettant un paramètre supplémentaire lors de l'enregistrement, par exemple :

navigator.serviceWorker.register('/service-worker.js', {
  scope: '/kitten/'
});

Dans l'exemple ci-dessus, le champ d'application du service worker est défini sur /kitten/. Le service worker intercepte les requêtes des pages dans /kitten/ et /kitten/lower/, mais pas celles des pages telles que /kitten ou /.

Remarque : Vous ne pouvez pas définir un champ d'application arbitraire qui se trouve au-dessus de l'emplacement réel du service worker. Toutefois, si votre service worker est actif sur un client desservi avec l'en-tête Service-Worker-Allowed, vous pouvez spécifier une portée maximale pour ce service worker au-dessus de son emplacement.

Pour en savoir plus

Code de solution

Pour obtenir une copie du code fonctionnel, accédez au dossier solution/.

Vous disposez désormais d'un service worker simple et opérationnel, et vous comprenez son cycle de vie.

Pour en savoir plus

Cycle de vie du service worker

Pour voir tous les ateliers de programmation du cours de formation sur les PWA, consultez l'atelier de programmation de bienvenue.