Błyskawiczne wczytywanie aplikacji internetowych za pomocą architektury powłoki aplikacji

Powłoka aplikacji to minimalna ilość kodu HTML, CSS i JavaScript, która umożliwia działanie interfejsu. Powłoka aplikacji powinna:

  • szybko się wczytuje.
  • być przechowywane w pamięci podręcznej.
  • wyświetlać treści dynamicznie.

Powłoka aplikacji to klucz do niezawodnego działania. Pomyśl o obudowie aplikacji jako pakiecie kodu, który zostałby opublikowany w sklepie z aplikacjami, gdybyś tworzyła/tworzył natywną aplikację. Jest to ładunek potrzebny do uruchomienia aplikacji, ale nie musi być to jeszcze wszystko. Zapisuje interfejs użytkownika lokalnie i dynamicznie pobiera treści za pomocą interfejsu API.

Odseparowanie warstwy aplikacji od kodu HTML, JS i CSS oraz treści HTML

Tło

W artykule Progresywne aplikacje internetowe Alex Russell opisuje, jak aplikacja internetowa może stopniowo zmieniać się wraz z korzystaniem z niej i zgodą użytkownika, aby zapewnić wygodę na poziomie aplikacji natywnych, w tym obsługę trybu offline, powiadomienia push i możliwość dodania aplikacji do ekranu głównego. To zależy w dużej mierze od funkcjonalności i wydajności elementu service worker oraz jego możliwości buforowania. Dzięki temu możesz skupić się na szybkości, zapewniając swoim aplikacjom internetowym takie same błyskawiczne wczytywanie i regularne aktualizacje, do jakich przywykłeś/przywykłaś w przypadku aplikacji natywnych.

Aby w pełni korzystać z tych możliwości, musimy zmienić sposób myślenia o witrynach: architektura powłoki aplikacji.

Przyjrzyjmy się, jak uporządkować aplikację za pomocą architektury powłoki aplikacji wzbogaconej o skrypt service worker. Omówimy renderowanie po stronie klienta i po stronie serwera oraz udostępnimy kompleksowy przykład, który możesz wypróbować już dziś.

Aby to zilustrować, w przykładzie poniżej pokazano pierwsze wczytanie aplikacji korzystającej z tej architektury. U dołu ekranu pojawi się komunikat „Aplikacja jest gotowa do użycia w trybie offline”. Jeśli później pojawi się aktualizacja powłoki, możemy poinformować użytkownika o nowościach.

Obraz usługi roboczej działającej w Narzędziach deweloperskich w przypadku powłoki aplikacji

Czym są service workery?

Skrypt service worker to skrypt działający w tle, oddzielnie od strony internetowej. Reaguje na zdarzenia, w tym żądania sieci wysyłane ze stron, które obsługuje, oraz powiadomienia push z serwera. Żywotność instancji roboczej jest celowo krótka. Uruchamia się, gdy otrzyma zdarzenie, i działa tylko tak długo, jak długo jest potrzebny do jego przetworzenia.

W porównaniu z JavaScriptem w normalnym kontekście przeglądania, interfejsy API usług mają też ograniczony zestaw interfejsów. Jest to standardowa praktyka w przypadku pracowników w internecie. Usługa w tle nie ma dostępu do DOM, ale może korzystać z interfejsu Cache API i wysyłać żądania sieciowe za pomocą interfejsu Fetch API. Do trwałego przechowywania danych i wymiany komunikatów między usługą wtyczki i stronami, którymi zarządza, można też używać interfejsu IndexedDB API i metody postMessage(). Zdarzenia push wysyłane z serwera mogą wywoływać interfejs Notification API w celu zwiększenia zaangażowania użytkowników.

Skrypt service worker może przechwytywać żądania sieciowe wysyłane przez stronę (co powoduje wywołanie zdarzenia pobierania w skrypcie service worker) i zwracać odpowiedź pobraną z sieci lub z lokalnej pamięci podręcznej, a nawet zbudowaną programowo. Jest to w zasadzie programowalny serwer proxy w przeglądarce. Najciekawsze jest to, że niezależnie od tego, skąd pochodzi odpowiedź, dla strony internetowej wygląda to tak, jakby nie było zaangażowanego skryptu service worker.

Aby dowiedzieć się więcej o skryptach service worker, przeczytaj Wprowadzenie do skryptów service worker.

Korzyści dotyczące wydajności

Usługa torby narzędzi to potężne narzędzie do buforowania offline, ale oferuje też znaczne korzyści w zakresie wydajności w postaci natychmiastowego wczytywania przy powtarzanych wizytach w witrynie lub aplikacji internetowej. Możesz przechowywać w pamięci podręcznej powłokę aplikacji, aby działała offline, i wypełniać jej zawartość za pomocą JavaScriptu.

Podczas kolejnych wizyt pozwala to wyświetlać na ekranie istotne piksele bez sieci, nawet jeśli Twoje treści pochodzą z tej sieci. Wyobraź sobie, że paski narzędzi i karty są wyświetlane natychmiast, a potem reszta treści wczytuje się stopniowo.

Aby przetestować tę architekturę na rzeczywistych urządzeniach, uruchomiliśmy przykładową powłokę aplikacji na stronie WebPageTest.org. Poniżej przedstawiamy uzyskane wyniki.

Test 1. Testowanie przez kabel za pomocą Nexusa 5 i Chrome Dev

Pierwszy widok aplikacji musi pobrać wszystkie zasoby z sieci i nie osiąga znaczącego poziomu renderowania przed upływem 1,2 sekundy. Dzięki buforowaniu w ramach usługi workera powtórna wizyta osiąga znaczące wyświetlenie i pełne załadowanie w 0, 5 sekundy.

Schemat testu strony internetowej w przypadku połączenia kablowego

Test 2: testowanie w sieci 3G w telefonie Nexus 5 z użyciem przeglądarki Chrome Dev

Możemy też przetestować nasz plik za pomocą nieco wolniejszego połączenia 3G. Tym razem pierwsze wyrenderowanie elementu znaczącego zajmuje 2,5 sekund podczas pierwszej wizyty. Pełne załadowanie strony zajmuje 7,1 sekundy. Dzięki buforowaniu w usługach workerów podczas powtórnej wizyty znacząca część treści jest wyświetlana i załadowanie kończy się w 0, 8 sekundach.

Schemat testu strony internetowej w przypadku połączenia 3G

Inne widoki przedstawiają podobną sytuację. Porównaj 3 sekundy, jakie są potrzebne na pierwsze wyrenderowanie elementu znaczącego w powłoce aplikacji:

Czas wyrenderowania w pierwszym widoku z testu strony internetowej

do 0,9 sekundy, gdy ta sama strona jest wczytywana z pamięci podręcznej naszego serwisu. Dzięki temu użytkownicy oszczędzają ponad 2 sekundy.

Odtwarzanie osi czasu w przypadku powtórnego wyświetlenia strony z testu strony internetowej

Podobne i stabilne wyniki wydajności są możliwe w przypadku własnych aplikacji korzystających z architektury powłoki aplikacji.

Czy worker usługi wymaga od nas zmiany sposobu tworzenia struktury aplikacji?

Serwisy w tle wymagają wprowadzenia pewnych subtelnych zmian w architekturze aplikacji. Zamiast upychać całą aplikację w ciągu HTML, możesz skorzystać z techniki AJAX. Tutaj masz powłokę (która jest zawsze przechowywana w pamięci podręcznej i która może się uruchamiać bez sieci) oraz treści, które są regularnie odświeżane i zarządzane osobno.

Konsekwencje tego podziału są poważne. Podczas pierwszej wizyty możesz renderować treści na serwerze i instalować skrypt service worker na kliencie. Podczas kolejnych wizyt wystarczy tylko poprosić o dane.

A co z progresywnym ulepszaniem?

Chociaż usługa w tle nie jest obecnie obsługiwana przez wszystkie przeglądarki, architektura powłoki treści aplikacji korzysta z ulepszeń progresywnych, aby zapewnić wszystkim użytkownikom dostęp do treści. Weźmy na przykład nasz przykładowy projekt.

Poniżej możesz zobaczyć pełną wersję renderowaną w Chrome, Firefox Nightly i Safari. Po lewej stronie widać wersję Safari, w której treści są renderowane na serwerze bez workera usługi. Po prawej stronie widać wersje Chrome i Firefox Nightly korzystające z usług.

Grafika przedstawiająca powłokę aplikacji wczytaną w Safari, Chrome i Firefox

Kiedy warto stosować tę architekturę?

Architektura powłoki aplikacji najlepiej sprawdza się w przypadku aplikacji i witryn, które są dynamiczne. Jeśli Twoja witryna jest mała i statyczna, prawdopodobnie nie potrzebujesz powłoki aplikacji i możesz po prostu zapisać całą witrynę w pamięci podręcznej w kroku oninstall skryptu service worker. Użyj podejścia, które najlepiej pasuje do Twojego projektu. Wiele frameworków JavaScript już zachęca do oddzielania logiki aplikacji od treści, co ułatwia stosowanie tego wzorca.

Czy istnieją już aplikacje produkcyjne korzystające z tego wzorca?

Architektura powłoki aplikacji jest możliwa dzięki wprowadzeniu zaledwie kilku zmian w ogólnym interfejsie aplikacji. Sprawdza się ona w przypadku dużych witryn, takich jak aplikacja internetowa Google I/O 2015 i poczta Inbox od Google.

Obraz: wczytywanie Poczty Google Ilustracja przedstawiająca skrzynkę odbiorczą korzystającą z usług interfejsu użytkownika.

Powłoki aplikacji offline to świetne rozwiązanie pod względem wydajności. Można je dobrze zademonstrować na przykładzie aplikacji offline Wikipedia Jake’a Archibalda i aplikacji internetowej Flipkart Lite.

Zrzuty ekranu pokazujące demo Jake'a Archibalda na Wikipedii

Wyjaśnienie architektury

Podczas pierwszego wczytywania strony Twoim celem jest jak najszybsze wyświetlenie na ekranie użytkownika treści znaczących.

Pierwsze wczytanie i wczytywanie innych stron

Diagram przedstawiający pierwszy wczyt z aplikacją powłokową

Ogólnie architektura powłoki aplikacji:

  • Nadaj priorytet początkowemu wczytywaniu, ale pozwól serwisowi workera na umieszczenie w pamięci podręcznej powłoki aplikacji, aby podczas kolejnych wizyt nie trzeba było ponownie pobierać powłoki z sieci.

  • Pozostałe elementy wczytywane są z opóźnieniem lub w tle. Dobrym rozwiązaniem jest użycie przechodzącego buforowania w przypadku treści dynamicznych.

  • Aby niezawodnie przechowywać w pamięci podręcznej i aktualizować usługę workera, która zarządza treściami statycznymi, użyj narzędzi dla workerów usług, takich jak sw-precache. (więcej informacji o sw-precache znajdziesz poniżej).

Aby to zrobić:

  • Serwer będzie wysyłać zawartość HTML, którą klient może renderować, i używać nagłówków wygaśnięcia pamięci podręcznej HTTP w dalekiej przyszłości, aby uwzględnić przeglądarki bez obsługi interfejsu Service Worker. Będzie ona obsługiwać nazwy plików za pomocą haszy, aby umożliwić „wersjonowanie” i łatwe aktualizowanie w późniejszym etapie cyklu życia aplikacji.

  • Strona(y) będzie zawierać wbudowane style CSS w tagu <style> w dokumencie <head>, aby zapewnić szybkie pierwsze wyrenderowanie powłoki aplikacji. Każda strona będzie asynchronicznie wczytywać kod JavaScript potrzebny do bieżącego widoku. Ponieważ kodu CSS nie można wczytywać asynchronicznie, możemy żądać stylów za pomocą JavaScriptu, ponieważ jest on asynchroniczny, a nie synchroniczny i sterowany przez parsowanie. Możemy też korzystać z funkcji requestAnimationFrame(), aby uniknąć sytuacji, w której dochodzi do szybkiego trafienia do pamięci podręcznej i użycia stylu przez przypadek w krytycznej ścieżce renderowania. requestAnimationFrame() wymusza wyświetlenie pierwszej klatki przed załadowaniem stylów. Inną opcją jest użycie projektów takich jak loadCSS firmy Filament Group, aby żądać kodu CSS asynchronicznie za pomocą JavaScriptu.

  • Usługa w tle będzie przechowywać w pamięci podręcznej wpis z obudową aplikacji, aby podczas kolejnych wizyt można było ją w całości wczytać z pamięci podręcznej usługi w tle, chyba że w sieci jest dostępna aktualizacja.

Powłoka aplikacji na potrzeby treści

Praktyczne wdrożenie

Stworzyliśmy działający przykład, korzystając z architektury powłoki aplikacji, standardowego kodu JavaScript ES2015 dla klienta i Express.js dla serwera. Oczywiście nic nie stoi na przeszkodzie, abyś używał własnego pakietu dla klienta lub serwera (np.PHP, Ruby, Python).

Cykl życia skryptu service worker

W przypadku projektu powłoki aplikacji używamy sw-precache, który oferuje następujący cykl życia usługi:

Zdarzenie Działanie
Zainstaluj Zapisuje w pamięci podręcznej powłokę aplikacji i inne zasoby aplikacji jednostronicowej.
Aktywuj Wyczyść stare pamięci podręczne.
Pobierz Używaj jednej strony aplikacji internetowej na adres URL i używaj pamięci podręcznej do zasobów i wstępnie zdefiniowanych części. Użyj sieci do innych żądań.

Bity serwera

W tej architekturze komponent po stronie serwera (w naszym przypadku napisany w Express) powinien być w stanie traktować zawartość i prezentację osobno. Treści można dodawać do układu HTML, co powoduje statyczne renderowanie strony, lub można je wyświetlać oddzielnie i ładować dynamicznie.

Twoja konfiguracja po stronie serwera może się znacznie różnić od tej, której używamy w przypadku aplikacji demonstracyjnej. Ten wzór aplikacji internetowych można zastosować w przypadku większości konfiguracji serwera, ale wymaga pewnych zmian w architekturze. Okazało się, że ten model działa całkiem nieźle:

Schemat architektury powłoki aplikacji
  • Punkty końcowe są zdefiniowane dla 3 części aplikacji: adresów URL widocznych dla użytkownika (indeks/symbol wieloznaczny), powłoki aplikacji (workera usługi) i częściowych plików HTML.

  • Każdy punkt końcowy ma kontroler, który pobiera układ handlebars, który z kolei może pobierać części i widoki w ramach handlebars. Mówiąc najprościej, częściowe widoki to fragmenty kodu HTML, które są kopiowane na stronę końcową. Uwaga: frameworki JavaScripta, które obsługują bardziej zaawansowaną synchronizację danych, są często znacznie łatwiejsze do przeniesienia do architektury Application Shell. Zamiast częściowych komponentów używają zwykle powiązań danych i synchronizacji.

  • Użytkownikowi najpierw wyświetla się strona statyczna z treścią. Ta strona rejestruje skrypt service worker (jeśli jest obsługiwany), który przechowuje w pamięci podręcznej powłokę aplikacji i wszystko, od czego ona zależy (CSS, JS itp.).

  • Muszla aplikacji będzie wtedy działać jako jednostronicowa aplikacja internetowa, która używa JavaScript do XHR w treściach konkretnego adresu URL. Wywołania XHR są kierowane do punktu końcowego /partials*, który zwraca mały fragment kodu HTML, CSS i JS potrzebny do wyświetlenia tych treści. Uwaga: jest wiele sposobów na realizację tego zadania, a XHR to tylko jeden z nich. Niektóre aplikacje będą wstawiać dane w tekście (np. za pomocą JSON) na potrzeby początkowego renderowania, więc nie będą „statyczne” w ujęciu spłaszczonego kodu HTML.

  • W przypadku przeglądarek bez obsługi skryptu service worker zawsze należy wyświetlić wersję zapasową. W naszym pokazie korzystamy z podstawowego stałego renderowania po stronie serwera, ale jest to tylko jedna z wielu opcji. Usługa workera daje nowe możliwości zwiększenia wydajności aplikacji jednostronicowej za pomocą oprogramowania pośredniczącego aplikacji przechowywanego w pamięci podręcznej.

Wersje plików

Pojawia się pytanie, jak zarządzać wersjonowaniem i aktualizowaniem plików. To zależy od aplikacji. Do wyboru masz:

  • najpierw z sieci, a w przeciwnym razie z wersji z bufora;

  • Tylko sieć i nie działa w trybie offline.

  • Zapisz starszą wersję w pamięci podręcznej i zaktualizuj ją później.

W przypadku samej powłoki aplikacji należy zastosować podejście oparte na pamięci podręcznej w przypadku konfiguracji usługi. Jeśli nie używasz pamięci podręcznej w przypadku powłoki aplikacji, oznacza to, że nie zastosowałeś prawidłowo architektury.

Narzędzia

Utrzymujemy kilka różnych bibliotek pomocniczych dla serwisów workerów, które ułatwiają konfigurowanie wstępnego buforowania powłoki aplikacji lub obsługi typowych wzorów buforowania.

Zrzut ekranu strony biblioteki usług pomocniczych w Centrum Podstawów Sieci

Używanie sw-precache w powłoce aplikacji

Użycie sw-precache do przechowywania w pamięci podręcznej powłoki aplikacji powinno rozwiązać problemy związane z wersjami plików, pytaniami dotyczącymi instalacji i aktywacji oraz scenariuszem pobierania powłoki aplikacji. Wprowadź sw-precache do procesu kompilacji aplikacji i użyj konfigurowalnych symboli wieloznacznych, aby pobrać zasoby statyczne. Zamiast ręcznie tworzyć skrypt service workera, pozwól narzędziu sw-precache wygenerować skrypt, który będzie zarządzać pamięcią podręczną w sposób bezpieczny i wydajny, korzystając z obsługi pobierania z pamięci podręcznej.

Pierwsze wizyty w aplikacji powodują wstępne buforowanie całego zestawu potrzebnych zasobów. Jest to podobne do instalowania natywnej aplikacji ze sklepu z aplikacjami. Gdy użytkownicy wrócą do aplikacji, pobrane zostaną tylko zaktualizowane zasoby. W naszym pokazie informujemy użytkowników o dostępności nowej powłoki, wyświetlając komunikat „Aktualizacje aplikacji. Odśwież stronę, aby wyświetlić nową wersję”. Ten wzór to prosty sposób na poinformowanie użytkowników o dostępności najnowszej wersji.

Używanie narzędzia sw-toolbox do buforowania w czasie działania

Do buforowania w czasie wykonywania za pomocą różnych strategii w zależności od zasobu użyj sw-toolbox:

  • cacheFirst dla obrazów oraz specjalna nazwana pamięć podręczna z niestandardową zasadą wygasania N maxEntries.

  • networkFirst lub najszybszy dla żądań interfejsu API, w zależności od pożądanej aktualności treści. Najszybszy może być odpowiedni, ale jeśli jest określony plik danych API, który jest często aktualizowany, użyj opcji networkFirst.

Podsumowanie

Architektury powłok aplikacji niosą ze sobą wiele korzyści, ale mają sens tylko w przypadku niektórych klas aplikacji. Model jest jeszcze młody, dlatego warto przeanalizować wysiłek i ogólne korzyści z tej architektury.

W ramach eksperymentów korzystaliśmy z udostępniania szablonów między klientem a serwerem, aby zminimalizować wysiłek związany z tworzeniem 2 warstw aplikacji. Dzięki temu stopniowe ulepszanie nadal będzie podstawową funkcją.

Jeśli rozważasz już użycie skryptów service worker w swojej aplikacji, przyjrzyj się architekturze i zdecyduj, czy ma ona sens w przypadku Twoich projektów.

Dziękujemy naszym recenzentom: Jeffowi Posnickowi, Paulowi Lewisowi, Alexowi Russellowi, Sethowi Thompsonowi, Robowi Dodsonowi, Taylor Savage i Joe Medley.