Makina Blog
Découvrir le Service Worker
Tout (ou presque) pour rendre votre webapp disponible offline.
Si vous n'en avez jamais entendu parler
Un Service Worker est un script chargé parallèlement aux scripts de votre page et qui va s'exécuter en dehors du contexte de votre page web. Bien que le Service Worker n'ait pas accès au DOM ou aux interactions avec l'utilisateur, il va pouvoir communiquer avec vos scripts via l'API postMessage. Il se place en proxy de votre Web App, interceptant toutes les requêtes serveur et propose par exemple d'y répondre avec un cache ou en récupérant des données du LocalStorage ou d'IndexedDB. Il rend donc votre application disponible offline.
Les promesses des Service Workers
Que nous soyons dans un contexte d'application mobile hybride ou de webapp responsive, la mobilité des terminaux d'accès traîne souvent avec elle son côté obscur : la perte de connectivité (ou la faible connectivité). Sans accès au serveur, nos applications deviennent inutiles et ce n'est pas l'AppCache qui va venir à notre secours. Les Service Workers nous permettent de pallier ces problèmes en utilisant des ressources en cache si le réseau n'est pas disponible, permettant ainsi à notre application de fournir une expérience peu dégradée avant de retrouver un réseau disponible.
A script apart
Le script d'un Service Worker tourne dans le navigateur, mais en arrière-plan, sans accès au DOM ni aux interactions avec les utilisateurs. Il se place entre votre webapp et le réseau, permettant de jouer le rôle de proxy pour nos ressources. Il a une durée de vie indépendante du site web, il s'arrête lorsqu'il n'est pas utilisé et redémarre si besoin. Il n'a pas besoin de votre application pour tourner et peut donc permettre l'envoi de notifications (Comme le précise caniuse.com il faudra essayer la démo avec Chromium ou Firefox 44).
Le cycle de vie d'un Service Worker
Pour installer un Service Worker pour votre site, nous devrons l'enregistrer (register) dans le script de notre webapp. Une fois installé, notre Service Worker va s'activer (activate), c'est à ce moment que nous allons gérer la mise à jour du cache ou du Service Worker lui-même.
Après l'activation, le Service Worker est prêt à intercepter les événements fetch et message émis respectivement par une requête serveur ou un appel via l'API postMessage.
Découvrir et tester les Services Workers
Mozilla et Google sont à la pointe concernant les Service Workers. Ils proposent tous les deux des recettes et autres patterns pour les utiliser au mieux.
N'hésitez pas à visiter le site https://serviceworke.rs de Mozilla pour découvrir les Service Workers en action.
Profitez de l'Offline Cookbook de Jake Archibald pour faire les points sur les cas d'utilisation. Il a même développé une application pour les besoins de démonstration.
Implémenter son premier Service Worker
Prérequis
- Utiliser un navigateur récent : Firefox 44+ ou Chromium 47+.
- Repérer l'activation du mode offline dans Firefox ou dans Chromium.
- Visiter l'application https://mdn.github.io/sw-test/.
- Retrouver le code de l'application https://github.com/mdn/sw-test
Le fichier html (source)
L'application charge trois ressources : un fichier de style CSS, un fichier contenant les données et un fichier JavaScript pour générer la page.
Le fichier de données (source)
Le fichier déclare une variable dans le scope global contenant une liste d'images avec pour chacune : un nom, un texte alternatif, une url et une attribution.
Le fichier app.js (source)
Le premier bloc enregistre le Service Worker avec le code suivant :
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw-test/sw.js', { scope: '/sw-test/' })
.then(function(reg) {
// suivre l'état de l'enregistrement du Service Worker : `installing`, `waiting`, `active`
});
}
Le script du Service Worker est situé à l'adresse /sw-test/sw.js
, nous rentrerons dans les détails dans le prochain paragraphe.
La suite du script récupère le fichier de données et crée pour chacune des entrées de la galerie une balise <img>
avec l'url de l'image, une balise <caption>
avec le titre et l'attribution, enfin une figure parente <figure>
pour encapsuler le tout. Tout cela se passe après que le document ait été chargé grâce à window.onload
.
Ici, rien de particulier, une requête ajax récupère les données et l'API document.createElement
permet de créer les différents éléments HTML et de les insérer dans la balise <section>
du fichier index.html
.
Le Service Worker (source)
Au premier appel du Service Worker, ce dernier est installé dans le navigateur de l'utilisateur grâce à :
this.addEventListener('install', function(event) {
// ajouter les fichiers au cache
});
event.waitUntil
permet de bloquer les autres événements jusqu'à la résolution (ou le rejet) des promesses passées en paramètres.
Le Service Worker propose une interface cache pour représenter les paires d'objets Request/Response qui seront mises en cache. On peut enregistrer plusieurs objets cache pour un même domaine. Ainsi le code suivant ouvre ‒ s'il existe ‒ ou crée ‒ sinon ‒ le cache v1 et y enregistrera, lorsqu'elles seront appelées, les paires de requêtes/réponses correspondant aux routes écrites :
caches.open('v1').then(function(cache) {
return cache.addAll([
'/sw-test/',
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/image-list.js',
'/sw-test/star-wars-logo.jpg',
'/sw-test/gallery/',
'/sw-test/gallery/bountyHunters.jpg',
'/sw-test/gallery/myLittleVader.jpg',
'/sw-test/gallery/snowTroopers.jpg'
]);
});
On remarquera l'utilisation des promesses dont on a déjà évoqué l'utilité.
Pour intercepter les requêtes, on ajoutera un comportement à l'événement fetch :
this.addEventListener('fetch', function(event) {
// C'est là que la magie opère, Noël !
});
event.respondWith
permet d'enregistrer une réponse personnalisée ou de gérer les erreurs du réseau.
Ensuite on retourne simplement la ressource qui correspond à la requête si elle est disponible dans le cache :
caches.match(event.request);
S'il n'y a pas de correspondance dans le cache, la promesse caches.match
sera rejetée et nous aurons une erreur réseau classique (404). Mais on peut aussi utiliser les possibilités des Service Workers pour proposer un traitement plus adéquat à nos erreurs réseau :
caches.match(event.request).catch(function() {
return fetch(event.request);
})
Ici, si le cache ne contient pas la ressource correspondante à la requête, on tente de faire un appel réseau avec cette même requête. Et dans le cas de notre exemple, nous faisons encore mieux en rajoutant cette ressource au cache si la requête réseau a fonctionné :
var response;
event.respondWith(caches.match(event.request).catch(function() {
return fetch(event.request);
}).then(function(r) {
response = r;
caches.open('v1').then(function(cache) {
cache.put(event.request, response);
});
return response.clone();
}));
Nous retournons une copie de la réponse car les objets requêtes et réponses sont des flux qui ne peuvent être consommés qu'une seule fois. Ici on consomme la réponse pour l'ajouter dans le cache et on retourne la réponse à celui qui a fait la requête, l'objet XMLHttpRequest
de app.js
.
Enfin, si la requête n'a pas de correspondance dans le cache et si le réseau n'est pas disponible, on tombe dans le dernier cas :
catch(function() {
return caches.match('/sw-test/gallery/myLittleVader.jpg');
})
Et on retourne une ressource par défaut, ici une des images que nous savons disponibles dans le cache.
Pour preuve, une fois le Service Worker activé (visitez une première fois https://mdn.github.io/sw-test), essayez donc de lire la ressource https://mdn.github.io/sw-test/mylittleponey qui n'existe pas dans l'application de démonstration.
Mise à jour et gestion du cache
Lorsque nous avons déjà une version de notre Service Worker installée et activée, celle-ci restera responsable des traitements des requêtes tant qu'il y aura des pages utilisant cette version du Service Worker.
Si nous mettons à disposition une nouvelle version de notre Service Worker, celle-ci sera installée en arrière-plan, mais ne sera activée et responsable des traitements de requêtes que lorsque plus aucune page chargée n'utilisera l'ancienne version.
Pour mettre à jour notre Service Worker, il nous suffira de changer la version du cache comme ceci :
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v2').then(function(cache) {
return cache.addAll([
'/sw-test/',
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/image-list.js',
…
// include other new resources for the new version...
]);
});
);
});
L'événement activate est émis juste avant que le Service Worker ne soit responsable des traitements des requêtes, c'est donc le bon moment pour gérer le cache en supprimant les entrées qui ne sont plus nécessaires, par exemple.
On écrira quelque chose comme ça :
this.addEventListener('activate', function(event) {
var cacheWhitelist = ['v2'];
event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (cacheWhitelist.indexOf(key) === -1) {
return caches.delete(keyList[i]);
}
}));
})
);
});
Dev tools
Les navigateurs qui implémentent le nécessaire aux Service Workers ajoutent quelques outils pour les lister, les debugger, les démarrer, les stopper, les désinscrire (unregister).
Pour Chromium, les outils sont disponibles via chrome://inspect/#service-workers
et chrome://serviceworker-internals
.
Pour Firefox, ils sont disponibles via about:serviceworkers
.
Les dev tools vous permettent également de tester les mode offline de votre web app comme vu plus haut avec : pour Firefox ou pour Chromium.
Annexes
Autres applications
Nous couvrirons sûrement les autres cas d'utilisation du Service Worker qui peut servir à autre chose que de fournir des contenus lorsqu'on est hors ligne, comme par exemple :
- La synchronisation des données en arrière-plan ;
- Répondre aux requêtes depuis d'autres origines ;
- Recevoir des données lourdes à calculer comme des données de géolocalisation ou de gyroscope, afin que plusieurs pages puissent y accéder sans refaire les calculs ;
- Compilation et gestion de dépendances de CoffeeScript, less, des modules CJS/AMD, etc. pour les besoins de développement ;
- Gestion et utilisation de services en d'arrière-plan ;
- Templates personnalisés en fonction de patterns d'URL ;
- Amélioration des performances, par exemple en pré-chargeant les ressources que l'utilisateur pourrait utiliser plus tard telles que les prochaines photos d'un album.
Cette liste, proposée par Mozilla Developer Network, n'est clairement pas exhaustive. On peut retrouver d'autres cas d'utilisation en action sur le site https://serviceworke.rs proposé également par Mozilla comme :
- Agir comme un serveur ;
- Logguer les utilisations d'une API ;
- Balance de charge en fonction des requêtes ;
- Mettre en cache à partir d'un fichier zip ;
- Faire de l'injection de dépendance ;
- Décaler l'envoi des requêtes pour attendre le retour en ligne ;
- etc.
Quelques liens
Enfin voici quelques ressources pour aller plus loin.
- Introduction to Service Worker, Matt Gaunt : http://www.html5rocks.com/en/tutorials/service-worker/introduction/
- Service Worker Cookbook, Mozilla : https://serviceworke.rs/
- Offline Recipes for Service Workers, David Walsh : https://hacks.mozilla.org/2015/11/offline-service-workers/
- Service Worker API, Mozilla Developer Network : https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
- Using Service Workers, MDN : https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
- Service Worker test repository, MDN : https://github.com/mdn/sw-test
- Can I Use Service Workers?, Alexis Deveria : http://caniuse.com/#search=service%20worker
- Getting started with Service Workers, Ritesh Kumar : http://www.sitepoint.com/getting-started-with-service-workers/
- Is Service Worker ready?, Jake Archibald : https://jakearchibald.github.io/isserviceworkerready/resources.html
- Une nouvelle architecture pour nos applications web mobiles, Julien Wajsberg : http://www.24joursdeweb.fr/2015/une-nouvelle-architecture-pour-nos-applications-web-mobiles/
- Background Sync, Web Incubator Community Group : https://github.com/WICG/BackgroundSync/blob/master/explainer.md
- Cache, MDN : https://developer.mozilla.org/en-US/docs/Web/API/Cache
- FetchEvent, MDN : https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent
- postMessage, MDN : https://developer.mozilla.org/en-US/docs/Web/API/Client/postMessage
- Using Service Workers, MDN : https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
Actualités en lien
Générer un fichier PMTiles avec Tippecanoe
Exemple de génération et d’affichage d’un jeu de tuiles vectorielles en PMTiles à partir de données publiques.
Publier une documentation VitePress sur Read The Docs
À l'origine, le site de documentation Read The Docs n'acceptait que les documentations Sphinx ou MKDocs. Depuis peu, le site laisse les mains libres pour builder sa documentation avec l'outil de son choix. Voici un exemple avec VitePress.
Comment compresser son code applicatif de manière efficace avec Nginx et Brotli ?
Dans cet article, nous allons mettre en place un algorithme de compression des données textuelles plus efficace, que celui utilisé habituellement, pour réduire le poids d'une page web.