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

Powłoka systemowa aplikacji to krótki kod HTML, CSS i JavaScript, który tworzy interfejs użytkownika. Powłoka aplikacji powinna:

  • szybkie ładowanie
  • być w pamięci podręcznej
  • dynamiczne wyświetlanie treści

Powłoka aplikacji to klucz do zapewnienia niezawodności wysokiej wydajności. Pomyśl o powłoce aplikacji jak o pakiecie kodu, który należałoby opublikować w sklepie z aplikacjami, gdy tworzysz aplikację natywną. Jest to obciążenie potrzebne do uruchomienia aplikacji, ale niekoniecznie do końca. Pozwala zachować lokalny interfejs użytkownika i dynamicznie pobierać treści przez interfejs API.

Oddzielenie powłoki HTML, JS i CSS od treści HTML

Wprowadzenie

Artykuł Alexa Russella na temat progresywnych aplikacji internetowych opisuje, jak aplikacja internetowa może stopniowo zmieniać się w wyniku użytkowania i wyrażania zgody użytkownika, dzięki czemu będzie działać jak aplikacja natywna, z obsługą offline, powiadomieniami push i możliwością dodania jej do ekranu głównego. W dużym stopniu zależy to od funkcjonalności i wydajności skryptów service worker oraz ich możliwości buforowania. Pozwala to skupić się na szybkości, zapewniając aplikacjom internetowym takim samo szybkie wczytywanie i regularne aktualizacje jak w aplikacjach natywnych.

Aby w pełni wykorzystać te możliwości, musimy znaleźć nowy sposób myślenia o witrynach: architekturę powłoki aplikacji.

Zobaczmy, jak utworzyć strukturę aplikacji za pomocą architektury powłoki powłoki aplikacji rozszerzonej interfejsu Service Worker. Przyjrzymy się renderowaniu po stronie klienta i serwera. Omówimy pełny przykład, który możesz wypróbować już dziś.

Poniższy przykład pokazuje pierwsze wczytanie aplikacji korzystającej z tej architektury. Zwróć uwagę na komunikat „Aplikacja jest gotowa do użytku w trybie offline” u dołu ekranu. Jeśli aktualizacja powłoki stanie się dostępna później, możemy poinformować użytkownika, że musi odświeżyć stronę, aby pobrać nową wersję.

Obraz skryptu service worker uruchomionego w Narzędziach deweloperskich dla powłoki aplikacji

Czym są mechanizmy Service Worker?

Skrypt service worker działa w tle, niezależny od strony internetowej. Reaguje na zdarzenia, w tym na żądania sieciowe wysyłane ze stron, które obsługuje, oraz na powiadomienia push z serwera. Skrypt service worker celowo ma krótki czas działania. Wybudza się w chwili, gdy wystąpi zdarzenie, i działa tylko tak długo, jak jest potrzebne do przetworzenia go.

Skrypty service worker mają też ograniczony zestaw interfejsów API w porównaniu z JavaScriptem w normalnym kontekście przeglądania. W przypadku pracowników w internecie jest to standard. Skrypt service worker nie ma dostępu do DOM, ale ma dostęp do interfejsu Cache API i może wysyłać żądania sieciowe za pomocą interfejsu Download API. Interfejsów IndexedDB API i postMessage() można też używać do zachowania trwałości danych i przesyłania komunikatów między skryptem service worker a stronami, którymi steruje. Zdarzenia push wysyłane z Twojego 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 ze strony (aktywujące zdarzenie pobierania w skrypcie service worker) i zwracać odpowiedź pobraną z sieci lub z lokalnej pamięci podręcznej, a nawet skonstruowaną automatycznie. W rzeczywistości jest to programowalny serwer proxy w przeglądarce. Najistotniejsze jest to, że niezależnie od tego, skąd pochodzi odpowiedź, wygląda ona tak, jakby nie było udziału skryptu service worker.

Aby dowiedzieć się więcej o skryptach service worker, przeczytaj wprowadzenie do mechanizmów Service Worker.

Korzyści z wydajności

Skrypty service worker pozwalają korzystać z pamięci podręcznej w trybie offline, ale zapewniają też istotne korzyści w zakresie wydajności w postaci natychmiastowego wczytywania w przypadku kolejnych wizyt w witrynie lub aplikacji internetowej. Powłokę aplikacji można buforować, aby działała offline i wypełniała zawartość za pomocą JavaScriptu.

Pozwala to wyświetlać na ekranie znaczące piksele bez połączenia z siecią, nawet jeśli Twoje treści w końcu pochodzą z tej sieci. To coś jak o natychmiastowym wyświetlaniu pasków narzędzi i kart, a potem stopniowe ładowanie reszty treści.

Aby przetestować tę architekturę na prawdziwych urządzeniach, uruchomiliśmy przykładową powłokę aplikacji na stronie WebPageTest.org. Wyniki znajdziesz poniżej.

Test 1: Testowanie z użyciem kabla na Nexusie 5 przy użyciu Chrome Dev

Pierwszy widok aplikacji musi pobrać wszystkie zasoby z sieci i nie uzyskuje znaczącego wyrenderowania przez 1,2 sekundy. Dzięki buforowaniu skryptu service worker podczas ponownej wizyty można wyrenderować konkretny obraz i całkowicie zakończyć wczytywanie w ciągu 0, 5 sekundy.

Schemat renderowania strony internetowej dla połączenia kablowego

Test 2: Testowanie 3G na Nexusie 5 przy użyciu Chrome Dev

Możemy również przetestować naszą próbkę, korzystając z nieco wolniejszego połączenia 3G. W tym przypadku pierwsze wyrenderowanie elementu zajmuje 2,5 sekundy przy pierwszej wizycie. Pełne wczytanie strony zajmuje 7,1 sekundy. Dzięki buforowaniu skryptu service worker powtórna wizyta jest namalowana i całkowicie wczytuje się w ciągu 0, 8 sekundy.

Schemat renderowania strony internetowej dla połączenia 3G

Inne poglądy mówią podobnie. Porównaj 3 sekundy potrzebne do osiągnięcia pierwszego znaczącego wyrenderowania w powłoce aplikacji:

Renderowanie osi czasu dla pierwszego widoku z testu strony internetowej

do 0,9 sekundy, jakie zajmuje wczytanie tej samej strony z naszej pamięci podręcznej skryptu service worker. Naszym użytkownikom zaoszczędziliśmy ponad 2 sekundy.

Kopiuj oś czasu dla powtarzanego widoku z testu strony internetowej

Dzięki architekturze powłoki aplikacji Twoje aplikacje mogą osiągnąć podobną i stabilną wydajność.

Czy mechanizm Service Worker wymaga od nas zmiany struktury aplikacji?

Skrypty service worker sugerują drobne zmiany w architekturze aplikacji. Zamiast zwierać całą aplikację w ciągu HTML, warto wprowadzić rozwiązania w stylu AJAX. W tym miejscu jest powłoka (zawsze przechowywana w pamięci podręcznej i dostępna bez połączenia z siecią) oraz treść, która jest regularnie odświeżana i zarządzana oddzielnie.

Konsekwencje tego podziału są znaczące. Przy pierwszej wizycie możesz wyrenderować treść na serwerze i zainstalować skrypt service worker w kliencie. Kolejne wizyty wymagają już tylko żądania danych.

A co z progresywnym ulepszeniem?

Chociaż skrypt service worker nie jest obecnie obsługiwany przez wszystkie przeglądarki, architektura powłoki treści aplikacji korzysta z progresywnego ulepszania, aby zapewnić wszystkim dostęp do treści. Weźmy na przykład nasz przykładowy projekt.

Poniżej znajdziesz pełną wersję wyrenderowaną w Chrome, Firefoksie Nightly i Safari. Po lewej stronie widać wersję Safari, w której treść jest renderowana na serwerze bez skryptu service worker. Po prawej stronie znajdują się wersje Chrome i Firefox Nightly oparte na mechanizmie Service Worker.

Obraz powłoki aplikacji załadowanej w Safari, Chrome i Firefoksie

Kiedy warto stosować taką architekturę?

Architektura powłoki systemowej aplikacji najlepiej sprawdza się w przypadku dynamicznych aplikacji i witryn. Jeśli Twoja witryna jest mała i statyczna, prawdopodobnie nie potrzebujesz powłoki aplikacji i możesz po prostu buforować całą witrynę w kroku oninstall skryptu service worker. Użyj metody, która będzie najlepsza dla Twojego projektu. Wiele struktur JavaScript już teraz zachęca do oddzielania logiki aplikacji od treści, dzięki czemu łatwiej jest zastosować ten wzorzec.

Czy są już jakieś aplikacje produkcyjne korzystające z tego wzoru?

Architektura powłoki systemowej aplikacji jest możliwa dzięki wprowadzeniu kilku zmian w ogólnym interfejsie aplikacji. Sprawdza się ona również w przypadku dużych witryn, takich jak progresywna aplikacja internetowa Google I/O 2015 czy skrzynka odbiorcza Google.

Obraz pokazujący wczytywanie skrzynki odbiorczej Google. Ilustruje skrzynkę odbiorczą za pomocą skryptu service worker.

Powłoki aplikacji offline zapewniają dużą wydajność. Można je również znaleźć w aplikacji Wikipedia offline opracowanej przez Jake'a Archibalda oraz progresywnej aplikacji internetowej Flipkart Lite.

Zrzuty ekranu przedstawiające prezentację Jake'a Archibalda w Wikipedii.

Omówienie architektury

Podczas pierwszego wczytywania – Twoim celem jest jak najszybsze udostępnienie przydatnych treści na ekranie użytkownika.

Pierwsze wczytywanie i wczytywanie innych stron

Schemat pierwszego wczytywania za pomocą App Shell

Ogólnie architektura powłoki aplikacji:

  • Nadaj priorytet wczytywania początkowego, ale pozwól skryptowi service worker zapamiętywać powłokę aplikacji w pamięci podręcznej, aby powtórne wizyty nie wymagały ponownego pobierania powłoki z sieci.

  • Leniwe ładowanie lub wczytywanie w tle. Jedną z dobrych opcji jest korzystanie z pamięci podręcznej odczytu w przypadku treści dynamicznych.

  • Używaj narzędzi Service Worker, takich jak sw-precache, aby w niezawodny sposób buforować dane i zaktualizować skrypt service worker, który zarządza treścią statyczną. Więcej informacji o sw-precache znajdziesz w dalszej części tego artykułu.

Aby to zrobić:

  • Serwer będzie wysyłać treści HTML, które klient może wyrenderować, i użyje w przyszłości nagłówków HTTP do wygaśnięcia pamięci podręcznej, aby uwzględnić przeglądarki, które nie obsługują mechanizmów Service Worker. Będzie udostępniać nazwy plików za pomocą haszy, aby umożliwić zarówno obsługę wersji, jak i łatwe aktualizacje do późniejszego wykorzystania w cyklu życia aplikacji.

  • Strony będą zawierać wbudowane style CSS w tagu <style> w obrębie dokumentu <head>, aby umożliwić szybkie pierwsze wyrenderowanie powłoki aplikacji. Każda strona asynchronicznie wczyta JavaScript niezbędny do wyświetlenia bieżącego widoku. Ponieważ CSS nie może być ładowany asynchronicznie, możemy zażądać stylów za pomocą JavaScriptu, ponieważ jest on asynchroniczny, a nie synchroniczny i oparty na parserze. requestAnimationFrame() pozwala nam też uniknąć sytuacji, w których możemy szybko uzyskać trafienie w pamięci podręcznej i przez to sprawić, że style przypadkowo staną się częścią krytycznej ścieżki renderowania. Funkcja requestAnimationFrame() wymusza wyrenderowanie pierwszej klatki przed wczytaniem stylów. Innym sposobem jest użycie projektów takich jak loadCSS firmy Filament Group do asynchronicznego żądania CSS za pomocą JavaScriptu.

  • Skrypt Service Worker przechowuje w pamięci podręcznej wpis powłoki aplikacji, dzięki czemu przy kolejnych wizytach powłoka może być ładowana w całości z pamięci podręcznej mechanizmu Service Worker, chyba że w sieci jest dostępna aktualizacja.

App Shell dla treści

Praktyczne wdrożenie

Przygotowaliśmy w pełni działający przykład wykorzystujący architekturę powłoki aplikacji, kod JavaScript w standardzie vanilla ES2015 dla klienta i Express.js dla serwera. Nic nie powstrzymuje Cię oczywiście przed używaniem własnego stosu zarówno do obsługi klienta, jak i serwera (np.PHP, Ruby lub Python).

Cykl życia instancji roboczych usługi

W projekcie powłoki aplikacji używamy obiektu sw-precache, który oferuje taki cykl życia skryptu service worker:

Zdarzenie Działanie
Zainstaluj Zapisuj w pamięci podręcznej powłokę aplikacji i inne zasoby aplikacji jednostronicowej.
Aktywuj Wyczyść stare pamięci podręczne.
Pobierz Wyświetlaj aplikację internetową na jednej stronie dla adresów URL i korzystaj z pamięci podręcznej w przypadku zasobów i wstępnie zdefiniowanych części. Używaj sieci do innych żądań.

Bity serwera

W tej architekturze komponent po stronie serwera (w tym przypadku napisany w formacie Express) powinien być w stanie oddzielnie traktować treść i prezentację. Treść można dodać do układu HTML, co spowoduje jej statyczne renderowanie, lub wyświetlać osobno i dynamicznie ładować.

Rozumiem, że konfiguracja po stronie serwera może znacznie się różnić od konfiguracji używanej w naszej aplikacji demonstracyjnej. Taki schemat aplikacji internetowych można osiągnąć w większości konfiguracji serwerów, ale wymaga zmiany architektury. Zauważyliśmy, że ten model sprawdza się dość dobrze:

Schemat architektury App Shell
  • Punkty końcowe są zdefiniowane dla 3 części aplikacji: adresu URL widocznego dla użytkownika (indeks/symbol wieloznaczny), powłoki aplikacji (skryptu service worker) i części kodu HTML.

  • Każdy punkt końcowy ma kontroler, który pobiera układ poręczy, który z kolei może pobierać części i widoki części kierownicy. Mówiąc najprościej, częściowe widoki to fragmenty kodu HTML skopiowane na ostateczną stronę. Uwaga: platformy JavaScript, które przeprowadzają bardziej zaawansowaną synchronizację danych, są często o wiele łatwiejsze do przeniesienia na architekturę Application Shell. Zwykle korzystają z wiązania danych i synchronizacji, a nie częściowych.

  • Początkowo wyświetlana jest statyczna strona z zawartością. Ta strona rejestruje skrypt service worker (o ile jest obsługiwany), który zapisuje w pamięci podręcznej powłokę aplikacji i wszystko, od której zależy (CSS, JS itp.).

  • Powłoka aplikacji będzie wtedy działać jak aplikacja internetowa na pojedynczej stronie, wykorzystując JavaScript do XHR w treści konkretnego adresu URL. Wywołania XHR są wysyłane do punktu końcowego /partials*, który zwraca mały fragment kodu HTML, CSS i JS niezbędny do wyświetlenia tej treści. Uwaga: istnieje wiele sposobów, aby podejść do tego problemu, a XHR to tylko jeden z nich. Niektóre aplikacje wstawiają dane w tekście (być może korzystają z kodu JSON) podczas początkowego renderowania, dlatego nie są „statyczne” w stosunku do spłaszczonego kodu HTML.

  • Przeglądarki nieobjęte skryptami service worker zawsze powinny mieć środowisko zastępcze. W naszej wersji demonstracyjnej używamy podstawowego statycznego renderowania po stronie serwera, ale jest to tylko jedna z wielu opcji. Aspekt skryptu service worker oferuje nowe możliwości zwiększania wydajności aplikacji w stylu aplikacji jednostronicowej za pomocą powłoki aplikacji zapisanej w pamięci podręcznej.

Obsługa wersji plików

Pojawia się pytanie, jak obsługiwać wersje plików i je aktualizować. To dotyczy konkretnej aplikacji i dostępne są następujące opcje:

  • Najpierw sieć, a w przeciwnym razie – wersja z pamięci podręcznej.

  • Tylko sieć, a awaria w trybie offline.

  • Starą wersję należy umieścić w pamięci podręcznej i zaktualizować później.

W przypadku samej powłoki aplikacji w konfiguracji mechanizmu Service Worker należy zastosować podejście oparte na pamięci podręcznej. Jeśli powłoka aplikacji nie jest buforowana, architektura nie została prawidłowo wdrożona.

Narzędzia

Przechowujemy wiele różnych bibliotek pomocniczych skryptu service worker, które ułatwiają proces wstępnego buforowania powłoki aplikacji lub obsługi typowych wzorców buforowania.

Zrzut ekranu przedstawiający witrynę biblioteki Service Worker w witrynie Web Fundamentals

Użyj sw-precache dla powłoki aplikacji

Użycie parametru sw-precache do zapisywania powłoki aplikacji w pamięci podręcznej powinno rozwiązać problemy dotyczące wersji plików, pytań dotyczących instalacji i aktywacji oraz scenariusza pobierania powłoki. Do pobierania zasobów statycznych dodaj komponent sw-precache w procesie kompilacji aplikacji i użyj konfigurowalnych symboli wieloznacznych. Zamiast ręcznie tworzyć skrypt skryptu service worker, zezwól sw-precache na wygenerowanie takiego skryptu, który w bezpieczny i wydajny sposób zarządza pamięcią podręczną przy użyciu modułu obsługi pobierania w pierwszej kolejności.

Początkowe wizyty w aplikacji wywołują wstępne buforowanie pełnego zbioru wymaganych zasobów. Przypomina to instalowanie aplikacji natywnej ze sklepu z aplikacjami. Gdy użytkownicy wracają do aplikacji, pobierane są tylko zaktualizowane zasoby. W naszej wersji demonstracyjnej informujemy użytkowników o udostępnieniu nowej powłoki z komunikatem „Aktualizacje aplikacji. Odśwież, aby uzyskać nową wersję”. Ułatwia on informowanie użytkowników, że mogą odświeżyć stronę do najnowszej wersji.

Używanie narzędzia SW-toolbox do buforowania środowiska wykonawczego

Używaj narzędzia sw-toolbox do buforowania środowiska wykonawczego. Strategie różnią się w zależności od zasobu:

  • cacheFirst w przypadku obrazów wraz z dedykowaną nazwaną pamięcią podręczną z niestandardową zasadą wygaśnięcia N maxEntries.

  • networkFirst lub najszybszy w przypadku żądań do interfejsu API, w zależności od pożądanej częstotliwości aktualizacji treści. Najszybsze rozwiązanie może być dobre, ale jeśli istnieje konkretny plik danych interfejsu API, który jest często aktualizowany, wybierz opcję networkFirst.

Podsumowanie

Architektura powłoki aplikacji ma kilka zalet, ale ma sens tylko w przypadku niektórych klas aplikacji. Model jest wciąż młody i warto przeanalizować wysiłek oraz ogólne korzyści w zakresie wydajności tej architektury.

Wykorzystaliśmy udostępnianie szablonów między klientem a serwerem, aby zminimalizować nakład pracy związanej z tworzeniem dwóch warstw aplikacji. Dzięki temu stopniowe ulepszanie jest główną funkcją.

Jeśli już zastanawiasz się nad wykorzystaniem mechanizmów Service Worker w swojej aplikacji, przyjrzyj się architekturze i oceń, czy sprawdzi się ona w Twoich własnych projektach.

Dzięki opiniom naszych weryfikatorów: Jeff Posnick, Paul Lewis, Alex Russell, Seth Thompson, Rob Dodson, Taylor Savage i Joe Medley.