Jusqu'à présent, il n'y a eu que des mentions et de minuscules extraits de code de l'interface Cache
.
Pour utiliser efficacement les service workers, il est nécessaire d'adopter une ou plusieurs stratégies de mise en cache, ce qui nécessite une certaine connaissance de l'interface Cache
.
Une stratégie de mise en cache est une interaction entre l'événement fetch
d'un service worker et l'interface Cache
.
La manière dont une stratégie de mise en cache est écrite dépend. Par exemple, il peut être préférable de traiter les requêtes pour les éléments statiques différemment des documents, ce qui affecte la composition d'une stratégie de mise en cache.
Avant d'examiner les stratégies, prenons une seconde pour voir ce qu'est l'interface Cache
et ce qu'elle est, et un bref aperçu de certaines des méthodes qu'elle propose pour gérer les caches des service workers.
Interface Cache
et cache HTTP
Si vous n'avez jamais travaillé avec l'interface Cache
, il peut être tentant de la considérer comme identique, ou du moins liée au cache HTTP. Ce n'est pas le cas.
- L'interface
Cache
est un mécanisme de mise en cache entièrement distinct du cache HTTP. - Quelle que soit la configuration de
Cache-Control
utilisée pour influer sur le cache HTTP, les éléments stockés dans l'interfaceCache
n'ont aucune incidence.
Il est utile de considérer les caches des navigateurs comme étant superposés. Le cache HTTP est un cache de bas niveau alimenté par des paires clé/valeur et des directives exprimées dans des en-têtes HTTP.
En revanche, l'interface Cache
est un cache de haut niveau géré par une API JavaScript.
Cela offre plus de flexibilité que lors de l'utilisation de paires clé-valeur HTTP relativement simplistes, et représente la moitié de ce qui rend les stratégies de mise en cache possibles.
Voici quelques méthodes d'API importantes pour les caches de service worker:
CacheStorage.open
pour créer une instanceCache
.Cache.add
etCache.put
pour stocker les réponses réseau dans un cache de service worker.Cache.match
pour localiser une réponse mise en cache dans une instanceCache
.Cache.delete
pour supprimer une réponse mise en cache d'une instanceCache
.
En voici quelques-uns. Il existe d'autres méthodes utiles, mais ce sont les méthodes de base que vous utiliserez plus loin dans ce guide.
L'événement simple fetch
L'autre moitié d'une stratégie de mise en cache est l'événement fetch
du service worker.
Jusqu'à présent, dans cette documentation, vous avez entendu parler de l'"interception des requêtes réseau", et c'est dans l'événement fetch
d'un service worker que cela se produit:
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(cacheName));
});
self.addEventListener('fetch', async (event) => {
// Is this a request for an image?
if (event.request.destination === 'image') {
// Open the cache
event.respondWith(caches.open(cacheName).then((cache) => {
// Respond with the image from the cache or from the network
return cache.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
// Add the network response to the cache for future visits.
// Note: we need to make a copy of the response to save it in
// the cache and use the original as the request response.
cache.put(event.request, fetchedResponse.clone());
// Return the network response
return fetchedResponse;
});
});
}));
} else {
return;
}
});
Il s'agit d'un exemple jouet, que vous pouvez voir en action par vous-même, mais il offre un aperçu de ce que les service workers peuvent faire. Le code ci-dessus effectue les opérations suivantes:
- Inspectez la propriété
destination
de la requête pour voir s'il s'agit d'une requête d'image. - Si l'image se trouve dans le cache du service worker, diffusez-la à partir de cet emplacement. Si ce n'est pas le cas, récupérez l'image à partir du réseau, stockez la réponse dans le cache et renvoyez-la.
- Toutes les autres requêtes sont transmises via le service worker sans interaction avec le cache.
L'objet event
d'une extraction contient une propriété request
comprenant des informations utiles pour vous aider à identifier le type de chaque requête:
url
, qui correspond à l'URL de la requête réseau actuellement gérée par l'événementfetch
.method
, qui est la méthode de requête (par exemple,GET
ouPOST
).mode
, qui décrit le mode de la requête. La valeur'navigate'
permet souvent de distinguer les requêtes de documents HTML des autres demandes.destination
, qui décrit le type de contenu demandé de manière à éviter d'utiliser l'extension de fichier de l'élément demandé.
Là encore, "asynchrony" s'appelle le jeu.
N'oubliez pas que l'événement install
propose une méthode event.waitUntil
qui accepte une promesse et attend qu'elle soit résolue avant de passer à l'activation.
L'événement fetch
propose une méthode event.respondWith
similaire que vous pouvez utiliser pour renvoyer le résultat d'une requête fetch
asynchrone ou une réponse renvoyée par la méthode match
de l'interface Cache
.
Stratégies de mise en cache
Maintenant que vous connaissez les instances Cache
et le gestionnaire d'événements fetch
, vous êtes prêt à vous plonger dans les stratégies de mise en cache des service workers.
Bien que les possibilités soient pratiquement infinies, ce guide s'appuie sur les stratégies proposées avec Workbox. Vous pouvez ainsi vous faire une idée de ce qui se passe en interne dans Workbox.
Cache uniquement
Commençons par une stratégie de mise en cache simple que nous appellerons "Cache uniquement". En effet, lorsque le service worker contrôle la page, les requêtes correspondantes n'arrivent que dans le cache. Cela signifie que tous les éléments mis en cache devront être mis en pré-cache pour que le modèle fonctionne et que ces éléments ne seront jamais mis à jour dans le cache tant que le service worker n'aura pas été mis à jour.
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
// Assets to precache
const precachedAssets = [
'/possum1.jpg',
'/possum2.jpg',
'/possum3.jpg',
'/possum4.jpg'
];
self.addEventListener('install', (event) => {
// Precache assets on install
event.waitUntil(caches.open(cacheName).then((cache) => {
return cache.addAll(precachedAssets);
}));
});
self.addEventListener('fetch', (event) => {
// Is this one of our precached assets?
const url = new URL(event.request.url);
const isPrecachedRequest = precachedAssets.includes(url.pathname);
if (isPrecachedRequest) {
// Grab the precached asset from the cache
event.respondWith(caches.open(cacheName).then((cache) => {
return cache.match(event.request.url);
}));
} else {
// Go to the network
return;
}
});
Ci-dessus, un tableau d'éléments est mis en pré-cache au moment de l'installation.
Lorsque le service worker gère des récupérations, nous vérifions si l'URL de requête gérée par l'événement fetch
se trouve dans le tableau d'éléments en pré-cache.
Si tel est le cas, nous récupérons la ressource dans le cache et ignorons le réseau.
Les autres requêtes sont transmises au réseau, et uniquement au réseau.
Pour voir cette stratégie en action, regardez cette démonstration avec votre console ouverte.
Réseau uniquement
Le contraire de "Cache uniquement" est "Réseau uniquement", où une requête est transmise via un service worker au réseau sans aucune interaction avec le cache du service worker. Il s'agit d'une bonne stratégie pour assurer l'actualisation du contenu (pensez au balisage), mais en contrepartie, cela ne fonctionnera jamais lorsque l'utilisateur est hors connexion.
Pour s'assurer qu'une requête passe par le réseau, vous n'appelez pas event.respondWith
pour une requête correspondante.
Pour être explicite, vous pouvez ajouter un return;
vide dans votre rappel d'événement fetch
pour les requêtes que vous souhaitez transmettre au réseau.
C'est ce qui se passe dans la version de démonstration de la stratégie"Cache uniquement" pour les requêtes qui ne sont pas mises en pré-cache.
Mettre d'abord le cache en cache, en revenant sur le réseau
Avec cette stratégie, les choses deviennent un peu plus compliquées. Pour les demandes avec correspondance, le processus est le suivant:
- La requête frappe le cache. Si l'élément se trouve dans le cache, diffusez-le à partir de celui-ci.
- Si la requête ne figure pas dans le cache, accédez au réseau.
- Une fois la requête réseau terminée, ajoutez-la au cache, puis renvoyez la réponse du réseau.
Voici un exemple de cette stratégie, que vous pouvez tester dans une démonstration en direct:
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('fetch', (event) => {
// Check if this is a request for an image
if (event.request.destination === 'image') {
event.respondWith(caches.open(cacheName).then((cache) => {
// Go to the cache first
return cache.match(event.request.url).then((cachedResponse) => {
// Return a cached response if we have one
if (cachedResponse) {
return cachedResponse;
}
// Otherwise, hit the network
return fetch(event.request).then((fetchedResponse) => {
// Add the network response to the cache for later visits
cache.put(event.request, fetchedResponse.clone());
// Return the network response
return fetchedResponse;
});
});
}));
} else {
return;
}
});
Bien que cet exemple ne couvre que les images, il s'agit d'une excellente stratégie à appliquer à tous les éléments statiques (tels que CSS, JavaScript, images et polices), en particulier ceux dont la version est hachée. Elle accélère le lancement des éléments immuables en ignorant toute vérification d'actualisation du contenu auprès du serveur que le cache HTTP peut lancer. Plus important encore, tous les éléments mis en cache seront disponibles hors connexion.
Le réseau d'abord, avec le cache
Si vous inversez la stratégie "Mise en cache d'abord, réseau en second", vous vous retrouvez avec la stratégie "Réseau d'abord, mise en cache en second", comme son nom l'indique:
- Vous allez d'abord sur le réseau pour une requête et placez la réponse dans le cache.
- Si vous vous déconnectez plus tard, vous revenez à la dernière version de cette réponse dans le cache.
Cette stratégie est idéale pour les requêtes HTML ou API lorsque, en ligne, vous souhaitez obtenir la version la plus récente d'une ressource, mais pas accorder un accès hors connexion à la version disponible la plus récente. Voici ce à quoi cela pourrait ressembler lorsqu'il est appliqué aux demandes pour HTML:
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('fetch', (event) => {
// Check if this is a navigation request
if (event.request.mode === 'navigate') {
// Open the cache
event.respondWith(caches.open(cacheName).then((cache) => {
// Go to the network first
return fetch(event.request.url).then((fetchedResponse) => {
cache.put(event.request, fetchedResponse.clone());
return fetchedResponse;
}).catch(() => {
// If the network is unavailable, get
return cache.match(event.request.url);
});
}));
} else {
return;
}
});
Vous pouvez essayer dans une démonstration. Tout d'abord, accédez à la page. Vous devrez peut-être actualiser la page avant de placer la réponse HTML dans le cache. Ensuite, dans vos outils de développement, simulez une connexion hors connexion, puis actualisez à nouveau la page. La dernière version disponible sera diffusée instantanément à partir du cache.
Dans les situations où la capacité hors connexion est importante, mais que vous devez équilibrer cette capacité avec l'accès à la version la plus récente d'un peu de balisage ou de données d'API, une stratégie efficace pour atteindre cet objectif consiste à mettre en place une stratégie "réseau d'abord, puis mise en cache".
Obsolète lors de la revalidation
Parmi les stratégies présentées jusqu'à présent, l'option "Obsolète et revalidée" est la plus complexe. Elle est semblable aux deux dernières stratégies d'une certaine manière, mais la procédure donne la priorité à la vitesse d'accès à une ressource, tout en la maintenant à jour en arrière-plan. Voici à quoi ressemble cette stratégie:
- Lors de la première requête d'élément, récupérez-le sur le réseau, placez-le dans le cache et renvoyez la réponse du réseau.
- Lors des requêtes suivantes, diffusez d'abord l'élément à partir du cache, puis "en arrière-plan", demandez-le à nouveau au réseau et mettez à jour l'entrée de cache de l'élément.
- Pour les requêtes ultérieures, vous recevrez la dernière version extraite du réseau placée dans le cache lors de l'étape précédente.
Il s'agit d'une excellente stratégie pour les choses très importantes à garder à jour, mais pas essentielles. Pensez à des choses comme des avatars pour un site de médias sociaux. Elles sont mises à jour au fur et à mesure que les utilisateurs le font, mais la dernière version n'est pas strictement nécessaire à chaque demande.
// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image') {
event.respondWith(caches.open(cacheName).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchedResponse = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchedResponse;
});
}));
} else {
return;
}
});
Vous pourrez voir cela en action dans encore une autre démonstration en direct, en particulier si vous prêtez attention à l'onglet "Network" (Réseau) des outils pour les développeurs de votre navigateur, ainsi qu'à la visionneuse CacheStorage
(si les outils pour les développeurs de votre navigateur disposent d'un tel outil).
En avant pour Workbox !
Ce document conclut notre examen de l'API des service workers et des API associées. Vous en savez donc suffisamment sur l'utilisation directe des service workers pour commencer à bricoler Workbox.