Les service workers sont des scripts JavaScript qui s'exécutent en arrière-plan de votre application web et qui permettent d'ajouter des fonctionnalités avancées à celle-ci. Ils agissent comme des intermédiaires entre l'application web, le navigateur et le réseau, leur permettant de contrôler les requêtes entrantes et sortantes et de gérer la mise en cache des ressources.
Les service workers permettent notamment de stocker des fichiers tels que des images, des scripts et des feuilles de style CSS localement sur l'appareil de l'utilisateur, ce qui améliore considérablement les performances de l'application en réduisant les temps de chargement des pages et en permettant une utilisation hors ligne de l'application.
Les service workers peuvent également gérer les notifications push, synchroniser des données en arrière-plan, mettre à jour automatiquement l'application et bien d'autres fonctionnalités encore. Ils offrent ainsi une expérience utilisateur améliorée et permettent aux applications web de rivaliser avec les applications mobiles natives.
En somme, les service workers sont un élément clé des Progressive Web Apps, offrant une expérience utilisateur rapide, fiable et engageante, même lorsque l'utilisateur est hors ligne ou dans des conditions de réseau difficiles.
Le fichier serviceworker.js
de PWA Bunga! va permettre :
Le fichier serviceworker.js de PWA Bunga! contient des variables permettant de rapidement configurer le système de cache et la stratégie de traitement des requêtes.
La constante cacheTitle
est une variable qui doit être définie dans votre application web afin de stocker le nom de votre application. Elle est utilisée pour
identifier les fichiers en cache associés à votre application. Le nom de l'application doit être unique et descriptif pour éviter toute confusion avec d'autres applications.
const cacheTitle = 'yourAppName'
La constante cacheVersion
est une variable qui doit être définie dans votre application web afin de stocker le numéro de version de votre application.
Elle est utilisée pour identifier les différents ensembles de fichiers en cache associés à différentes versions de l'application.
Il est important de mettre à jour cette valeur chaque fois que vous publiez une nouvelle version de votre application pour vous assurer que les utilisateurs
ont la dernière version.
const cacheVersion = 'v1.0'
La constante cacheName
est une variable qui combine les valeurs de "cacheTitle" et "cacheVersion" pour former un nom de cache unique pour votre application.
Elle est utilisée pour créer un cache pour votre application et stocker les fichiers associés à cette version de l'application.
const cacheName = cacheTitle + '-' + cacheVersion
La constante contentToCache
est une variable qui doit être définie dans votre application web afin de stocker une liste de fichiers à mettre en cache pour une utilisation
hors ligne. Les fichiers peuvent inclure des pages, des images, des styles CSS, des scripts JavaScript, des icônes et d'autres éléments nécessaires pour que l'application
fonctionne correctement lorsqu'elle est hors ligne.
const contentToCache = [
// - Pages
'/',
'index.html',
// - Favicons
'favicon.ico',
'favicon.svg',
// 'favicon-16.png',
// 'favicon-32.png',
// - CSS
'assets/css/pwabunga.css',
'assets/css/styles.css',
// - JS
'assets/js/scripts.js',
// - PWA
'pwa/css/pwabunga-ui.css',
'pwa/icons/apple-touch-icon.png',
'pwa/icons/icon-192.png',
'pwa/icons/icon-512.png',
'pwa/icons/icon-maskable-192.png',
'pwa/icons/icon-maskable-512.png',
'pwa/js/pwabunga.js',
'pwa/app.webmanifest'
]
La constante requestProcessingMethod
est une variable qui doit être définie dans votre application web afin de déterminer la stratégie à utiliser pour traiter les requêtes de réseau.
Elle peut prendre l'une des valeurs suivantes : 'cacheOnly', 'networkOnly', 'cacheFirst', 'networkFirst' ou 'staleWhileRevalidate'. Chaque valeur correspond à une stratégie différente
pour répondre aux requêtes de réseau et pour gérer le contenu mis en cache.
cacheOnly
: Si cette valeur est utilisée, les requêtes seront traitées à partir du cache uniquement. Si le contenu n'est pas disponible dans le cache, la requête échouera.cacheOnly
: Si cette valeur est utilisée, les requêtes seront traitées à partir du réseau uniquement. Si le réseau est inaccessible, la requête échouera.cacheFirst
: Si cette valeur est utilisée, les requêtes seront traitées à partir du cache en premier lieu. Si le contenu n'est pas disponible dans le cache, la requête sera envoyée au réseau. Si le réseau n'est pas disponible, la requête échouera.networkFirst
: Si cette valeur est utilisée, les requêtes seront traitées à partir du réseau en premier lieu. Si le réseau est inaccessible, la requête sera envoyée au cache.
Si le contenu n'est pas disponible dans le cache, la requête échouera.staleWhileRevalidate
: Si cette valeur est utilisée, les requêtes seront traitées à partir du cache. Le contenu du cache sera retourné immédiatement, même s'il est expiré.
Une requête sera envoyée au réseau pour récupérer la dernière version du contenu, qui sera ensuite mise en cache pour les prochaines requêtes.
const requestProcessingMethod = 'cacheFirst'
La constante dynamicFolder
est une variable qui peut être utilisée dans votre application web pour définir le chemin d'un dossier dynamique qui ne doit pas être mis en cache.
Cette variable est utilisée en conjonction avec la stratégie "networkOnly" pour s'assurer que les requêtes vers ce dossier sont toujours traitées à partir du réseau et jamais mises en cache.
Lorsque cette variable est définie, toutes les requêtes qui commencent par le chemin défini seront considérées comme des requêtes "networkOnly". Cela signifie que ces requêtes seront toujours envoyées au réseau pour être traitées et que le contenu ne sera jamais mis en cache.
const dynamicFolder = '/api/'
Afin d'agir sur le cache, nous utilisons les fonctions suivantes :
La fonction addResourcesToCache
est une fonction asynchrone qui peut être utilisée dans votre application web pour ajouter des ressources dans le cache. Cette fonction prend un seul paramètre,
resources
, qui doit être un tableau contenant une liste de toutes les ressources que vous souhaitez ajouter au cache.
Ouvre une instance du cache de l'application en utilisant la méthode open()
de l'API CacheStorage, avec la variable cacheName
en paramètre
const cache = await caches.open(cacheName)
Utilise la méthode addAll()
pour mettre dans le cache de l'application les ressources
await cache.addAll(resources)
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre que toutes les ressources soient ajoutées dans le cache avant de
continuer l'exécution du code.
const addResourcesToCache = async (resources) => {
const cache = await caches.open(cacheName)
await cache.addAll(resources)
}
La fonction putInCache
est une fonction asynchrone qui peut être utilisée dans votre application web pour mettre en cache une requête et sa réponse. Cette fonction prend deux paramètres,
request
et response
, qui représentent respectivement la requête et la réponse que vous souhaitez mettre en cache.
Ouvre l'instance du cache de l'application
const cache = await caches.open(cacheName)
Utilise la méthode put()
pour ajouter ou remplacer dans le cache de l'application la ressource.
await cache.put(request, response)
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre que la mise en cache soit terminée avant de
continuer l'exécution du code.
const putInCache = async (request, response) => {
const cache = await caches.open(cacheName)
await cache.put(request, response)
}
La fonction deleteCache
est une fonction asynchrone qui peut être utilisée dans votre application web pour supprimer un cache spécifique en utilisant sa clé.
Utilise la méthode delete()
avec le paramètre key
, l'entrée du cache à supprimer.
await caches.delete(key)
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre que la suppression soit terminée avant de continuer
l'exécution du code.
const deleteCache = async (key) => {
await caches.delete(key)
}
La fonction deleteOldCaches
est une fonction asynchrone qui peut être utilisée dans votre application web pour supprimer tous les caches qui ne sont pas nécessaires à
votre application.
Récupère l'instance de la version du cache à conserver
const cacheKeepList = [cacheName]
Récupère les différentes instances de cache disponibles
const keyList = await caches.keys()
Enlève à la variable précédente l'instance de la version du cache de l'application à conserver
const cachesToDelete = keyList.filter((key) => !cacheKeepList.includes(key))
Supprime les anciennes versions du cache de l'application en utilisant la fonction deleteCache
await Promise.all(cachesToDelete.map(deleteCache))
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre que la suppression soit terminée avant de continuer
l'exécution du code.
const deleteOldCaches = async () => {
const cacheKeepList = [cacheName]
const keyList = await caches.keys()
const cachesToDelete = keyList.filter((key) => !cacheKeepList.includes(key))
await Promise.all(cachesToDelete.map(deleteCache))
}
La fonction cacheOnly
est une fonction asynchrone qui peut être utilisée dans votre service worker pour répondre aux requêtes en utilisant le cache uniquement.
Teste si la requête à une correspondance dans le cache de l'application avec la méthode .match()
const responseFromCache = await caches.match(request)]
S'il y a une correspondance, renvoie la réponse provenant du cache
if (responseFromCache) {
return responseFromCache
}
Sinon, renvoie une réponse 404 avec un message d'erreur en texte brut
else {
return new Response("Cache error happened", {
status: 404,
headers: { "Content-Type": "text/plain" },
})
}
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre la réponse avant de continuer l'exécution du code.
const cacheOnly = async ({ request }) => {
const responseFromCache = await caches.match(request)
if (responseFromCache) {
return responseFromCache
} else {
return new Response("Cache error happened", {
status: 404,
headers: { "Content-Type": "text/plain" }
})
}
}
La fonction networkOnly
est une fonction asynchrone qui prend un objet en paramètre qui contient une requête à traiter. Elle essaie d'abord de récupérer la ressource
demandée à partir du réseau. Si la requête réseau réussit, elle renvoie la réponse. Si la requête réseau échoue, elle renvoie une réponse 408 avec un message d'erreur.
Teste si la requête trouve une réponse sur le serveur avec la méthode .fetch()
const responseFromNetwork = await fetch(request)
S'il y a une correspondance, renvoie la réponse provenant du serveur
return responseFromNetwork
Sinon, renvoie une réponse 408 (Request Timeout) avec un message d'erreur "Network error happened"
catch (error) {
return new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" }
})
}
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre la réponse avant de continuer l'exécution du code.
const networkOnly = async ({ request }) => {
try {
const responseFromNetwork = await fetch(request)
return responseFromNetwork
} catch (error) {
return new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" }
})
}
}
La fonction cacheFirst
est une fonction asynchrone qui peut être utilisée dans une application web pour améliorer les performances en réduisant le temps de réponse et le nombre de requêtes réseau.
Cette fonction suit une stratégie de mise en cache qui consiste à chercher en premier la réponse dans le cache avant de faire appel au réseau.
Si l'URL de la requête contient le dossier dynamique (dynamicFolder
)
if (request.url.includes(dynamicFolder)) {
renvoie la réponse provenant du serveur
return fetch(request);
Teste si la requête à une correspondance dans le cache de l'application avec la méthode .match()
const responseFromCache = await caches.match(request)
S'il y a une correspondance, renvoie la réponse provenant du cache
if (responseFromCache) {
return responseFromCache
}
Sinon, teste si la requête a une correspondance sur le serveur
const responseFromNetwork = await fetch(request)
si elle en a une, met à jour le cache de l'application en y ajoutant la nouvelle ressource avec la fonction putInCache
putInCache(request, responseFromNetwork.clone())
Et renvoie la réponse provenant du serveur
return responseFromNetwork
Sinon, renvoie une réponse 408 (Request Timeout) avec un message d'erreur "Network error happened"
catch (error) {
return new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" }
})
}
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre la réponse avant de continuer l'exécution du code.
const cacheFirst = async ({ request }) => {
if (request.url.includes(dynamicFolder)) {
return fetch(request);
}
const responseFromCache = await caches.match(request)
if (responseFromCache) {
return responseFromCache
}
try {
const responseFromNetwork = await fetch(request)
putInCache(request, responseFromNetwork.clone())
return responseFromNetwork;
} catch (error) {
return new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" },
})
}
}
La fonction networkFirst
est une fonction asynchrone qui peut être utilisée dans votre application web pour récupérer une ressource depuis le réseau.
Si la récupération à partir du réseau échoue, elle tente de récupérer la réponse depuis le cache.
Teste si la requête a une correspondance sur le serveur
const responseFromNetwork = await fetch(request)
si elle en a une, met à jour le cache de l'application en y ajoutant ou en remplaçant la ressource avec la fonction putInCache
putInCache(request, responseFromNetwork.clone())
Et renvoie la réponse provenant du serveur
return responseFromNetwork
Sinon, teste si la requête à une correspondance dans le cache de l'application avec la méthode .match()
const responseFromCache = await caches.match(request)
S'il y a une correspondance, renvoie la réponse provenant du cache
if (responseFromCache) {
return responseFromCache
}
Sinon, renvoie une réponse 408 (Request Timeout) avec un message d'erreur "Network error happened"
catch (error) {
return new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" }
})
}
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre la réponse avant de continuer l'exécution du code.
const networkFirst = async ({ request }) => {
try {
const responseFromNetwork = await fetch(request)
putInCache(request, responseFromNetwork.clone())
return responseFromNetwork;
} catch (error) {
const responseFromCache = await caches.match(request)
if (responseFromCache) {
return responseFromCache
}
return new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" }
})
}
}
La fonction staleWhileRevalidate
est une fonction asynchrone qui peut être utilisée pour améliorer les performances des applications Web en
fournissant une réponse rapide à un utilisateur tout en mettant à jour la version du cache en arrière-plan.
Ouvre l'instance du cache de l'application en utilisant la méthode open()
de l'API CacheStorage
const cache = await caches.open(cacheName)
Teste si la requête à une correspondance dans le cache de l'application avec la méthode .match()
const responseFromCache = await caches.match(request)]
Teste si la requête trouve une réponse sur le serveur avec la méthode .fetch()
const responseFromNetwork = fetch(request)
Si la requête a une correspondance dans le cache
if (responseFromCache) {
Crée une copie de la réponse trouvée dans le cache pour que la réponse originale ne soit pas modifiée
const responseClone = responseFromCache.clone()
Récupère la réponse du réseau
responseFromNetwork.then(response => {
Ajoute la réponse récupérée depuis le réseau dans le cache
cache.put(request, response.clone())
renvoie la copie de la réponse du cache pour terminer l'exécution de la fonction
return responseClone
Essaie d'exécuter une requête réseau en utilisant la méthode fetch() de l'API Web
try {
const response = await responseFromNetwork
Ajoute la réponse récupérée depuis le réseau dans le cache
cache.put(request, response.clone())
Renvoie la réponse récupérée depuis le réseau pour terminer l'exécution de la fonction.
return response
Sinon, renvoie une réponse 408 (Request Timeout) avec un message d'erreur "Network error happened"
return new Response("Network error happened", {
status: 408,
headers: { "Content-Type": "text/plain" }
})
Notez que cette fonction est asynchrone, ce qui signifie qu'elle peut être utilisée avec l'opérateur await
pour attendre la réponse avant de continuer l'exécution du code.
const staleWhileRevalidate = async ({ request }) => {
const cache = await caches.open(cacheName)
const responseFromCache = await caches.match(request)
const responseFromNetwork = fetch(request)
if (responseFromCache) {
const responseClone = responseFromCache.clone()
responseFromNetwork.then(response => {
cache.put(request, response.clone())
})
return responseClone
}
try {
const response = await responseFromNetwork
cache.put(request, response.clone())
return response
} catch (error) {
return new Response('Network error occurred.', {
status: 408,
headers: { 'Content-Type': 'text/plain' }
})
}
}
Cet évènement correspond à la phase d'installation du service worker.
self.addEventListener('install', (event) => {
console.log(cacheName + ' Installation')
})
C'est au moment de l'évènement install que l'on va créer l'instance du cache de l'application avec les ressources statiques avec à la fonction addResourcesToCache
.
La fonction utilise la méthode waitUntil de l'objet event pour attendre que toutes les ressources soient ajoutées au cache.
event.waitUntil(
addResourcesToCache(contentToCache)
)
self.addEventListener('install', (event) => {
console.log(cacheName + ' Installation')
event.waitUntil(
addResourcesToCache(contentToCache)
)
})
L'évènement activate
est émis lorsque le service worker est téléchargé et exécuté par le navigateur, et qu'il est activé.
self.addEventListener('activate', (event) => {
})
C'est pendant cette évènement que l'on va demander au service worker d'exécuter la fonction deleteOldCaches()
,
afin de supprimer l'instance du cache de l'ancienne version du service worker lors de l'activation de la nouvelle version
event.waitUntil(
deleteOldCaches()
)
self.addEventListener('activate', (event) => {
event.waitUntil(
deleteOldCaches()
)
})
L'évènement message
va être utilisé ici pour recevoir les messages via l'API Message, qui permet aux scripts de différents contextes (comme un service worker et une fenêtre de navigateur) de communiquer entre eux en envoyant des messages.
self.addEventListener('message', (event) => {
})
Quand le Service worker reçoit le message indiquant que l'utilisateur a demandé d'effectuer la mise à jour (voir la fonction pwaUpdate() du fichier pwabunga.js)
if (event.data === 'SKIP_WAITING') {
Le Service worker exécute la mise à jour en activant le nouveau service worker
self.skipWaiting()
Quand le Service worker reçoit le message indiquant que l'on souhaite afficher la version de la PWA (voir la fonction pwaParams() du fichier pwabunga.js)
if (event.data === 'GET_VERSION') {
Pour tous les clients associés au service worker
self.clients.matchAll().then(function(clients) {
clients.forEach(function(client) {
Envoie le message contenant la variable de la version de la PWA
client.postMessage(cacheVersion)
self.addEventListener('message', (event) => {
if (event.data === 'SKIP_WAITING') {
self.skipWaiting()
}
if (event.data === 'GET_VERSION') {
self.clients.matchAll().then(function(clients) {
clients.forEach(function(client) {
client.postMessage(cacheVersion)
})
})
}
})
L'évènement fetch
permet de gérer les requêtes HTTP provenant de l'application cliente.
Il est utilisé pour récupérer des ressources depuis le réseau ou le cache, selon la configuration de l'application
self.addEventListener('fetch', (event) => {
})
Récupère l'objet request
depuis l'événement fetch
const request = event.request
Définit un objet methods
contenant les méthodes de traitement de requêtes possibles
const methods = {
'cacheOnly': cacheOnly,
'networkOnly': networkOnly,
'cacheFirst': cacheFirst,
'networkFirst': networkFirst,
'staleWhileRevalidate': staleWhileRevalidate
}
Récupère la méthode de traitement de requête correspondante à la méthode de la requête choisie dans la constante requestProcessingMethod
const method = methods[requestProcessingMethod]
Si une méthode de traitement de requête existe pour la méthode courante, elle l'utilise pour gérer la requête
if (method) {
event.respondWith(method({request}))
}
self.addEventListener('fetch', (event) => {
const request = event.request
const methods = {
'cacheOnly': cacheOnly,
'networkOnly': networkOnly,
'cacheFirst': cacheFirst,
'networkFirst': networkFirst,
'staleWhileRevalidate': staleWhileRevalidate
}
const method = methods[requestProcessingMethod]
if (method) {
event.respondWith(method({request}))
}
})