Le préchargement de navigation vous permet de contourner le temps de démarrage d'un service worker en effectuant des requêtes en parallèle.
Navigateurs pris en charge
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
Résumé
- Dans certains cas, le temps de démarrage d'un service worker peut retarder la réponse du réseau.
- Disponible dans les trois principaux moteurs de navigateur, le préchargement de navigation permet de résoudre ce problème en vous permettant d'envoyer la requête en parallèle au démarrage du service worker.
- Vous pouvez distinguer les requêtes de précharge des navigations standards à l'aide d'un en-tête et diffuser un contenu différent.
Problème
Lorsque vous accédez à un site qui utilise un service worker pour gérer les événements de récupération, le navigateur demande au service worker une réponse. Cela implique de démarrer le service worker (s'il n'est pas déjà en cours d'exécution) et de déclencher l'événement de récupération.
Le temps de démarrage dépend de l'appareil et des conditions. Elle est généralement d'environ 50 ms. Sur mobile, il atteint 250 ms. Dans les cas extrêmes (appareils lents, processeur en détresse), elle peut dépasser 500 ms. Toutefois, étant donné que le service worker reste actif pendant un laps de temps déterminé par le navigateur entre les événements, ce délai n'est constaté qu'occasionnellement, par exemple lorsque l'utilisateur accède à votre site à partir d'un nouvel onglet ou d'un autre site.
Le temps de démarrage ne pose pas de problème si vous répondez à partir du cache, car l'avantage d'ignorer le réseau est supérieur au délai de démarrage. Mais si vous répondez via le réseau...
La requête réseau est retardée à cause du démarrage du service worker.
Nous continuons à réduire le temps de démarrage en utilisant la mise en cache du code dans V8, en ignorant les service workers qui n'ont pas d'événement de récupération, en lançant les service workers de manière spéculative et en effectuant d'autres optimisations. Cependant, le temps de démarrage est toujours supérieur à zéro.
Facebook a porté à notre attention l'impact de ce problème et a demandé un moyen d'exécuter les requêtes de navigation en parallèle:
Le préchargement de la navigation à la rescousse
Le préchargement de navigation est une fonctionnalité qui vous permet d'indiquer : "Lorsque l'utilisateur effectue une requête de navigation GET, démarrez la requête réseau pendant le démarrage du service worker".
Le délai de démarrage est toujours présent, mais il ne bloque pas la requête réseau. L'utilisateur reçoit donc le contenu plus tôt.
Voici une vidéo illustrant cette fonctionnalité, dans laquelle le service worker reçoit un délai de démarrage délibéré de 500 ms à l'aide d'une boucle de type "while-loop" :
Voici la démonstration. Pour profiter des avantages du préchargement de la navigation, vous devez disposer d'un navigateur compatible.
Activer le préchargement de la navigation
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Vous pouvez appeler navigationPreload.enable()
à tout moment ou le désactiver avec navigationPreload.disable()
. Toutefois, comme votre événement fetch
doit l'utiliser, il est préférable de l'activer et de le désactiver dans l'événement activate
de votre service worker.
Utiliser la réponse préchargée
Le navigateur effectuera maintenant des préchargements pour les navigations, mais vous devrez toujours utiliser la réponse suivante:
addEventListener('fetch', event => {
event.respondWith(async function() {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response;
// Else try the network.
return fetch(event.request);
}());
});
event.preloadResponse
est une promesse qui se résout avec une réponse si:
- Le préchargement de navigation est activé.
- Il s'agit d'une requête
GET
. - Il s'agit d'une requête de navigation (que les navigateurs génèrent lors du chargement de pages, y compris des iFrames).
Sinon, event.preloadResponse
est toujours là, mais il est résolu avec undefined
.
Réponses personnalisées pour les préchargements
Si votre page a besoin de données issues du réseau, le moyen le plus rapide consiste à les demander dans le service worker et à créer une réponse diffusée unique contenant certaines parties du cache et des parties provenant du réseau.
Imaginons que nous voulions afficher un article:
addEventListener('fetch', event => {
const url = new URL(event.request.url);
const includeURL = new URL(url);
includeURL.pathname += 'include';
if (isArticleURL(url)) {
event.respondWith(async function() {
// We're going to build a single request from multiple parts.
const parts = [
// The top of the page.
caches.match('/article-top.include'),
// The primary content
fetch(includeURL)
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include')),
// The bottom of the page
caches.match('/article-bottom.include')
];
// Merge them all together.
const {done, response} = await mergeResponses(parts);
// Wait until the stream is complete.
event.waitUntil(done);
// Return the merged response.
return response;
}());
}
});
Dans ce qui précède, mergeResponses
est une petite fonction qui fusionne les flux de chaque requête. Cela signifie que nous pouvons afficher l'en-tête mis en cache
lorsque le contenu du réseau est en flux continu.
Cette méthode est plus rapide que le "shell de l'application". au fur et à mesure que la requête réseau est effectuée en même temps que la demande de page. De plus, le contenu peut être diffusé sans piratage majeur.
Cependant, la requête pour includeURL
sera retardée par le démarrage du service worker. Nous pouvons également utiliser le préchargement de navigation pour résoudre ce problème, mais dans le cas présent, nous ne voulons pas précharger la page entière, mais précharger une inclusion.
Pour ce faire, un en-tête est envoyé avec chaque requête de préchargement:
Service-Worker-Navigation-Preload: true
Le serveur peut l'utiliser pour envoyer un contenu différent pour les requêtes de préchargement de navigation par rapport à une requête de navigation standard. N'oubliez pas d'ajouter un en-tête Vary: Service-Worker-Navigation-Preload
afin que les caches sachent que vos réponses diffèrent.
Nous pouvons maintenant utiliser la requête de préchargement:
// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
// Else do a normal fetch
.then(r => r || fetch(includeURL))
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include'));
const parts = [
caches.match('/article-top.include'),
networkContent,
caches.match('/article-bottom')
];
Modifier l'en-tête
Par défaut, la valeur de l'en-tête Service-Worker-Navigation-Preload
est true
, mais vous pouvez la définir comme vous le souhaitez:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Vous pouvez, par exemple, le définir sur l'ID du dernier message que vous avez mis en cache localement, afin que le serveur ne renvoie que des données plus récentes.
Obtenir l'état
Vous pouvez rechercher l'état du préchargement de la navigation à l'aide de getState
:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Un grand merci à Matt Falkenhagen et Tsuyoshi Horo pour leur travail sur cette fonctionnalité et pour leur aide concernant cet article. Nous remercions sincèrement toutes les personnes impliquées dans l'effort de normalisation.