Wstępne wczytanie nawigacji pozwala skrócić czas uruchamiania instancji roboczej usługi przez równoległe wysyłanie żądań.
Podsumowanie
- W niektórych sytuacjach czas uruchamiania mechanizmu Service Worker może opóźnić odpowiedź sieciową.
- Funkcja wstępne wczytywanie przez nawigację, dostępna w 3 głównych wyszukiwarkach, rozwiązuje ten problem, umożliwiając wysłanie żądania równolegle z uruchamianiem mechanizmu 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 przechodzisz do witryny, która do obsługi zdarzeń pobierania używa skryptu service worker, przeglądarka prosi o odpowiedź. Wymaga to uruchomienia skryptu service worker (jeśli jeszcze nie jest uruchomiony) i wysyłania zdarzenia pobierania.
Czas uruchamiania zależy od urządzenia i warunków. Zwykle zajmuje to około 50 ms. Na komórkach trwa to już ponad 250 ms. W skrajnych przypadkach (powolne urządzenia lub problemy z procesorem) wartość może przekraczać 500 ms. Ponieważ jednak mechanizm Service Worker pozostaje aktywny między zdarzeniami przez czas określony przez przeglądarkę, opóźnienie występuje tylko od czasu do czasu, np. gdy użytkownik przechodzi do Twojej witryny z nowej karty lub z innej witryny.
Czas uruchamiania nie stanowi problemu, jeśli odpowiadasz z pamięci podręcznej, ponieważ pominięcie sieci jest większe niż opóźnienie uruchamiania. Jeśli jednak odpowiadasz, korzystając z sieci...
Żądanie sieciowe jest opóźnione przez uruchamianie skryptu service worker.
Nadal skracamy czas uruchamiania, używając buforowania kodu w wersji 8, pomijając mechanizmy Service Worker, które nie mają zdarzenia pobierania, uruchamiając mechanizmy Service Worker w sposób spekulacyjny i stosując inne optymalizacje. Jednak czas uruchamiania będzie zawsze większy od zera.
Facebook zwrócił naszą uwagę na wpływ tego problemu i poprosił o opracowanie sposobu równoległego wykonywania żądań związanych z nawigacją:
Nawigacja wstępnie wczytuje się na ratunek
Wstępne wczytywanie nawigacji to funkcja, która pozwala powiedzieć: „Gdy użytkownik wysyła żądanie nawigacji GET, uruchom żądanie sieciowe podczas uruchamiania skryptu service worker”.
Opóźnienie uruchomienia nadal występuje, ale nie blokuje żądania sieciowego, więc użytkownik zapoznaje się z treścią wcześniej.
Oto film pokazujący, jak to działa. Skrypt service worker ma celowe 500 ms opóźnienia uruchomienia z wykorzystaniem pętli „if”:
Oto jej wersja demonstracyjna. Aby skorzystać z zalet wstępnego wczytywania nawigacji, musisz mieć przeglądarkę, która ją obsługuje.
Aktywuj 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 navigationPreload.enable()
w dowolnym momencie lub wyłączyć go za pomocą navigationPreload.disable()
. Zdarzenie fetch
musi jednak z niego korzystać, więc najlepiej jest je włączać i wyłączać w zdarzeniu activate
skryptu service worker.
Używanie wstępnie wczytanej odpowiedzi
Teraz przeglądarka rozpocznie wstępne wczytywanie elementów nawigacyjnych, ale nadal musisz skorzystać z 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:
- Włączone jest wstępne wczytywanie nawigacji.
- Żądanie ma postać
GET
. - Żądanie to żądanie nawigacji (które przeglądarki generują podczas wczytywania stron, łącznie z elementami iframe).
W przeciwnym razie błąd event.preloadResponse
jest nadal dostępny, ale występuje błąd undefined
.
Niestandardowe odpowiedzi na potrzeby wstępnego wczytywania
Jeśli strona potrzebuje danych z sieci, najszybszym sposobem jest żądanie ich w skrypcie service worker i utworzenie pojedynczej odpowiedzi strumieniowej 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 przesyłania treści z sieci.
To szybsze niż „powłoka aplikacji” gdy żądanie sieciowe jest wysyłane razem z żądaniem strony, dzięki czemu treści mogą być przesyłane strumieniowo bez poważnych ataków hakerów.
Żądanie dotyczące includeURL
będzie jednak opóźnione o 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, tylko wstępnie ładować stronę z uwzględnieniem.
W tym celu razem z każdym żądaniem wstępnego wczytywania wysyłany jest nagłówek:
Service-Worker-Navigation-Preload: true
Serwer może go używać do wysyłania innych treści w żądaniach wstępnego wczytywania nawigacji niż w przypadku zwykłych żądań nawigacji. Pamiętaj tylko o dodaniu nagłówka Vary: Service-Worker-Navigation-Preload
, aby pamięci podręczne wiedzieli, ż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')
];
Zmiana nagłówka
Domyślnie wartość nagłówka Service-Worker-Navigation-Preload
to true
, ale możesz to zmienić:
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.
Pobieram informacje o stanie
Stan wstępnego wczytywania nawigacji możesz sprawdzić za pomocą narzędzia 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 Tsuyoshim Horo za pracę nad tą funkcją oraz za pomoc przy tym artykule. Serdecznie dziękujemy wszystkim osobom zaangażowanym w pracę na rzecz standaryzacji.