Wstępne wczytywanie nawigacji pozwala skrócić czas uruchamiania usługi Service Worker dzięki równoległemu wysyłaniu żądań.
Podsumowanie
- W niektórych sytuacjach czas uruchamiania service workera może opóźnić odpowiedź sieci.
- Wstępne wczytywanie nawigacji, dostępne w 3 głównych silnikach przeglądarek, rozwiązuje ten problem, umożliwiając wysyłanie żądania równolegle z uruchamianiem usługi Service Worker.
- Żądania wstępnego wczytywania możesz odróżnić od zwykłych nawigacji za pomocą nagłówka i wyświetlać różne treści.
Problem
Gdy przejdziesz do witryny, która używa service workera do obsługi zdarzeń pobierania, przeglądarka poprosi go o odpowiedź. Obejmuje to uruchomienie procesu service worker (jeśli nie jest jeszcze uruchomiony) i wysłanie zdarzenia pobierania.
Czas uruchamiania zależy od urządzenia i warunków. Zwykle wynosi około 50 ms. Na urządzeniach mobilnych jest to około 250 ms. W skrajnych przypadkach (wolne urządzenia, obciążony procesor) może to być ponad 500 ms. Jednak ponieważ proces roboczy usługi pozostaje aktywny przez określony przez przeglądarkę czas między zdarzeniami, to opóźnienie występuje tylko sporadycznie, np. gdy użytkownik przechodzi do Twojej witryny z nowej karty lub innej witryny.
Czas uruchamiania nie stanowi problemu, jeśli odpowiadasz z pamięci podręcznej, ponieważ korzyść z pominięcia sieci jest większa niż opóźnienie uruchamiania. Jeśli jednak odpowiadasz za pomocą sieci…
Żądanie sieciowe jest opóźnione przez uruchomienie usługi Service Worker.
Stale skracamy czas uruchamiania, korzystając z pamięci podręcznej kodu w V8, pomijając procesy service worker, które nie mają zdarzenia pobierania, spekulatywnie uruchamiając procesy service worker i stosując inne optymalizacje. Czas uruchamiania zawsze będzie jednak większy niż zero.
Facebook zwrócił naszą uwagę na wpływ tego problemu i poprosił o możliwość wykonywania żądań nawigacji równolegle:
Wstępne wczytywanie nawigacji
Wstępne wczytywanie nawigacji to funkcja, która pozwala określić: „Gdy użytkownik wyśle żądanie nawigacji GET, rozpocznij żądanie sieciowe podczas uruchamiania usługi Service Worker”.
Opóźnienie przy uruchamianiu nadal występuje, ale nie blokuje żądania sieciowego, więc użytkownik szybciej otrzymuje treści.
Oto film pokazujący działanie tej funkcji. W tym przypadku opóźnienie uruchomienia service workera wynosi 500 ms, co uzyskano za pomocą pętli while:
Oto prezentacja Aby korzystać z zalet wstępnego wczytywania nawigacji, musisz mieć przeglądarkę, która je obsługuje.
Aktywowanie wstępnego ładowania nawigacji
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Możesz w każdej chwili zadzwonić pod numer navigationPreload.enable()
lub wyłączyć tę funkcję za pomocą przycisku navigationPreload.disable()
. Ponieważ jednak Twoje zdarzenie fetch
musi z niej korzystać, najlepiej jest włączać i wyłączać ją w zdarzeniu activate
w usłudze Service Worker.
Korzystanie z wstępnie wczytanej odpowiedzi
Przeglądarka będzie teraz wstępnie wczytywać dane do nawigacji, ale nadal musisz używać odpowiedzi:
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
to obietnica, która jest realizowana w postaci odpowiedzi, jeśli:
- Wstępne wczytywanie nawigacji jest włączone.
- Żądanie jest żądaniem
GET
. - Żądanie jest żądaniem nawigacyjnym (generowanym przez przeglądarki podczas wczytywania stron, w tym elementów iframe).
W przeciwnym razie event.preloadResponse
nadal będzie istnieć, ale będzie rozwiązywać się za pomocą undefined
.
Niestandardowe odpowiedzi na wstępne wczytywanie
Jeśli strona potrzebuje danych z sieci, najszybszym sposobem jest wysłanie żądania w usłudze Service Worker i utworzenie pojedynczej przesyłanej strumieniowo odpowiedzi zawierającej części z pamięci podręcznej i części z sieci.
Załóżmy, że chcemy wyświetlić artykuł:
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;
}());
}
});
W powyższym przykładzie mergeResponses
to mała funkcja, która łączy strumienie każdego żądania. Oznacza to, że możemy wyświetlać nagłówek z pamięci podręcznej, podczas gdy treści z sieci są przesyłane strumieniowo.
Jest to szybsze niż model „szkieletu aplikacji”, ponieważ żądanie sieciowe jest wysyłane wraz z żądaniem strony, a treści mogą być przesyłane strumieniowo bez większych obejść.
Żądanie includeURL
zostanie jednak opóźnione o czas uruchomienia skryptu service worker. Do rozwiązania tego problemu możemy też użyć wstępnego wczytywania nawigacji, ale w tym przypadku nie chcemy wstępnie wczytywać całej strony, tylko plik dołączany.
Aby to umożliwić, z każdym żądaniem wstępnego wczytywania wysyłany jest nagłówek:
Service-Worker-Navigation-Preload: true
Serwer może używać tego sygnału do wysyłania różnych treści w przypadku żądań wstępnego wczytywania nawigacji niż w przypadku zwykłych żądań nawigacji. Pamiętaj tylko, aby dodać nagłówek Vary: Service-Worker-Navigation-Preload
, aby pamięci podręczne wiedziały, że Twoje odpowiedzi się różnią.
Teraz możemy użyć żądania wstępnego wczytywania:
// 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')
];
Zmienianie nagłówka
Domyślnie wartość nagłówka Service-Worker-Navigation-Preload
to true
, ale możesz ustawić dowolną wartość:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Możesz na przykład ustawić go na identyfikator ostatniego wpisu zapisanego lokalnie w pamięci podręcznej, aby serwer zwracał tylko nowsze dane.
Pobieranie stanu
Stan wstępnego wczytywania nawigacji możesz sprawdzić za pomocą tego kodu: getState
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Dziękujemy Mattowi Falkenhagenowi i Tsuyoshi Horo za pracę nad tą funkcją i pomoc w przygotowaniu tego artykułu. Ogromne podziękowania dla wszystkich osób zaangażowanych w proces standaryzacji.