Co zespół ds. Bulletin dowiedział się o skryptach service worker podczas tworzenia aplikacji PWA.
To pierwszy z serii postów na blogu, w których zespół Google Bulletin dzieli się wnioskami z tworzenia PWA dla użytkowników zewnętrznych. W tych postach omówimy niektóre problemy, z którymi się spotkaliśmy, sposoby ich rozwiązania oraz ogólne wskazówki, które pomogą Ci uniknąć błędów. Nie jest to jednak pełny przegląd PWA. Chcemy w ten sposób podzielić się z Tobą naszą wiedzą.
W tym pierwszym poście przedstawimy trochę informacji ogólnych, a potem omówimy wszystko, co wiemy o usługach roboczych.
Tło
Bulletin był aktywnie rozwijany od połowy 2017 r. do połowy 2019 r.
Dlaczego zdecydowaliśmy się na tworzenie aplikacji internetowej
Zanim przejdziemy do procesu tworzenia, sprawdźmy, dlaczego w przypadku tego projektu tworzenie aplikacji internetowej na potrzeby tej platformy było atrakcyjną opcją:
- Możliwość szybkiego iterowania. Jest to szczególnie cenna informacja, ponieważ testy pilotażowe dotyczące wiadomości byłyby przeprowadzane na wielu rynkach.
- Jednolita baza kodu. Użytkownicy zostali podzieleni mniej więcej po równo między Androida i iOS. Dzięki PWA mogliśmy utworzyć jedną aplikację internetową, która działa na obu platformach. Zwiększyliśmy dzięki temu tempo pracy i wpływ zespołu.
- Aktualizacja następuje szybko i niezależnie od zachowań użytkownika. Progresywne aplikacje internetowe mogą aktualizować się automatycznie, co zmniejsza liczbę nieaktualnych klientów działających bez ograniczeń. Udało nam się wprowadzić przełomowe zmiany w backendzie w bardzo krótkim czasie, co pozwoliło na migrację danych klientów.
- Łatwa integracja z aplikacjami własnymi i aplikacjami innych firm Taka integracja była wymagana w przypadku aplikacji. W przypadku PWA często oznaczało to samo otwarcie adresu URL.
- Uproszczenie procesu instalowania aplikacji.
Nasze zasady
W usłudze Bulletin użyliśmy oprogramowania Polymer, ale możesz użyć każdej nowoczesnej, dobrze wspieranej platformy.
Czego się nauczyliśmy o skryptach service worker
Nie możesz mieć PWA bez usługodawcy. Skrypty service worker mają spore możliwości, np. zaawansowane strategie buforowania, możliwość pracy w trybie offline, synchronizację w tle itp. Chociaż mechanizmy Service Worker zwiększają złożoność procesów, odkryliśmy, że ich zalety przewyższają dodatkową złożoność.
Jeśli to możliwe, wygeneruj
Unikaj ręcznego pisania skryptu usługi. Ręczne pisanie skryptów service worker wymaga ręcznego zarządzania zasobami w pamięci podręcznej i przepisywania logiki, która jest wspólna dla większości bibliotek service worker, takich jak Workbox.
Z powodu naszego wewnętrznego pakietu technologicznego nie mogliśmy jednak użyć biblioteki do generowania i zarządzania naszym service workerem. Nasze wnioski mogą to odzwierciedlać. Więcej informacji znajdziesz w artykule Problemy związane z niewygenerowanymi skryptami service worker.
Nie wszystkie biblioteki są zgodne z usługami w tle
Niektóre biblioteki JS opierają się na założeniach, które nie działają zgodnie z oczekiwaniami, gdy są uruchamiane przez usługę workera. Na przykład: załóżmy, że interfejsy window
lub document
są dostępne, albo że używasz interfejsu API niedostępnego dla pracowników obsługi klienta (XMLHttpRequest
, pamięć lokalna itp.). Upewnij się, że wszystkie niezbędne biblioteki w aplikacji są zgodne z serwerem workera. W przypadku tej konkretnej aplikacji internetowej chcieliśmy użyć do uwierzytelniania skryptu gapi.js, ale nie było to możliwe, ponieważ nie obsługiwał on skryptów service worker. Autorzy bibliotek powinni też w miarę możliwości ograniczyć lub usunąć niepotrzebne założenia dotyczące kontekstu JavaScriptu, aby obsługiwać przypadki użycia usług workera, np. unikając interfejsów API niezgodnych z usługami workera i unikanie stanu globalnego.
Unikaj dostępu do IndexedDB podczas inicjalizacji
Nie czytaj danych IndexedDB podczas inicjowania skryptu service worker. W przeciwnym razie możesz się spotkać z niepożądaną sytuacją:
- Użytkownik ma aplikację internetową z wersją N interfejsu IndexedDB (IDB)
- Nowa aplikacja internetowa jest wysyłana z wersją IDB N+1
- Użytkownik odwiedza PWA, co powoduje pobranie nowego modułu usługi
- Nowy skrypt service worker odczytuje dane z IDB, zanim zarejestruje event handlera
install
, powodując cykl uaktualniania IDB z N na N+1. - Użytkownik ma starego klienta w wersji N, więc proces uaktualniania usługi pracującej w tle się zawiesza, ponieważ aktywne połączenia są nadal otwarte w starszej wersji bazy danych.
- Skrypt service worker zawiesza się i nigdy się nie instaluje
W naszym przypadku pamięć podręczna została unieważniona podczas instalacji skryptu service worker, więc jeśli nigdy nie został on zainstalowany, użytkownicy nie otrzymali zaktualizowanej aplikacji.
Spraw, aby była odporna na awarie
Chociaż skrypty usługi pracują w tle, można je w dowolnym momencie zakończyć, nawet w trakcie operacji wejścia/wyjścia (sieci, IDB itp.). Każdy długotrwały proces powinien być możliwy do wznowienia w dowolnym momencie.
W przypadku procesu synchronizacji, w którym duże pliki były przesyłane na serwer i zapisywane w IDB, naszym rozwiązaniem w przypadku przerw w przesyłaniu częściowych było wykorzystanie systemu wznawiania naszej wewnętrznej biblioteki przesyłania, zapisanie adresu URL do wznowienia przesyłania w IDB przed przesłaniem i użycie tego adresu URL do wznowienia przesyłania, jeśli nie zostało ukończone za pierwszym razem. Ponadto przed każdą długotrwałą operacją wejścia-wyjścia stan był zapisywany w pliku IDB, aby wskazywać, na jakim etapie procesu znajduje się każda pozycja.
Nie polegaj na stanie globalnym
Skrypty service worker występują w innym kontekście, więc może nie być dostępnych wiele symboli. Duża część naszego kodu działała zarówno w kontekście window
, jak i w kontekście skryptu service worker (np. logowanie, flagi, synchronizacja itp.). Kod musi chronić usługi, których używa, takie jak pamięć lokalna czy pliki cookie. Możesz używać globalThis
do odwoływania się do obiektu globalnego w sposób, który będzie działać we wszystkich kontekstach. Zmiennych globalnych używaj też oszczędnie, ponieważ nie ma gwarancji, kiedy skrypt zostanie zakończony i stan zostanie usunięty.
Lokalny proces programowania
Głównym elementem usług w tle jest buforowanie zasobów lokalnie. W fazie rozwoju rozwiązanie to jest jednak przeciwieństwem tego, czego oczekujesz, zwłaszcza gdy aktualizacje są przeprowadzane leniwie. Nadal musisz mieć zainstalowanego agenta serwera, aby móc debugować problemy z tym narzędziem lub korzystać z innych interfejsów API, takich jak synchronizacja w tle czy powiadomienia. W Chrome możesz to zrobić w Narzędziach deweloperskich Chrome, zaznaczając pole wyboru Omijaj sieć (panel Aplikacja > panel Pracownicy usługi) oraz zaznaczając pole wyboru Wyłącz pamięć podręczną na panelu Sieć, aby wyłączyć pamięć podręczną. Aby objąć więcej przeglądarek, zdecydowaliśmy się na inne rozwiązanie, czyli dodanie flagi do wyłączenia buforowania w naszym serwisie, która jest domyślnie włączona w wersjach dla deweloperów. Dzięki temu deweloperzy zawsze otrzymują najnowsze zmiany bez problemów z pamięcią podręczną. Ważne jest też uwzględnienie nagłówka Cache-Control: no-cache
, aby uniemożliwić przeglądarce zapisywanie komponentów w pamięci podręcznej.
Latarnia morska
Lighthouse udostępnia kilka narzędzi do debugowania przydatnych w przypadku PWA. Skanuje witrynę i generuje raporty dotyczące PWAs, wydajności, dostępności, SEO i innych sprawdzonych metod. Zalecamy uruchomienie Lighthouse w ramach ciągłej integracji, aby otrzymać powiadomienie, jeśli nie spełniasz któregoś z kryteriów dotyczących PWAs. Tak się raz zdarzyło, gdy usługa nie została zainstalowana, a nie zauważyliśmy tego przed wdrożeniem w wersji produkcyjnej. Użycie Lighthouse w naszym procesie CI zapobiegłoby temu problemowi.
Stosuj tryb ciągłego dostarczania
Ponieważ usługowe pliki workera mogą być aktualizowane automatycznie, użytkownicy nie mogą ograniczać uaktualnień. Dzięki temu znacznie zmniejsza się liczba przestarzałych klientów w środowisku naturalnym. Gdy użytkownik otwierał naszą aplikację, pracownik usługi wyświetlał starszego klienta, a w tym samym czasie pobierał nowego klienta. Po pobraniu nowego klienta użytkownik zobaczy prośbę o odświeżenie strony, aby uzyskać dostęp do nowych funkcji. Nawet jeśli użytkownik zignoruje to żądanie, to przy następnym odświeżeniu strony otrzyma nową wersję klienta. W efekcie użytkownikom trudno jest odrzucić aktualizacje w taki sam sposób jak w przypadku aplikacji na iOS lub Androida.
Udało nam się wprowadzić przełomowe zmiany w backendzie w bardzo krótkim czasie dla klientów. Zwykle dajemy użytkownikom miesiąc na aktualizację do nowszych wersji klientów, zanim wprowadzą one nieistotne zmiany. Aplikacja była wyświetlana, mimo że była nieaktualna, więc starsze klienci mogli nadal istnieć, nawet jeśli użytkownik nie otwierał aplikacji przez długi czas. W iOS usługa kończy działać po kilku tygodniach, więc w tym przypadku nie ma takiej możliwości. W przypadku Androida ten problem można rozwiązać, jeśli treści nie wyświetlają się w czasie, gdy są nieaktualne lub tracą ważność po kilku tygodniach. W praktyce nigdy nie mieliśmy problemów z nieaktywnymi klientami. To, jak rygorystyczne mają być te zasady, zależy od konkretnego przypadku użycia, ale PWA zapewniają znacznie większą elastyczność niż aplikacje na iOS lub Androida.
Pobieranie wartości plików cookie w usługach workera
Czasami dostęp do wartości plików cookie w kontekście mechanizmu Service Worker jest konieczne. W naszym przypadku musieliśmy uzyskać dostęp do wartości plików cookie, aby wygenerować token do uwierzytelniania żądań interfejsu API własnych. W usłudze workera interfejsy API synchroniczne, takie jak document.cookies
, są niedostępne. Zawsze możesz wysłać z skryptu service worker z prośbą o wartości plików cookie do aktywnych (sąsiadujących okna) klientów, chociaż może się on jednak uruchamiać w tle bez dostępnych klientów w oknie, np. podczas synchronizacji w tle. Aby to obejść, utworzyliśmy na naszym serwerze frontendu punkt końcowy, który po prostu odsyłał wartość pliku cookie z powrotem do klienta. Pracownik usługi wysłał żądanie sieci do tego punktu końcowego i odczytał odpowiedź, aby uzyskać wartości plików cookie.
Po wydaniu interfejsu Cookie Store API to obejście nie powinno być już potrzebne w przypadku przeglądarek, które go obsługują, ponieważ zapewnia on asynchroniczny dostęp do plików cookie przeglądarki i może być używany bezpośrednio przez usługę roboczą.
Pułapki związane z niegenerowanymi skryptami service worker
Sprawdzanie, czy skrypt usługi zmienia się, gdy zmienia się jakikolwiek statyczny plik z pamięci podręcznej
Typowym wzorcem PWA jest instalowanie przez usługę workera wszystkich statycznych plików aplikacji w fazie install
, co umożliwia klientom bezpośrednie korzystanie z pamięci podręcznej interfejsu Cache Storage API w przypadku wszystkich kolejnych wizyt. Usługi są instalowane tylko wtedy, gdy przeglądarka wykryje, że skrypt usługi uległ zmianie. Dlatego musieliśmy się upewnić, że sam plik skryptu usługi uległ zmianie, gdy zmienił się plik w pamięci podręcznej. Zrobiliśmy to ręcznie, umieszczając w skrypcie usługi pracownika zasobów hasz zestawu zasobów statycznych, dzięki czemu każda wersja generowała oddzielny plik JavaScript usługi pracownika. Biblioteki instancji Service Worker, takie jak Workbox, automatyzują ten proces.
Testowanie jednostkowe
Interfejsy API mechanizmu Service Worker można wykonać, dodając detektory zdarzeń do obiektu globalnego. Na przykład:
self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));
Testowanie może być uciążliwe, ponieważ musisz zasymulować zdarzenie aktywujące i obiekt zdarzenia, zaczekać na wywołanie zwrotne respondWith()
, a potem na obietnicę, zanim wreszcie przetestujesz wynik. Łatwiej jest delegować całą implementację do innego pliku, który jest łatwiejszy do przetestowania.
import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));
Ze względu na trudności związane z testowaniem jednostkowym skryptu usługi zachowaliśmy go w jak najbardziej podstawowej formie, przenosząc większość implementacji do innych modułów. Ponieważ te pliki były tylko standardowymi modułami JS, można je było łatwiej testować za pomocą standardowych bibliotek testowych.
Części 2 i 3
W częściach 2. i 3. tej serii zajmiemy się zarządzaniem multimediami i problemami związanymi z iOS. Jeśli chcesz dowiedzieć się więcej o tworzeniu aplikacji internetowych w Google, odwiedź profile naszych autorów, aby dowiedzieć się, jak się z nami skontaktować: