Mit dem Navigations-Preload können Sie die Startzeit des Service Workers umgehen, indem Sie Anfragen parallel stellen.
Zusammenfassung
- In einigen Fällen kann die Startzeit des Service Workers eine Netzwerkantwort verzögern.
- Navigation Preload ist in den drei wichtigsten Browser-Engines verfügbar und behebt dieses Problem, indem Sie die Anfrage parallel zum Start des Service Workers stellen können.
- Sie können Preload-Anfragen anhand eines Headers von regulären Navigationsvorgängen unterscheiden und unterschiedliche Inhalte bereitstellen.
Das Problem
Wenn Sie eine Website aufrufen, auf der Fetch-Ereignisse von einem Service Worker verarbeitet werden, fragt der Browser den Service Worker nach einer Antwort. Dazu muss der Service Worker gestartet werden (falls er noch nicht ausgeführt wird) und das Fetch-Ereignis gesendet werden.
Die Startzeit hängt vom Gerät und den Bedingungen ab. Sie liegt in der Regel bei etwa 50 ms. Auf Mobilgeräten sind es eher 250 ms. In extremen Fällen (langsame Geräte, CPU-Überlastung) kann es über 500 ms dauern. Da der Service Worker jedoch für eine vom Browser festgelegte Zeit zwischen Ereignissen aktiv bleibt, tritt diese Verzögerung nur gelegentlich auf, z. B. wenn der Nutzer über einen neuen Tab oder eine andere Website auf Ihre Website zugreift.
Die Startzeit ist kein Problem, wenn Sie aus dem Cache antworten, da der Vorteil, das Netzwerk zu überspringen, größer ist als die Startverzögerung. Wenn Sie jedoch über das Netzwerk antworten…
Die Netzwerkanfrage wird durch das Starten des Service Workers verzögert.
Wir arbeiten weiter daran, die Startzeit zu verkürzen, indem wir Code-Caching in V8 verwenden, Service Worker ohne Fetch-Ereignis überspringen und Service Worker spekulativ starten. Die Bootzeit ist jedoch immer größer als null.
Facebook hat uns auf die Auswirkungen dieses Problems aufmerksam gemacht und um eine Möglichkeit gebeten, Navigationsanfragen parallel auszuführen:
Navigation vorab laden
Mit der Navigationsvorabladung können Sie festlegen, dass die Netzwerkanfrage gestartet wird, während der Service Worker hochfährt, wenn der Nutzer eine GET-Navigationsanfrage stellt.
Die Startverzögerung ist weiterhin vorhanden, blockiert aber die Netzwerkanfrage nicht. Der Nutzer erhält die Inhalte also schneller.
Hier sehen Sie ein Video, in dem der Service Worker mit einer While-Schleife bewusst um 500 ms verzögert gestartet wird:
Hier ist die Demo. Damit Sie die Vorteile des Vorabladens von Navigationen nutzen können, benötigen Sie einen Browser, der diese Funktion unterstützt.
Vorabladen der Navigation aktivieren
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Sie können navigationPreload.enable()
jederzeit aufrufen oder mit navigationPreload.disable()
deaktivieren. Da Ihr fetch
-Ereignis jedoch darauf zugreifen muss, ist es am besten, es im activate
-Ereignis Ihres Service Workers zu aktivieren und zu deaktivieren.
Vorgeladene Antwort verwenden
Der Browser führt jetzt Preloads für Navigationsvorgänge aus. Sie müssen die Antwort aber weiterhin verwenden:
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
ist ein Promise, das mit einer Antwort aufgelöst wird, wenn:
- Das Vorabladen der Navigation ist aktiviert.
- Die Anfrage ist eine
GET
-Anfrage. - Die Anfrage ist eine Navigationsanfrage, die von Browsern beim Laden von Seiten (einschließlich iFrames) generiert wird.
Andernfalls ist event.preloadResponse
weiterhin vorhanden, wird aber mit undefined
aufgelöst.
Benutzerdefinierte Antworten für Preloads
Wenn Ihre Seite Daten aus dem Netzwerk benötigt, ist es am schnellsten, diese im Service Worker anzufordern und eine einzelne gestreamte Antwort zu erstellen, die Teile aus dem Cache und Teile aus dem Netzwerk enthält.
Angenommen, wir möchten einen Artikel anzeigen:
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;
}());
}
});
Im obigen Beispiel ist mergeResponses
eine kleine Funktion, die die Streams der einzelnen Anfragen zusammenführt. So können wir den im Cache gespeicherten Header anzeigen, während die Netzwerkinhalte gestreamt werden.
Das ist schneller als das „App-Shell“-Modell, da die Netzwerkanfrage zusammen mit der Seitenanfrage erfolgt und der Inhalt ohne größere Hacks gestreamt werden kann.
Die Anfrage für includeURL
wird jedoch durch die Startzeit des Service Workers verzögert. Wir können auch hier Navigation Preload verwenden, aber in diesem Fall möchten wir nicht die gesamte Seite, sondern nur ein Include vorab laden.
Dazu wird mit jeder Preload-Anfrage ein Header gesendet:
Service-Worker-Navigation-Preload: true
Der Server kann damit für Navigations-Preload-Anfragen andere Inhalte senden als für eine reguläre Navigationsanfrage. Denken Sie nur daran, einen Vary: Service-Worker-Navigation-Preload
-Header hinzuzufügen, damit Caches wissen, dass sich Ihre Antworten unterscheiden.
Jetzt können wir die Preload-Anfrage verwenden:
// 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')
];
Kopfzeile ändern
Standardmäßig ist der Wert des Service-Worker-Navigation-Preload
-Headers true
. Sie können ihn jedoch beliebig festlegen:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Sie können sie beispielsweise auf die ID des letzten lokal gespeicherten Beitrags festlegen, damit der Server nur neuere Daten zurückgibt.
Zustand abrufen
Sie können den Status des Navigations-Preloads mit getState
aufrufen:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Vielen Dank an Matt Falkenhagen und Tsuyoshi Horo für ihre Arbeit an dieser Funktion und ihre Hilfe bei diesem Artikel. Ein großes Dankeschön an alle, die an der Standardisierung beteiligt waren.