Życie pracowników service worker

Trudno jest określić, co robią pracownicy service worker, jeśli nie znają ich cyklu życia. Ich wewnętrzne działanie wydaje się nieprzejrzyste, a nawet ogólnikowe. Warto pamiętać, że – tak jak w przypadku każdego innego interfejsu API przeglądarki – zachowania skryptu service worker są jasno zdefiniowane i określone, a ich działania są możliwe w trybie offline przy jednoczesnym umożliwieniu aktualizacji bez zakłócania wrażeń użytkownika.

Zanim zagłębimy się w obszar roboczy, zapoznaj się z cyklem życia skryptu service worker, by móc korzystać z odpowiednich funkcji.

Definicje terminów

Zanim zagłębisz się w cykl życia mechanizmów Service Worker, warto poznać jego działanie.

Kontrola i zakres

Koncepcja kontroli jest kluczowa dla zrozumienia sposobu działania mechanizmów Service Worker. Strona, którą określa się jako kontrolowana przez mechanizm Service Worker, to strona, która pozwala mu na przechwytywanie żądań sieciowych w jego imieniu. Skrypt service worker jest dostępny i może wykonywać zadania na stronie w danym zakresie.

Zakres

Zakres skryptu service worker zależy od jego lokalizacji na serwerze WWW. Jeśli skrypt service worker działa na stronie /subdir/index.html w lokalizacji /subdir/sw.js, jego zakres to /subdir/. Aby zobaczyć, jak działa zakres, zapoznaj się z tym przykładem:

  1. Otwórz stronę https://service-worker-scope-viewer.glitch.me/subdir/index.html. Pojawi się komunikat z informacją, że żaden skrypt service worker nie kontroluje strony. Jednak strona rejestruje skrypt service worker z domeny https://service-worker-scope-viewer.glitch.me/subdir/sw.js.
  2. Odśwież stronę. Skrypt service worker został zarejestrowany i jest teraz aktywny, więc kontroluje stronę. Formularz zawierający zakres skryptu service worker, bieżący stan i jego adres URL będzie widoczny. Uwaga: konieczność ponownego wczytania strony nie ma nic wspólnego z zakresem, tylko cyklem życia skryptu service worker, który omówimy później.
  3. Otwórz stronę https://service-worker-scope-viewer.glitch.me/index.html. Mimo że skrypt service worker był zarejestrowany w tym źródle, nadal wyświetla się komunikat z informacją, że nie ma aktualnego skryptu service worker. Dzieje się tak, ponieważ ta strona nie znajduje się w zakresie zarejestrowanego skryptu service worker.

Zakres określa, jakie strony kontroluje skrypt service worker. W tym przykładzie oznacza to, że skrypt service worker wczytany z domeny /subdir/sw.js może kontrolować tylko strony znajdujące się w drzewie /subdir/ lub w jego poddrzewie.

Określanie zakresu działa domyślnie poniżej, ale maksymalny dozwolony zakres można zastąpić, ustawiając nagłówek odpowiedzi Service-Worker-Allowed, a także przekazując opcję scope do metody register.

Jeśli nie ma istotnego powodu, aby ograniczyć zakres skryptu service worker do podzbioru źródła, ładuj je z katalogu głównego serwera WWW, aby jego zakres był jak najszerszy i nie musisz się przejmować nagłówkiem Service-Worker-Allowed. W ten sposób jest to znacznie prostsze.

Klient

Gdy mówi się, że skrypt service worker kontroluje stronę, w rzeczywistości kontroluje klienta. Klient to dowolna otwarta strona, której adres URL należy do zakresu danego skryptu service worker. Chodzi o wystąpienia typu WindowClient.

Cykl życia nowego skryptu service worker

Aby skrypt service worker mógł kontrolować stronę, najpierw musi ją powstać. Zacznijmy od tego, co się dzieje po wdrożeniu nowego skryptu service worker w witrynie bez aktywnego skryptu service worker.

Rejestracja

Rejestracja to początkowy etap cyklu życia skryptu service worker:

<!-- In index.html, for example: -->
<script>
  // Don't register the service worker
  // until the page has fully loaded
  window.addEventListener('load', () => {
    // Is service worker available?
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js').then(() => {
        console.log('Service worker registered!');
      }).catch((error) => {
        console.warn('Error registering service worker:');
        console.warn(error);
      });
    }
  });
</script>

Ten kod jest uruchamiany w wątku głównym i wykona te działania:

  1. Ponieważ pierwsza wizyta użytkownika w witrynie ma miejsce bez zarejestrowanego skryptu service worker, przed jego zarejestrowaniem poczekaj, aż strona zostanie w pełni wczytana. Pozwala to uniknąć rywalizacji o przepustowość, jeśli skrypt service worker umieszcza cokolwiek w pamięci podręcznej.
  2. Skrypt service worker jest dobrze obsługiwany, ale szybkie sprawdzenie pozwala uniknąć błędów w przeglądarkach, w których nie jest obsługiwana.
  3. Gdy strona zostanie wczytana w pełni i jeśli skrypt service worker jest obsługiwany, zarejestruj /sw.js.

Kilka ważnych informacji:

  • Skrypty service worker są dostępne tylko przez HTTPS lub lokalnego hosta.
  • Jeśli zawartość skryptu service worker zawiera błędy składni, rejestracja nie powiedzie się, a skrypta ta zostanie odrzucona.
  • Przypomnienie: mechanizmy Service Worker działają w zakresie. W tym przypadku zakres obejmuje całe źródło, ponieważ zostało ono załadowane z katalogu głównego.
  • Po rozpoczęciu rejestracji stan skryptu service worker jest ustawiany na 'installing'.

Po zakończeniu rejestracji rozpocznie się instalacja.

Instalacja

Skrypt service worker uruchamia zdarzenie install po rejestracji. Skrypt install jest wywoływany tylko raz na skrypt service worker i nie zostanie uruchomiony ponownie, dopóki nie zostanie zaktualizowany. Wywołanie zwrotne zdarzenia install można zarejestrować w zakresie instancji roboczej za pomocą addEventListener:

// /sw.js
self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v1';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v1'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.bc7b80b7.css',
      '/css/home.fe5d0b23.css',
      '/js/home.d3cc4ba4.js',
      '/js/jquery.43ca4933.js'
    ]);
  }));
});

Spowoduje to utworzenie nowej instancji Cache i wstępne buforowanie zasobów. Będziemy mieć wiele okazji do omówienia funkcji buforowania w późniejszym czasie, więc skoncentrujmy się na roli event.waitUntil. event.waitUntil akceptuje obietnicę i czeka, aż zostanie ona zrealizowana. W tym przykładzie ta obietnica ma dwie asynchroniczne funkcje:

  1. Tworzy nową instancję Cache o nazwie 'MyFancyCache_v1'.
  2. Po utworzeniu pamięci podręcznej tablica adresów URL zasobów jest wstępnie przechowywana w pamięci podręcznej przy użyciu asynchronicznej metody addAll.

Instalacja się nie uda, jeśli obietnice przekazane do event.waitUntil zostaną odrzucone. W takim przypadku skrypt service worker jest odrzucany.

Jeśli obiecane warunki resolve, instalacja się powiedzie, a stan skryptu service worker zmieni się na 'installed' i zostanie aktywowany.

Działania

Jeśli rejestracja i instalacja się powiedzie, skrypt service worker aktywuje się i jego stan zmieni się na 'activating'Możesz wykonać tę pracę podczas aktywacji w zdarzeniu activate tego skryptu. Typowym zadaniem w przypadku tego zdarzenia jest przycinanie starych pamięci podręcznych, ale w przypadku zupełnie nowego skryptu service worker nie jest to obecnie istotne i zostanie ono rozszerzone, gdy będziemy omawiać aktualizacje mechanizmów Service Worker.

W przypadku nowych skryptów service worker activate uruchamia się natychmiast po pomyślnym zakończeniu działania install. Po zakończeniu aktywacji stan skryptu service worker zmieni się na 'activated'. Zwróć uwagę, że domyślnie nowy skrypt service worker zacznie kontrolować stronę dopiero w trakcie następnego etapu nawigacji lub odświeżenia.

Obsługa aktualizacji skryptu service worker

Po wdrożeniu pierwszego skryptu service worker trzeba będzie go później zaktualizować. Aktualizacja może być na przykład wymagana, jeśli w logice obsługi żądań lub w pamięci podręcznej zajdą zmiany.

Kiedy pojawiają się aktualizacje

Przeglądarki sprawdzają dostępność aktualizacji mechanizmu service worker, gdy:

  • Użytkownik przechodzi do strony w zakresie skryptu service worker.
  • Skrypt navigator.serviceWorker.register() jest wywoływany z adresem URL innym niż obecnie zainstalowany skrypt service worker – ale nie zmieniaj adresu URL takiego skryptu.
  • Skrypt navigator.serviceWorker.register() jest wywoływany z tym samym adresem URL co zainstalowany skrypt service worker, ale z innym zakresem. Aby tego uniknąć, w miarę możliwości należy utrzymywać zakres na poziomie głównym źródła.
  • Gdy zdarzenia takie jak 'push' lub 'sync' zostały wywołane w ciągu ostatnich 24 godzin, ale nie musisz się jeszcze nimi przejmować.

Jak przebiegają aktualizacje

Ważne jest, by wiedzieć, kiedy przeglądarka aktualizuje skrypt service worker, ale ważne jest też, by dowiedzieć się, jak to zrobić. Zakładając, że URL lub zakres skryptu service worker pozostają bez zmian, obecnie zainstalowany skrypt service worker aktualizuje się tylko wtedy, gdy jego zawartość ulegnie zmianie.

Przeglądarki wykrywają zmiany na kilka sposobów:

  • Wszelkie zmiany bajtów w skryptach żądane przez importScripts (w stosownych przypadkach).
  • Wszelkie zmiany w kodzie najwyższego poziomu skryptu service worker, które mają wpływ na odcisk cyfrowy wygenerowany przez przeglądarkę.

Przeglądarka wykonuje tu wiele pracy. Aby mieć pewność, że przeglądarka ma wszystko, czego potrzebuje do niezawodnego wykrywania zmian w treści skryptu service worker, nie blokuj pamięci podręcznej HTTP ani nie zmieniaj nazwy pliku. Przeglądarka automatycznie sprawdza dostępność aktualizacji, gdy następuje przejście do nowej strony w zakresie skryptu service worker.

Ręczne uruchamianie sprawdzania dostępności aktualizacji

Jeśli chodzi o aktualizacje, logika rejestracji zwykle nie powinna się zmieniać. Wyjątkiem może być jednak sytuacja, w której sesje w witrynie są długie. Może się to zdarzyć w aplikacjach jednostronicowych, w których żądania nawigacji występują rzadko, ponieważ na początku cyklu życia aplikacja zwykle napotyka jedno żądanie nawigacji. W takich sytuacjach w wątku głównym może zostać uruchomiona aktualizacja ręczna:

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

W przypadku tradycyjnych witryn lub w każdym przypadku, gdy sesje użytkownika nie trwają długo, ręczne aktualizacje nie są prawdopodobnie konieczne.

Instalacja

Gdy korzystasz z usługi tworzenia pakietów do generowania zasobów statycznych, takie zasoby będą miały w nazwie hasze, np. framework.3defa9d2.js. Załóżmy, że niektóre z tych zasobów są wstępnie zapisane w pamięci podręcznej, aby można było później korzystać z nich offline. Wymaga to aktualizacji skryptu service worker do wstępnego buforowania zaktualizowanych zasobów:

self.addEventListener('install', (event) => {
  const cacheKey = 'MyFancyCacheName_v2';

  event.waitUntil(caches.open(cacheKey).then((cache) => {
    // Add all the assets in the array to the 'MyFancyCacheName_v2'
    // `Cache` instance for later use.
    return cache.addAll([
      '/css/global.ced4aef2.css',
      '/css/home.cbe409ad.css',
      '/js/home.109defa4.js',
      '/js/jquery.38caf32d.js'
    ]);
  }));
});

2 rzeczy różnią się od pierwszego przykładowego zdarzenia install od wcześniejszego:

  1. Zostanie utworzona nowa instancja Cache z kluczem 'MyFancyCacheName_v2'.
  2. Nazwy zasobów w pamięci podręcznej uległy zmianie.

Warto zapamiętać, że zaktualizowany skrypt service worker jest instalowany równolegle z poprzednim. Oznacza to, że stary skrypt service worker nadal kontroluje wszystkie otwarte strony, a po instalacji nowy mechanizm przechodzi w stan oczekiwania, aż zostanie aktywowany.

Domyślnie nowy mechanizm Service Worker zostanie aktywowany, gdy żaden klient nie jest kontrolowany przez stary. Dzieje się tak, gdy wszystkie otwarte karty danej witryny są zamknięte.

Działania

Po zainstalowaniu zaktualizowanego skryptu service worker i zakończeniu etapu oczekiwania aktywuje się on, a stary skrypt jest odrzucany. Typowe zadanie wykonywane w zaktualizowanym zdarzeniu activate skryptu service worker to usunięcie starych pamięci podręcznych. Usuń stare pamięci podręczne, pobierając klucze wszystkich otwartych instancji Cache z caches.keys i usuwając pamięci podręczne, które nie znajdują się na zdefiniowanej liście dozwolonych, za pomocą caches.delete:

self.addEventListener('activate', (event) => {
  // Specify allowed cache keys
  const cacheAllowList = ['MyFancyCacheName_v2'];

  // Get all the currently active `Cache` instances.
  event.waitUntil(caches.keys().then((keys) => {
    // Delete all caches that aren't in the allow list:
    return Promise.all(keys.map((key) => {
      if (!cacheAllowList.includes(key)) {
        return caches.delete(key);
      }
    }));
  }));
});

Stare pamięci podręczne nie są czyszczone. Musimy to zrobić sami, albo ryzykujesz przekroczenie limitów miejsca na dane. Ponieważ nazwa 'MyFancyCacheName_v1' z pierwszego skryptu service worker jest nieaktualna, lista dozwolonych instancji pamięci podręcznej jest aktualizowana, aby określić element 'MyFancyCacheName_v2', który usuwa pamięci podręczne o innej nazwie.

Zdarzenie activate zakończy się po usunięciu starej pamięci podręcznej. W tym momencie nowy skrypt service worker przejmie kontrolę nad stroną, zastępując starą.

Cykl życia trwa nieprzerwanie

Niezależnie od tego, czy instancja robocza ma być używana do wdrażania i aktualizacji skryptu service worker, czy też bezpośrednio używa się interfejsu Service Worker API, warto poznać cykl życia mechanizmów Service Worker. W związku z tym zachowania pracowników service worker powinny wydawać się bardziej logiczne niż tajemnicze.

Jeśli chcesz dowiedzieć się więcej na ten temat, zapoznaj się z tym artykułem Jake Archibalda. Istnieje wiele niuansów w tym, jak przebiega cały cykl życia usługi, ale jest ona oczywiste, dzięki czemu ta wiedza może zajść daleko, gdy korzystasz z Workbox.