Tworzenie aplikacji PWA w Google, część 1

czego zespół Bulletin nauczył się o skryptach service worker podczas tworzenia aplikacji PWA.

Douglasa Parkera
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

To pierwszy z serii postów na blogu o wnioskach, jakie zespół Newslettera Google nauczył się podczas tworzenia aplikacji PWA dla użytkowników zewnętrznych. Omówimy w nich wybrane wyzwania, metody, jakie podjęliśmy, by je przezwyciężyć, i ogólne porady na temat unikania pułapek. W żadnym wypadku nie omawiamy tu wszystkich aplikacji PWA. Chcemy podzielić się wnioskami wyciągniętymi z doświadczenia naszego zespołu.

W pierwszym poście przedstawimy podstawowe informacje, a następnie zagłębimy się w wszystko, czego dowiedzieliśmy się o mechanizmach Service Worker.

Wprowadzenie

Newsletter był rozwijany od połowy 2017 roku do połowy 2019 roku.

Dlaczego zdecydowaliśmy się stworzyć progresywną aplikację internetową?

Zanim przejdziemy do procesu programowania, zobaczmy, dlaczego stworzenie aplikacji PWA było atrakcyjną opcją w tym projekcie:

  • Umiejętność szybkiego iteracji. Ta funkcja jest szczególnie przydatna, bo Bulletin ma być uruchamiany w ramach pilotażu na wielu rynkach.
  • Jedna baza kodu. Nasi użytkownicy byli mniej więcej po równo między Androida i iOS. Dzięki PWA mogliśmy stworzyć jedną aplikację internetową, która działa na obu platformach. Zwiększyło to tempo i wpływ pracy zespołu.
  • Szybkie aktualizowanie informacji niezależnie od zachowania użytkowników. PWA mogą aktualizować się automatycznie, co zmniejsza liczbę nieaktualnych klientów. Udało nam się wprowadzić newralgiczne zmiany backendu w przypadku klientów z bardzo krótkim czasem migracji.
  • Łatwa integracja z aplikacjami własnymi i innych firm. Taka integracja była wymagana dla aplikacji. PWA oznaczało często samo otwarcie adresu URL.
  • Wyeliminowano problemy z instalowaniem aplikacji.

Nasz system

W aplikacji Bulletin użyliśmy Polymera, ale wszystkie nowoczesne, dobrze obsługiwane platformy będą działać.

Czego dowiedzieliśmy się o mechanizmach service worker

Nie możesz mieć PWA bez skryptu service worker. Mechanizmy Service Worker dają duże możliwości, takie jak zaawansowane strategie buforowania, funkcje offline, synchronizacja w tle itp. Choć mechanizmy Service Worker bywają bardziej złożone, okazało się, że ich korzyści przeważają nad dodatkową złożonością.

Wygeneruj go, jeśli możesz

Unikaj ręcznego pisania skryptu skryptu service worker. Ręczne pisanie mechanizmów Service Worker wymaga ręcznego zarządzania zasobami z pamięci podręcznej i przepisywania logiki, która jest typowa dla większości bibliotek takich jak Workbox.

Ze względu na wewnętrzny stos technologiczny nie mogliśmy wygenerować skryptu service worker i nim zarządzać za pomocą biblioteki. Niektóre wnioski można odzwierciedlić poniżej. Więcej informacji znajdziesz w artykule Problemy z niegenerowanymi skryptami service worker.

Nie wszystkie biblioteki są zgodne z mechanizmami Service Worker

Niektóre biblioteki JS przyjmują założenia, które nie działają zgodnie z oczekiwaniami, gdy uruchamia je skrypt service worker. Jeśli na przykład są dostępne window lub document albo interfejs API jest niedostępny dla skryptów service worker (XMLHttpRequest, pamięć lokalna itp.). Dopilnuj, aby wszystkie biblioteki, których potrzebujesz do obsługi aplikacji, są zgodne z skryptami service worker. W przypadku tej konkretnej aplikacji PWA chcieliśmy użyć kodu gapi.js do uwierzytelniania, ale było to niemożliwe, ponieważ nie obsługiwał on mechanizmów Service Worker. Autorzy bibliotek powinni też ograniczać lub usuwać zbędne założenia dotyczące kontekstu JavaScriptu tam, gdzie jest to możliwe, aby obsługiwać przypadki użycia skryptu service worker – na przykład przez unikanie niezgodnych interfejsów API niezgodnych z mechanizmami Service Worker i unikanie globalnego stanu.

Unikaj dostępu do IndexedDB podczas inicjowania

Podczas inicjowania skryptu skryptu service worker nie czytaj IndexedDB, bo może to spowodować niepożądaną sytuację:

  1. Użytkownik ma aplikację internetową z IndexedDB (IDB) w wersji N
  2. Nowa aplikacja internetowa została przekazana przy użyciu IDB w wersji N+1
  3. Użytkownik odwiedza progresywną aplikację internetową, która aktywuje pobranie nowego skryptu service worker
  4. Nowy skrypt service worker odczytuje dane z IDB przed zarejestrowaniem modułu obsługi zdarzeń install, aktywując cykl uaktualniania IDB, przechodząc z N na N+1.
  5. Ponieważ użytkownik korzysta ze starego klienta w wersji N, proces uaktualniania skryptu service worker jest zawieszany, ponieważ aktywne połączenia są nadal otwarte na starą wersję bazy danych
  6. Skrypt service worker zawiesza się i nie instaluje się

W naszym przypadku pamięć podręczna została unieważniona po zainstalowaniu skryptu service worker, więc jeśli ten mechanizm nie został zainstalowany, użytkownicy nie otrzymali zaktualizowanej aplikacji.

Zadbaj o elastyczność

Chociaż skrypty skryptu service worker działają w tle, mogą zostać przerwane w dowolnym momencie, nawet w trakcie operacji wejścia-wyjścia (sieci, IDB itp.). Każdy długo trwający proces powinien w każdej chwili móc zostać wznowiony.

W przypadku synchronizacji, która polegała na przesłaniu dużych plików na serwer i zapisaniu w IDB, naszym rozwiązaniem w przypadku przerw w przesyłaniu częściowym było skorzystanie z systemu wznawiania przesyłania w naszej wewnętrznej bibliotece przesyłania. Zapisanie w IDB adresu URL umożliwiającego wznowienie przesyłania oraz użycie tego adresu URL do wznowienia przesyłania, jeśli za pierwszym razem nie zostało ukończone. Poza tym przed długotrwałą operacją wejścia-wyjścia stan był zapisywany w IDB, aby wskazać, na którym etapie procesu znajdujemy się dla każdego rekordu.

Nie zależą od stanu globalnego

Skrypty service worker istnieją w innym kontekście, dlatego brakuje wielu symboli, których można by oczekiwać. Duża część naszego kodu była uruchamiana zarówno w kontekście window, jak i w kontekście skryptu service worker (np. w przypadku logowania, flag, synchronizacji itp.). Kod musi zapewniać zabezpieczenia przed używanymi przez siebie usługami, takimi jak pamięć lokalna czy pliki cookie. Za pomocą metody globalThis możesz się odwoływać do obiektu globalnego w sposób, który będzie działać we wszystkich kontekstach. Oszczędnie korzystaj z danych przechowywanych w zmiennych globalnych, ponieważ nie ma gwarancji, kiedy skrypt zostanie zamknięty, a stan usunięty.

Programowanie lokalne

Głównym komponentem mechanizmów Service Worker jest buforowanie zasobów lokalnie. Jednak w trakcie tworzenia aplikacji jest to przeciwieństwo Twoich oczekiwań, zwłaszcza gdy aktualizacje są wykonywane leniwie. Musisz zainstalować mechanizm serwera, aby możliwe było debugowanie związanych z nim problemów lub używanie innych interfejsów API, np. synchronizacji w tle, lub powiadomień. W Chrome możesz to zrobić za pomocą Narzędzi deweloperskich w Chrome, zaznaczając pole wyboru Pomijaj dla sieci (panel Aplikacja > panel Sytuacje service worker) oraz zaznaczając pole wyboru Wyłącz pamięć podręczną w panelu Sieć, co pozwala wyłączyć pamięć podręczną. Aby uwzględnić więcej przeglądarek, zdecydowaliśmy się umieścić flagę wyłączającą buforowanie w naszym mechanizmie Service Worker, która jest domyślnie włączona w kompilacjach dla programistów. Dzięki temu deweloperzy zawsze otrzymują najnowsze zmiany bez problemów z pamięcią podręczną. Pamiętaj o dodaniu nagłówka Cache-Control: no-cache, aby zapobiec zapisywaniem zasobów w pamięci podręcznej przez przeglądarkę.

Latarnia morska

Lighthouse udostępnia wiele narzędzi do debugowania przydatnych w przypadku PWA. Skanuje witrynę i generuje raporty dotyczące PWA, wydajności, ułatwień dostępu, SEO i innych sprawdzonych metod. Zalecamy uruchamianie narzędzia Lighthouse w ramach ciągłej integracji, aby otrzymywać alerty w przypadku naruszenia jednego z kryteriów aplikacji PWA. Przydarzyło się to raz. Skrypt service worker nie był instalowany, ale nie zdawaliśmy sobie z tego sprawy przed wdrożeniem w środowisku produkcyjnym. Posiadanie Lighthouse w ramach naszej inicjatywy mogłoby temu zapobiec.

Wykorzystanie ciągłego dostarczania

Skrypty service worker mogą aktualizować się automatycznie, dlatego użytkownicy nie mają możliwości ograniczenia uaktualnień. Pozwala to znacznie ograniczyć liczbę nieaktualnych klientów, których można używać na rynku. Gdy użytkownik otworzył naszą aplikację, skrypt service worker obsługiwał starego klienta podczas leniwego pobierania nowego klienta. Po pobraniu nowego klienta użytkownik powinien odświeżyć stronę, aby uzyskać dostęp do nowych funkcji. Nawet jeśli użytkownik zignoruje tę prośbę, przy następnym odświeżeniu strony otrzyma nową wersję klienta. Dlatego trudno jest odmówić aktualizacji w taki sam sposób jak w przypadku aplikacji na iOS lub Androida.

Udało nam się wprowadzić newralgiczne zmiany backendu z bardzo krótkim czasem migracji klientów. Zwykle dajemy użytkownikom miesiąc na aktualizację do nowszej wersji klientów, zanim wprowadzisz niezbędne zmiany. Aplikacja była przestarzała, więc starsze klienty mogły istnieć bezpłatnie, gdyby użytkownicy nie otwierali ich przez dłuższy czas. W przypadku iOS mechanizmy Service Worker są usuwane po kilku tygodniach, więc w tym przypadku nie ma miejsca. W przypadku Androida ten problem można rozwiązać, nie wyświetlając treści, gdy są nieaktualne, lub ręcznie wygasają po kilku tygodniach. W praktyce nie napotkaliśmy żadnych problemów w przypadku nieaktualnych klientów. Stopień rygorystyczności poszczególnych zespołów zależy od konkretnego przypadku użycia, ale PWA zapewniają znacznie większą elastyczność niż aplikacje na iOS lub Androida.

Uzyskiwanie wartości plików cookie w skrypcie service worker

Czasami dostęp do wartości plików cookie jest potrzebny w kontekście skryptu service worker. W naszym przypadku potrzebujemy dostępu do wartości plików cookie, aby wygenerować token do uwierzytelniania własnych żądań do interfejsu API. W skrypcie service worker synchroniczne interfejsy API, takie jak document.cookies, są niedostępne. Zawsze możesz wysłać ze skryptu service worker wiadomość do aktywnych (okienowanych) klientów z prośbą o wartości plików cookie. Może on jednak działać w tle, bez dostępnych klientów w oknach, na przykład podczas synchronizacji w tle. Aby obejść ten problem, utworzyliśmy na serwerze frontendu punkt końcowy, który po prostu przesyła klientowi wartość pliku cookie. Skrypt service worker wysłał do tego punktu końcowego żądanie sieciowe i odczytał odpowiedź, aby uzyskać wartości plików cookie.

Od momentu udostępnienia Cookie Store API to obejście nie powinno już być konieczne w przypadku przeglądarek, które je obsługują, ponieważ zapewnia ono asynchroniczny dostęp do plików cookie przeglądarki i można z niego korzystać bezpośrednio przez skrypt service worker.

Problemy związane z niewygenerowanymi skryptami service worker

Sprawdź, czy skrypt skryptu service worker zmieni się w przypadku zmiany statycznego pliku w pamięci podręcznej

Typowym wzorcem PWA jest instalowanie przez mechanizm Service Worker wszystkich statycznych plików aplikacji na etapie install, co umożliwia klientom korzystanie z pamięci podręcznej interfejsu Cache Storage API podczas wszystkich kolejnych wizyt . Mechanizmy Service Worker są instalowane tylko wtedy, gdy przeglądarka wykryje, że skrypt service worker zmienił się w jakiś sposób. Musieliśmy się więc upewnić, że sam plik skryptu zmienił się w jakiś sposób wraz ze zmianą pliku w pamięci podręcznej. Zrobiliśmy to ręcznie, osadzając hasz statycznego zbioru plików zasobów w skrypcie skryptu service worker, dzięki czemu każda wersja generuje osobny plik JavaScript skryptu service worker. Biblioteki mechanizmów Service Worker, takie jak Workbox, automatyzują ten proces.

Testowanie jednostkowe

Interfejsy API mechanizmów Service Worker działają, dodając detektory zdarzeń do obiektu globalnego. Na przykład:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

Testy mogą być trudne, ponieważ trzeba imitować aktywator zdarzenia, obiekt zdarzenia, czekać na wywołanie zwrotne respondWith(), a następnie czekać na obietnicę, zanim w końcu skorzystasz z wyniku. Łatwiejszym sposobem na uporządkowanie wdrożenia jest delegowanie całej implementacji do innego pliku, który można łatwiej sprawdzić.

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 skryptu service worker zachowaliśmy jak najściślejsze jej omówienie, dzieląc większość implementacji na inne moduły. Ponieważ były to tylko standardowe moduły JS, można je łatwiej przetestować za pomocą standardowych bibliotek testowych.

Wkrótce udostępnimy część 2 i 3

W części 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 PWA w Google, odwiedź nasze profile autora i dowiedz się, jak się z nami skontaktować: