Co zespół 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 Androida i iOS byli podzieleni mniej więcej po równo. 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. PWA mogą się automatycznie aktualizować, co zmniejsza liczbę przestarzałych klientów w środowisku. 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 Takie integracje były wymagane w przypadku aplikacji. W przypadku aplikacji PWA często oznaczało to po prostu otwarcie adresu URL.
- Uproszczenie procesu instalowania aplikacji.
Nasza platforma
W przypadku Bulletin użyliśmy Polymer, ale sprawdzi się też dowolna nowoczesna, dobrze obsługiwana platforma.
Czego się nauczyliśmy o skryptach service worker
Nie możesz mieć PWA bez usługodawcy. Skrypty service worker dają wiele możliwości, takich jak zaawansowane strategie buforowania, funkcje offline czy synchronizacja w tle. Chociaż skrypty service worker zwiększają złożoność, stwierdziliśmy, że korzyści z ich stosowania przeważają nad dodatkowymi trudnościami.
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 zestawu narzędzi nie mogliśmy jednak użyć biblioteki do generowania i zarządzania naszym service workerem. Poniżej znajdziesz nasze wnioski, które to potwierdzają. Aby dowiedzieć się więcej, przeczytaj artykuł Pułapki w przypadku skryptów service worker niegenerowanych przez Google.
Nie wszystkie biblioteki są zgodne z usługami w tle
Niektóre biblioteki JS zawierają założenia, które nie działają zgodnie z oczekiwaniami, gdy są uruchamiane przez usługę w tle. Na przykład: załóżmy, że interfejsy window
lub document
są dostępne, albo że używasz interfejsu API niedostępnego dla robotów usługowych (XMLHttpRequest
, pamięć lokalna itp.). Upewnij się, że wszystkie niezbędne biblioteki w aplikacji są zgodne z serwerem roboczym. 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 IndexedDB podczas inicjowania skryptu usługi, ponieważ może to spowodować 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 serwisu workera
- 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 instalowania usługi w tle, więc jeśli usługa w tle nigdy nie została zainstalowana, użytkownicy nigdy 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, który przesyła duże pliki na serwer i zapisuje je w IDB, nasze rozwiązanie dotyczące przerwanych częściowych przesyłań polegało na wykorzystaniu systemu umożliwiającego wznowienie przesyłania w naszej wewnętrznej bibliotece przesyłania. Przed przesyłaniem zapisujemy adres URL umożliwiający wznowienie przesyłania w IDB, a jeśli przesyłanie nie zostało ukończone za pierwszym razem, używamy tego adresu URL, aby wznowić przesyłanie. 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 działają w innym kontekście, więc wiele symboli, których można się spodziewać, jest nieobecnych. Wiele naszego kodu działało zarówno w kontekście window
, jak i w kontekście usługi workera (np. rejestrowanie, 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. Jednak podczas tworzenia aplikacji jest to właśnie odwrotne od tego, czego oczekujesz, zwłaszcza gdy aktualizacje są wykonywane leniwie. Nadal musisz mieć zainstalowanego agenta serwera, aby móc debugować związane z nim problemy 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 zapobiec przechowywaniu przez przeglądarkę 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 zostanie poproszony 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 przejście na nowsze wersje klienta przed wprowadzeniem przełomowych zmian. 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 można ograniczyć ten problem, nie wyświetlając treści, które są stale dostępne, lub ręcznie ustawiając datę wygaśnięcia treści 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 w kontekście usługi roboczej konieczny jest dostęp do wartości plików cookie. 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ługach workera interfejsy API synchroniczne, takie jak document.cookies
, są niedostępne. Z serwisowego workera zawsze możesz wysłać wiadomość do aktywnych (oknowanych) klientów, aby poprosić o wartości plików cookie. Jednak możliwe jest, że serwisowy worker będzie działać w tle bez dostępu do klientów oknowanych, np. podczas synchronizacji w tle. Aby to obejść, utworzyliśmy punkt końcowy na serwerze front-end, 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 przeczytał 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 usług roboczych, takie jak Workbox, automatyzują ten proces.
Testowanie jednostkowe
Interfejsy API usług workera działają poprzez dodawanie odbiorników 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 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 omówimy zarządzanie multimediami oraz problemy związane 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ć: