Dans l’article précédent je vous ai expliqué comment fonctionnait les services workers et comment ils nous permettaient d’enrichir nos progressive webapps en offrant une expériene offline. Nous allons aujourd’hui voir comment ajouter un service worker dans votre application web.
Reprenons le graphique présentant le cycle de vie d’un service worker
Dans le cas courant un service worker peut
référencer toutes les ressources prises en compte par le SW
écrire un listener sur l’événement install pour ajouter des dépendances ou pour commencer à peupler des éléments dans le cache
écrire un listener sur l’événement activate pour nettoyer le cache des ressources qui ne sont plus utilisées. Il est important d’ailleurs que l’id cache de votre service worker soit toujours le même. Sinon ce travail de nettoyage ne pourra pas être effectué. On reviendra un peu plus tard sur les problèmes éventuels avec le cache
écrire un listener sur l’événement fetch pour interagir avec les requêtes effectuées par l’utilisateur. Vous pouvez définir des stratégies pour privilégier des ressources de votre cache plutôt que les ressources présentes sur votre serveur
écrire un listener sur l’événement push pour que votre service worker intervienne quand votre serveur pousse de l’information. Vous pouvez par exemple faire de la notification
écrire un listener sur l’événement sync pour se synchroniser avec le serveur quand vous retrouvez une connexion Internet
L’écriture du service worker en soit peut être assez fastidieuse et source d’erreur si les choses sont mal faites. Dans les cas les plus courants, le code est toujours à peu près le même. Google propose plusieurs librairies pour vous aider dans la mise en place des services workers.
La règle de base est de disposer d’un site sécurisé en HTTPS. Si vous voulez tester en local vous pouvez aussi lancer Chrome en désactivant ce contrôle.
google-chrome --user-data-dir=/tmp/foo --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost
Mais attention aux surprises. Un service worker s’enregistre sur le domaine. Si vous le faites sur localhost, il risque de s’activer chaque fois que vous lancez une page en local. Après rien de grave vous pouvez à tout moment contrôler les services workers via les Chrome Dev Tools.
Les Chromes Dev Tools permettent de simuler des événements (Update, Push, Sync..) mais aussi de déréférencer un service worker. Vous pouvez également nettoyer les caches, les services workers via la section Clear Storage
Un service ne peut être enregistré que sur la même origine que votre application. Si votre origine est dev-mind.fr vous ne pouvez pas enregistrer un service worker d’un autre site.
Vous devez déclarer votre service worker dans votre script principal
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js', scope: '/subcontent')
.then(function(registration) {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
})
.catch(function(e) {
console.error('Error during service worker registration:', e);
});
}
Quand vous enregistrer un service worker vous pouvez ajouter un scope pour spécifier un sous ensemble de votre site qui sera contrôlé par ce service worker.
Un service worker contrôle plusieurs pages. Chaque fois qu’une page de votre scope est chargée, le service worker est installé ou réactivé.
Vous pouvez vous appuyer sur 2 outils fournit par Google
sw-precache est un plugin que vous pouvez intégrer à votre processus de buid (Gulp, Grunt) et qui va générer toute la configuration de vos services workers.
sw-toolbox est une librairie que vous pouvez intégrer à votre site et qui va proposer plusieurs utilitaires pour votre service worker. Cette librairie fournit un mini routeur qui permet de définir des stratégies de cache en fonction de routes définies (au format expressJs).
Avec sw-toolbox vous pouvez par exemple définir les stratégies suivantes (basées sur le offline cookbook de Jake Archibald)
networkFirst : essaye de lancer la requête en mode connecté. Si le réseau répond la réponse est stockée dans le cache et servie. Si la réponse dépasse un timeout défini ou si le réseau est inaccessible le SW retourne la ressource si elle est présente dans le cache. Cette stratégie est intéressante quand vous voulez afficher les données les plus récentes.
cacheFirst : si la ressource est dans le cache elle est directment renvoyée. Sinon on charge la ressource. Cette stratégie est utilisée pour des éléments qui ne changent pas (sinon vous devez mettre en place une stratégie pour mettre à jour ces ressources quand elles changent).
fastest : deux requêtes sont lancées en parallèle et la première qui arrive est prise en compte. Avec la latence réseau et les temps de chargement le cache gagne toujours. Cette solution est intéressante pour lancer une mise à jour en tâche de fond d’une ressource. Stratégie qui est au final plus souple que cacheFirst
cacheOnly : on ne regarde que dans le cache. Si la ressource n’est pas là nous avons une erreur. Intéressant sur mobile par exemple pour préserver la batterie quand elle commence à faiblir.
networkOnly : inverse on interroge toujours le réseau. Cette stratégie est un peu inutile vu qu’il se passe la même chose si vous n’utilisez pas de services workers
Voici la configuration à appliquer par exemple dans votre build Gulp pour générer un service worker utilisant sw-toolbox.
gulp.task('generate-service-worker', (cb) => {
let config = {
cacheId: 'dev-mind',
runtimeCaching: [{
urlPattern: '/(.*)',
handler: 'networkFirst',
options: {
networkTimeoutSeconds: 3,
maxAgeSeconds: 7200
}
}],
staticFileGlobs: ['build/dist/**/*.{js,html,css,png,jpg,json,gif,svg,webp,eot,ttf,woff,woff2,gz}'],
stripPrefix: 'build/dist',
verbose: true
};
swPrecache.write('build/dist/service-worker.js', config, cb);
});
Vous devez indiquer à swPrecache où le service worker est généré. Au niveau de la configuration vous devez spécifier
un id pour le cache : omme je le disais plus haut c’est important de toujours garder le même identifiant pour que le service worker généré soit capable de nettoyer le cache quand ce dernier comporte des éléments plus utilisés
une ou plusieurs configuration de cache (runtimeCaching): vous définisser des URLs au format ExpressJS afin d’indiquer quels fichiers seront pris en compte par cette configuration (ici je prends toutes les URL du site). Vous pouvez ensuite choisir la stratégie de cache à appliquer et ajouter des options. J’utilise ici 2 options intéressantes. La première networkTimeoutSeconds permet de privilégier le cache si le timeout est dépassé (vous permet de servir votre site quand la qualité du réseau est très fluctuante. L’option maxAgeSeconds permet de définir une durée de vie dans le cache
staticFileGlobs vous permet de définir quels fichiers serons gérés par votre service worker
…
Pour limiter les problèmes de cache je vous conseil également de faire du cache busting. Le Cache busting consiste à utiliser un nom unique pour vos ressources. En gros dans votre processus de build vous renommer chacune de vos ressources en mettant un numéro de révision. Cette manière de faire force le navigateur à recharger des ressources quand celles ci changent. Par contre vous devez vous assurer que votre point d’entrée de votre application (index.html) soit
Je ne vais pas m’attarder sur cette solution car Google a annoncé à Google IO 2017 la sortie de Workboxjs une nouvelle toolbox pour vous aider à écrire des applications progressives…. Si vous utilisez swPrecache et swToolbox pas d’affolement, Google maintient toujours ces solutions.
C’est ce que nous verrons dans le prochain article sur les services workers