Przyspieszenie skryptu service worker dzięki wstępnym ładowaniu nawigacji

Wstępne wczytywanie nawigacji pozwala skrócić czas uruchamiania skryptu service worker przez równoczesne wysyłanie żądań.

Jake Archibald
Jake Archibald

Obsługa przeglądarek

  • 59
  • 18
  • 99
  • 15,4

Źródło

Podsumowanie

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...

Uruchamianie
Prośba o nawigację

Żą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:

Uruchamianie
Prośba o nawigację



Zauważyliśmy, że tak.

„Wstępne wczytywanie nawigacji” na pomoc

Wstępne wczytywanie nawigacji to funkcja, która pozwala powiedzieć: „Hej, gdy użytkownik wysyła żądanie nawigacji GET, uruchom żą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.

Aktywowanie wstępnego wczytywania 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 tej funkcji, więc najlepiej włączyć lub wyłączyć je 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.

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')
];

Zmiana 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ą.

Część serii Nowo interoperacyjnie