Wstępne wczytywanie nawigacji pozwala skrócić czas uruchamiania skryptu service worker przez równoczesne wysyłanie żądań.
Podsumowanie
- W niektórych sytuacjach czas uruchamiania skryptu service worker może opóźnić odpowiedź sieci.
- Dostępne w 3 głównych mechanizmach przeglądarek wstępne wczytywanie rozwiązuje ten problem, umożliwiając wysyłanie żądań równolegle z uruchamianiem skryptu service worker.
- Żądania wstępnego wczytywania możesz odróżnić od zwykłych elementów nawigacyjnych za pomocą nagłówka i wyświetlać różne treści.
Problem
Gdy wejdziesz na stronę, która używa skryptu service worker do obsługi zdarzeń pobierania, przeglądarka prosi go o odpowiedź. Wiąże się to z uruchomieniem skryptu service worker (jeśli nie jest jeszcze uruchomiony) i wysłaniem zdarzenia pobierania.
Czas uruchamiania zależy od urządzenia i warunków. Zwykle zajmuje to około 50 ms. Na komórce to 250 ms. W skrajnych przypadkach (powolne urządzenie, niedziałający procesor) może trwać dłużej niż 500 ms. Ponieważ jednak skrypt service worker pozostaje w trybie ustalonym przez przeglądarkę między zdarzeniami, opóźnienie występuje rzadko, na przykład gdy użytkownik przechodzi do witryny ze nowej karty lub z innej witryny.
Czas uruchamiania nie stanowi problemu, jeśli odpowiadasz z pamięci podręcznej, ponieważ korzyści płynące z pominięcia sieci są większe niż opóźnienie rozruchu. Ale jeśli odpowiadasz, używając sieci...
Żądanie sieciowe jest opóźnione przez uruchomienie skryptu service worker.
W dalszym ciągu zmniejszamy czas uruchamiania, używając buforowania kodu w wersji 8, pomijając mechanizmy Service Worker, które nie mają zdarzenia pobierania, spekulacyjnie uruchamiając mechanizmy Service Worker i inne optymalizacje. Czas uruchamiania będzie jednak zawsze dłuższy niż 0.
Facebook zwrócił naszą uwagę na ten problem i poprosił o umożliwienie równoległego wykonywania żądań dotyczących nawigacji:
Wstępne wczytywanie nawigacji na ratunek
Wstępne wczytywanie nawigacji to funkcja, która pozwala powiedzieć: „Gdy użytkownik wysyła żądanie nawigacji GET, uruchamiaj żądanie sieciowe podczas uruchamiania skryptu service worker”.
Opóźnienie uruchamiania jest nadal widoczne, ale nie blokuje żądania sieciowego, więc użytkownik szybciej uzyskuje dostęp do treści.
Oto film pokazujący działanie aplikacji. Skrypt service worker ma celowo 500-sekundowe opóźnienie na uruchomienie z użyciem pętli podczas działania:
Oto prezentacja. Aby korzystać z zalet wstępnego wczytywania nawigacji, musisz mieć przeglądarkę, która je obsługuje.
Włącz wstępne wczytywanie nawigacji
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Możesz zadzwonić do firmy navigationPreload.enable()
w dowolnej chwili lub wyłączyć ją za pomocą usługi navigationPreload.disable()
. Zdarzenie fetch
musi jednak korzystać z tego zdarzenia, więc najlepiej je włączyć lub wyłączyć w zdarzeniu activate
skryptu service worker.
Korzystanie ze wstępnie wczytanej odpowiedzi
Teraz przeglądarka będzie wstępnie wczytywała nawigację, ale nadal musisz użyć 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 kończy się odpowiedzią, jeśli:
- Wstępne wczytywanie nawigacji jest włączone.
- Żądanie ma postać
GET
. - Żądanie jest żądaniem nawigacyjnym (generowanym przez przeglądarki podczas wczytywania stron, w tym elementów iframe).
W przeciwnym razie event.preloadResponse
nadal występuje, ale rozwiązuje się jako undefined
.
Niestandardowe odpowiedzi dla wstępnego wczytywania
Jeśli strona potrzebuje danych z sieci, najszybszym sposobem jest wysłanie żądania do skryptu service worker i utworzenie pojedynczej odpowiedzi przesyłanej strumieniowo z częściami 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 scala strumienie każdego żądania. Oznacza to, że możemy wyświetlać nagłówek z pamięci podręcznej, gdy zawartość sieci jest przesyłana strumieniowo.
To działanie jest szybsze niż model „powłoka aplikacji”, ponieważ żądanie sieciowe jest wysyłane razem z żądaniem strony i treści można przesyłać strumieniowo bez poważnych ataków hakerskich.
Żądanie includeURL
zostanie jednak opóźnione przez czas uruchamiania skryptu service worker. Aby rozwiązać ten problem, możemy też użyć wstępnego wczytywania nawigacji, ale w tym przypadku nie chcemy wstępnie wczytywać całej strony, chcemy wstępnie załadować plik uwzględniony.
W związku z tym przy każdym żądaniu wstępnego wczytywania wysyłany jest nagłówek:
Service-Worker-Navigation-Preload: true
Serwer może go używać do wysyłania innej treści w przypadku żądań wstępnego wczytywania nawigacji niż w przypadku zwykłego żądania nawigacji. Pamiętaj tylko o dodaniu nagłówka Vary: Service-Worker-Navigation-Preload
, dzięki czemu pamięci podręcznej będą wiedzieć, że Twoje odpowiedzi się różnią.
Teraz możemy skorzystać z żądania wstępnego wczytania:
// 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ślną wartością nagłówka Service-Worker-Navigation-Preload
jest true
, ale możesz ją ustawić tak, jak chcesz:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Możesz na przykład ustawić identyfikator ostatniego posta zapisanego lokalnie w pamięci podręcznej, aby serwer zwracał tylko nowsze dane.
Sprawdzanie stanu
Stan wstępnego wczytywania nawigacji możesz sprawdzić za pomocą funkcji 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 Tsuyoshiem Horo za pracę nad tą funkcją. Pomogli nam również w tym artykule. Serdecznie dziękujemy wszystkim zaangażowanym w pracę standaryzacyjną.