Podejrzewam, że sporo osób słyszało już o PWA (Progressive Web Apps), ale odświeżmy wiedzę na szybko – w największym skrócie chodzi o utworzenie aplikacji z witryny internetowej. Jeśli twoja aplikacja jest zgodna z PWA, to osoby używające tej witryny powinny być w stanie dodać skrót (ikonkę) do ekranu głównego (lub do pulpitu). Taka apka powinna również w pewnym zakresie działać bez sieci (offline), co może być osiągnięte za pomocą Service Workera. Pamiętaj, że dostosowując witrynę do zasad PWA udoskonalasz doświadczenia wszystkich osób! Nie tylko osób korzystających z urządzeń mobilnych.
W tym artykule omówię podstawy przekształcania istniejącej witryny na PWA. Z jednej strony będę mówił o podstawach, ale przy odrobinie szczęścia na podstawie poniższych informacji możesz w ciągu 1-2 dni zrobić w pełni funkcjonalne PWA. Możesz natrafić pod drodze na parę pułapek, ale spróbuję pokazać tutaj sztuczki, które pozwolą łatwiej nawigować między przeszkodami.
Ten artykuł to pierwsza część z mojej serii o PWA. Część druga dostępna po angielsku: PWA and HTTP Caching.
Zatrzymajmy się na chwilę i zastanówmy się czy to jest coś czego można i warto używać. Innymi słowy, czy jest warte twojego czasu. Moja odpowiedź brzmi: tak. Ale nie chciałbym być gołosłownym, więc podrzucę trochę odnośników, dla ciebie i być może dla twoich zwierzchniczek lub zwierzchników :-).
Google od samego początku silnie wspiera PWA. Nie jest to zbyt zaskakujące, bo to w zasadzie oni go wymyślili... z pomocą Jake'a Archibalda. Jeśli nie kojarzysz go, to dodam, że jest on osobą, która parę lat temu latała po konferencjach i pokrzykiwała na Application Cache (Application Cache to poprzednik PWA). Myślę, że w dużej mierze dzięki jego krytyce (i jego przejściu do Google) mamy teraz lepsze rozwiązanie do tworzenia aplikacji mobilnych i nie tylko.
Dodawanie skrótu do witryny zgodnej z PWA zostało zaimplementowane już w Chrome 42 a ulepszone w Chrome 57 (Paul Kinlan. „The New and Improved Add to Home screen”. Developers Google. 2017). Pierwsze wydanie Chrome for Android pojawiło się już w Android 4.0 (Mat Smith. „Google Chrome Beta arrives on Android (video)”. Engadget. 2012). Co prawda nie znalazłem dokładnej listy zgodności wersji Chrome z wersjami Android, ale udało mi się zainstalować i przetestować Chrome 64 na Android 4.1.2 (Xperia S).
Czyli Android 4.1+ powinien wystarczyć do pełnej obsługi PWA.
Microsoft wydaje się być zdeterminowany by co najmniej dorównać Google we wspieraniu PWA. Stworzyli PWA Builder, który jest narzędziem wspomagającym tworzenie PWA. Trwają również prace nad automatycznym wyszukiwaniem PWA (za pomocą botów) i tworzenie z nich aplikacji, które mają być dostępne w Windows Store. Takie aplikacje powinny się zacząć pojawiać jeszcze w kwietniu 2018 (Paul Thurrott. „Microsoft’s Bold Plan to Bring PWAs to Windows 10”. Thurrott. 2017).
A jak wygląda sprawa z niesfornym chłopcem Internetu? Tu może zaskoczę niektórych, ale Apple jednak zdecydował się zaimplementować Service Workera i powinien on być dostępny w niedawnej aktualizacji (Prathik S Shetty. „Progressive web apps (PWAs) are coming to a Safari near you”. Medium. 2018). Od iOS 11.3 zarówno Web App Manifest, jak i Service Worker, powinny być dostępne. Są pewne różnice w zachowaniu Service Worker Cache w wersji Apple (głównie kwestia tego, że cache może być wyczyszczony dla rzadziej używanych aplikacji), ale zasadniczo PWA działa (Maximiliano Firtman. „PWAs are coming to iOS 11.3: Cupertino, we have a problem”. Medium. 2018).
Nie wspominam o przeglądarce Mozilla Firefox powyżej, bo chociaż jest to znacząca przeglądarka, to Mozilla nie ma obecnie liczącego się systemu operacyjnego. Warto jednak wiedzieć, że kluczowe standardy są wspierane w Firefoksie, a nawet dodawanie skrótów do PWA powinno działać w Firefoksie na Android.
Więcej o kompatybilności przeglądarek można znaleźć m.in. na MDN: kompatybilność dla Service Worker API oraz kompatybilność dla Web App Manifest.
Podstawowe listy zgodności można znaleźć na przykład na stronach Google (co jest wymagane do zainstalowania apki) oraz Microsoftu (Minimalne wymagania dla PWA).
Nie będę tutaj wchodził w techniczne szczegóły zgodności. Nie jest to niezbędne. Wystarczające są następujące elementy:
Manifest jest prostym plikiem, ale jest tu parę kruczków. Polecam używanie Lighthouse do sprawdzania zgodności ze standardem. Raport z testów za pewne urazi twoje uczucia ;-), ale poprowadzi w dobrym kierunku. Pamiętaj, że nie musisz zdobyć 100% w każdym z testów. Przeczytaj jednak wskazówki uważnie i postaraj się je zastosować.
Jednym z kruczków jest to, że co prawda można użyć SVG jako ikonę, ale Lighthouse wskaże, że w manifeście brakuje wymaganych rozmiarów ikon.
Przykładowo poniższy fragment jest zgodny ze standardem manifestu W3C:
{ "icons": [ { "src": "/icon/favicon.ico", "sizes": "16x16 32x32" }, { "src": "/icon/appicon.svg", "sizes": "33x33" } ] }
Ale powyższe może nie działać poprawnie. W praktyce powinno się podać listę typowych wymiarów ikon, dla których użyte ma być SVG. Czyli coś w tym stylu:
{ "icons": [ { "src": "/icon/favicon.ico", "sizes": "16x16 32x32" }, { "src": "/icon/appicon.svg", "sizes": "48x48 72x72 96x96 114x114 128x128 144x144 192x192 256x256 512x512" } ] }
Moim zdaniem to jest błąd, który powinien zostać poprawiony... Ale wypisanie listy wymiarów nie zaszkodzi i nie zajmie dużo czasu.
W razie gdybyś się zagubił(-a) w tym co może być w danym polu i co ono oznacza, to polecam dokumentację Web App Manifest na witrynie MDN. Opisują tam dosyć szczegółowo poszczególne pola manifestu.
Service Worker to w zasadzie zestaw API (zobacz więcej na MDN). Na początek wystarczy wiedzieć, że większość dotyczy cachowania i przejmowania żądań do serwera. Tak, worker może przechwycić żądanie i odpowiedzieć prawie dowolną treścią. To właśnie dzięki temu twoja aplikacja może działać również bez użycia sieci.
Warto dodać w tym miejscu, że Service Worker Cache zastępuje wcześniejszy Application Cache. Znajomość Application Cache nie jest niezbędna do budowy PWA, ale jeśli poznałeś(-aś) wcześniej Application Cache Manifest, to Service Worker na pierwszy rzut oka może się wydawać zdecydowaną przesadą. Tak przynajmniej było w moim wypadku. Moje pierwsze wrażenia były takie, że chociaż Service Worker rozwiązuje niektóre problemy, to jest zbyt skomplikowany w użyciu... Okazało się, że nie miałem racji. Istnieją narzędzia i proste skrypty, które znacząco upraszczają sprawę i pozwalają na szybki start.
To może zależeć od tego z czym obecnie pracujesz. Jeśli używasz jakiegoś dużego frameworka JS (takiego jak Angular 2+), to za pewne znajdziesz narzędzie dostosowane pod dany framework lub nawet jakąś prostą opcję, która umożliwia utworzenie workera. Możesz również użyć bardziej uniwersalnego narzędzia do generowania Service Worker jakim jest sw-precache. Powinno się udać dosyć prosto użyć sw-precache, jeśli do budowania aplikacji używasz gulp lub webpack.
Chciałbym jednak wyraźnie zaznaczyć, że Service Worker nie jest zależny od konkretnego frameworka. Można go spokojnie utworzyć ręcznie i będzie działał całkiem nieźle. Jako startera można użyć bazowego przykładu Service Workera od Google. Na początek wymagane jest jedynie drobne dostosowanie skryptu.
Bazowy przykład Service Workera od Google jest w pełni sprawny skryptem, który będzie się nadawał dla prawie dowolnej aplikacji, a przynajmniej do takich, które mają połączone skrypty i style. Większość aplikacji i tak łączy swoje skrypty w jeden lub parę plików, więc w większości wypadków powinno być dobrze.
service-worker.js
.PRECACHE_URLS
wpisz listę skryptów i stylów, które są niezbędne do załadowania pierwszej strony aplikacji.if ('serviceWorker' in navigator) { navigator.serviceWorker.register('service-worker.js'); }
I już. Twoja aplikacja powinna teraz działać w wersji offline. Dobrze jest poświęcić trochę czasu na testy i dalsze dostosowanie PRECACHE_URLS
wedle potrzeb. Możesz na przykład chcieć załadować dodatkowe elementy, które nie pojawiają się od razu, ale mogą być potrzebne po zmianach użytkownika. Na przykład możesz dodać do listy CSS z alternatywnych skórek.
Ważne! Service Worker działa na wszystkie podfoldery. Jeśli kod statystyczny jest w innym folderze niż API, to prawdopodobnie wszystko będzie działać jak trzeba. Jeśli jednak aplikacja jest w głównym folderze, a API w podrzędnym, to możesz mieć problem.
Załóżmy taką sytuację (sytuacja najprostsza):
https://example.com/my-static-app/ -- tu jest cały kod statycznej aplikacji (JS, CSS, HTML) https://example.com/my-api/ -- tu jest API
Jeśli w powyższym przykładzie nie chcesz cachować żądań do API, to w tym wypadku umieścisz service-worker.js
w folderze my-static-app
. I na początek raczej nie polecałbym cachowania API.
Teraz nieco bardziej skomplikowany przypadek:
https://example.com/ -- tu jest kod statycznej aplikacji (JS, CSS, HTML) https://example.com/my-api/ -- tu jest API
W tym wypadku service-worker.js
musi być umieszczony w głównym folderze. To oznacza, że folder my-api
będzie cachowany. To z kolei oznacza, że np. pierwsze żądanie do /my-api/user/login
zostanie wysłane do serwera, ale następne zostanie już zwrócone z cache. Ale bez paniki. Dodanie wyjątku jest proste. Wystarczy wcześniej wyjść z obsługi zdarzenia fetch
. Przy okazji można dodać pomijanie cachowania żądań typu POST itp (Chrome i tak jeszcze nie umie ich cachować, a zazwyczaj i tak chcemy cachować tylko żądania GET).
self.addEventListener('fetch', event => { // skip non-GET requests if (event.request.method !== 'GET') { return; } // local request if (event.request.url.startsWith(self.location.origin + '/my-api/')) { const localUrl = event.request.url.replace(self.location.origin, ''); if (localUrl.startsWith('/my-api/')) { return; } } // ... // ... });
Teoretycznie możesz chcieć cachować niektóre żądania do API (np. jeśli z API pobierasz tłumaczenia). Lepiej jest jednak na początek cachować raczej mniej, niż więcej.
Zwróć uwagę, że w opisanym powyżej przykładzie workera znajdują się dwa odrębne rodzaje cache. Kluczem pierwszego jest precache-v1
, a drugiego runtime
. To jest w zasadzie głównie kwestia przyjętej konwencji, ale jednak oba rodzaje cache są napełniane w inny sposób w tym skrypcie.
install
. Tutaj worker może pobrać kluczowe elementy aplikacji i udostępnić je gdy urządzenie jest poza zasięgiem sieci.fetch
.Jeśli pozostawisz service worker sam sobie, to już nigdy nie zaktualizujesz aplikacji swoim użytkownikom. Wszystkie pliki, które znalazły się w cache będą z niego serwowane po wieczność. Żądania nowych wersji nie trafią nigdy do serwera. Zwracam uwagę, że zmiana precache-v1
na precache-v2
może również nie być wystarczająca. Pierwszym problemem może być to, że użytkownicy nie dostaną nowego kodu workera (to zwykle jest problem ustawień cachowania po stronie serwera). Drugim problemem jest to, że przeglądarki i tak mogą używać standardowego cache po przepuszczeniu żądania przez Service Worker.
Co zrobić, żeby unikać problemów z ładowaniem nowych elementów z serwera?
?v=1
. Czyli przy ładowaniu stylów można użyć np.: <link rel="stylesheet" href="styles.css?v=1">
. Wersją może być numer rewizji z SVN, znacznik czasowy z momentu budowania itp. Zależy to głównie od tego jak budujesz aplikację.Może się zdarzyć, że wpadniesz na jeden z problematycznych przypadków. Zanim jednak zagłębimy się w przypadki szczególne, warto sprawdzić czy podstawowa wersja aktualizacji po prostu działa. To jest procedura testowania aktualizacji, której osobiście używam:
about:debugging#workers
-> unregister (wyrejestruj).some-file.js
dodając jakiś unikatowy komunikat np. alert("Update v2")
.app.js
został zbudowany (zakładam, że łączysz skrypty w app.js
).PRECACHE
.Pamiętaj, że takie testy musisz wykonać również na serwerze produkcyjnym (docelowym). Ale jeśli już to zadziała, to wszystko powinno być OK.
Zwróć uwagę, że powyżej są przy okazji opisane kroki według których będzie działać aktualizacja. Rzecz jasna wykonanie pierwszego kroku nie powinno być konieczne dla przeciętnego użytkownika(-czki). Czyli w najgorszym razie restart przeglądarki powinien pozwolić załadować nową wersję aplikacji. Myślę, że to jest dopuszczalne ograniczenie.
Na ten moment nie udało mi się niestety sprawdzić działania w Safari, ale spróbuję zrobić to później i zaktualizować informacje. Jeśli uda ci się to sprawdzić wcześniej, to poproszę o informację.
Jeśli natomiast powyższa procedura nie działa w twoim wypadku, to zapraszam ponownie później :-). W następnym artykule chciałbym opisać nieco więcej o aktualizacjach PWA i zdecydowanie więcej o cachowaniu.