Łatwe zwiększanie wydajności stron internetowych – edycja Google I/O 2018

Na konferencji Google I/O 2018 przedstawiliśmy zestawienie narzędzi, bibliotek i technik optymalizacji, które ułatwiają poprawę wydajności stron internetowych. Wyjaśniamy je na przykładzie aplikacji Oodles Theater. Omawiamy też nasze eksperymenty z ładowaniem predykcyjnym i nową inicjatywę Guess.js.

Ewa Gasperowicz

W ciągu ostatniego roku intensywnie pracowaliśmy nad tym, aby internet był szybszy i bardziej wydajny. Dzięki temu powstały nowe narzędzia, podejścia i biblioteki, które chcemy przedstawić w tym artykule. W pierwszej części pokażemy kilka technik optymalizacji, które zastosowaliśmy w praktyce podczas tworzenia aplikacji Oodles Theater. W drugiej części omówimy nasze eksperymenty z ładowaniem predykcyjnym i nową inicjatywą Guess.js.

Potrzeba wydajności

Z roku na rok internet staje się coraz bardziej obciążony. Jeśli sprawdzimy stan sieci, zobaczymy, że mediana rozmiaru strony na urządzeniach mobilnych wynosi około 1,5 MB, z czego większość to JavaScript i obrazy.

Rosnący rozmiar witryn wraz z innymi czynnikami, takimi jak opóźnienie sieci, ograniczenia procesora, wzorce blokujące renderowanie czy zbędny kod zewnętrzny, przyczynia się do skomplikowania kwestii wydajności.

Większość użytkowników uważa szybkość za najważniejszy element wygody korzystania z witryny. Nie jest to zaskakujące, ponieważ nie można zbyt wiele zrobić, dopóki strona nie zostanie wczytana. Nie możesz czerpać korzyści ze strony ani podziwiać jej estetyki.

Piramida hierarchii UX
Rys. 1. Jak ważna jest szybkość dla użytkowników? (Speed Matters, Vol. 3)

Wiemy, że wydajność jest ważna dla użytkowników, ale znalezienie miejsca, od którego należy zacząć optymalizację, może być trudne. Na szczęście istnieją narzędzia, które mogą Ci w tym pomóc.

Lighthouse – podstawa przepływu pracy związanego z wydajnością

Lighthouse to część Narzędzi deweloperskich w Chrome, która umożliwia przeprowadzenie audytu witryny i zawiera wskazówki, jak ją ulepszyć.

Niedawno wprowadziliśmy szereg nowych kontroli wydajności, które są bardzo przydatne w codziennej pracy programistycznej.

Nowe audyty Lighthouse
Rys. 2. Nowe audyty Lighthouse

Przyjrzyjmy się, jak można je wykorzystać na praktycznym przykładzie: aplikacji Oodles Theater. Jest to mała demonstracyjna aplikacja internetowa, w której można wypróbować niektóre z naszych ulubionych interaktywnych Google Doodle, a nawet zagrać w grę lub dwie.

Podczas tworzenia aplikacji chcieliśmy mieć pewność, że będzie ona jak najbardziej wydajna. Punktem wyjścia do optymalizacji był raport Lighthouse.

Raport Lighthouse dotyczący aplikacji Oodles
Rys. 3. Raport Lighthouse dotyczący aplikacji Oodles

Początkowa wydajność naszej aplikacji, jak wynika z raportu Lighthouse, była dość słaba. W sieci 3G użytkownik musiał czekać 15 sekund na pierwsze wyrenderowanie elementu znaczącego lub na to, aż aplikacja stanie się interaktywna. Lighthouse wykrył mnóstwo problemów z naszą witryną, a ogólny wynik wydajności na poziomie 23 dokładnie to odzwierciedlał.

Strona ważyła około 3,4 MB – musieliśmy ją odchudzić.

To było nasze pierwsze wyzwanie związane z wydajnością: znaleźć elementy, które można łatwo usunąć bez wpływu na ogólne wrażenia.

Możliwości optymalizacji skuteczności

Usuwanie niepotrzebnych zasobów

Istnieją pewne oczywiste elementy, które można bezpiecznie usunąć: białe znaki i komentarze.

Korzyści z minifikacji
Rys. 4. Minifikuj i kompresuj kod JavaScript oraz CSS.

Lighthouse wyróżnia tę możliwość w kontroli nieskompresowanego kodu CSS i JavaScriptu. W procesie kompilacji używaliśmy webpacka, więc do minifikacji użyliśmy po prostu wtyczki Uglify JS.

Minifikacja jest powszechnym zadaniem, więc bez problemu znajdziesz gotowe rozwiązanie dla dowolnego procesu kompilacji, którego używasz.

Inną przydatną kontrolą w tym obszarze jest Włącz kompresję tekstu. Nie ma powodu, aby wysyłać nieskompresowane pliki, a większość sieci CDN obsługuje obecnie tę funkcję od razu po zainstalowaniu.

Do hostowania kodu używaliśmy Hostingu Firebase, który domyślnie włącza gzipping, więc dzięki hostowaniu kodu w rozsądnej sieci CDN otrzymaliśmy tę funkcję bezpłatnie.

Chociaż gzip jest bardzo popularną metodą kompresji, inne mechanizmy, takie jak Zopfli i Brotli, również zyskują na popularności. Brotli jest obsługiwany przez większość przeglądarek. Możesz użyć pliku binarnego, aby wstępnie skompresować zasoby przed wysłaniem ich na serwer.

Używaj zasad dotyczących efektywnej pamięci podręcznej

Kolejnym krokiem było upewnienie się, że nie wysyłamy zasobów dwukrotnie, jeśli nie jest to konieczne.

Dzięki kontroli Nieefektywne zasady pamięci podręcznej w Lighthouse zauważyliśmy, że możemy zoptymalizować nasze strategie buforowania, aby osiągnąć dokładnie ten cel. Ustawiając na serwerze nagłówek wygaśnięcia max-age, upewniliśmy się, że podczas kolejnej wizyty użytkownik może ponownie wykorzystać pobrane wcześniej zasoby.

Najlepiej dążyć do buforowania jak największej liczby zasobów w bezpieczny sposób przez jak najdłuższy czas i udostępniać tokeny weryfikacyjne, aby skutecznie ponownie weryfikować zaktualizowane zasoby.

Usuń nieużywany kod.

Do tej pory usunęliśmy oczywiste części niepotrzebnego pobierania, ale co z mniej oczywistymi? Może to być na przykład nieużywany kod.

Pokrycie kodu w Narzędziach deweloperskich
Rys. 5. Sprawdź pokrycie kodu

Czasami w naszych aplikacjach umieszczamy kod, który nie jest tak naprawdę potrzebny. Dzieje się tak zwłaszcza wtedy, gdy pracujesz nad aplikacją przez dłuższy czas, zmienia się Twój zespół lub zależności, a czasami pozostaje osierocona biblioteka. Dokładnie to nam się przydarzyło.

Na początku używaliśmy biblioteki Material Components, aby szybko stworzyć prototyp aplikacji. Z czasem przeszliśmy na bardziej niestandardowy wygląd i całkowicie zapomnieliśmy o tej bibliotece. Na szczęście dzięki sprawdzeniu pokrycia kodu udało nam się go odnaleźć w pakiecie.

Statystyki pokrycia kodu możesz sprawdzić w Narzędziach deweloperskich zarówno pod kątem czasu działania, jak i czasu wczytywania aplikacji. Na dolnym zrzucie ekranu widać 2 duże czerwone paski – ponad 95% kodu CSS i duża część kodu JavaScript były nieużywane.

Lighthouse wykrył ten problem również podczas audytu nieużywanych reguł CSS. Wykazało to potencjalne oszczędności na poziomie ponad 400 kB. Wróciliśmy więc do kodu i usunęliśmy z niego zarówno JavaScript, jak i CSS z tej biblioteki.

Jeśli usuniemy adapter MVC, rozmiar stylów spadnie do 10 KB.
Rys. 6. Jeśli usuniemy adapter MVC, rozmiar stylów spadnie do 10 KB.

Dzięki temu nasz pakiet CSS zmniejszył się 20-krotnie, co jest całkiem niezłym wynikiem jak na niewielkie, dwuwierszowe zatwierdzenie.

Oczywiście poprawiło to nasz wynik wydajności, a także znacznie skróciło czas do pełnej interaktywności.

Jednak w przypadku takich zmian nie wystarczy sprawdzać samych danych i wyników. Usuwanie rzeczywistego kodu zawsze wiąże się z ryzykiem, dlatego należy zwracać uwagę na potencjalne regresje.

Nasz kod był nieużywany w 95% – gdzieś nadal jest te 5%. Okazało się, że jeden z naszych komponentów nadal używał stylów z tej biblioteki – były to małe strzałki w suwaku doodle. Ponieważ jednak jest on tak mały, możemy po prostu ręcznie przywrócić te style w przyciskach.

Przyciski zostały uszkodzone z powodu braku biblioteki
Rys. 7. Jeden komponent nadal używał usuniętej biblioteki.

Jeśli więc usuniesz kod, upewnij się, że masz odpowiedni proces testowania, który pomoże Ci uchronić się przed potencjalnymi regresjami wizualnymi.

Unikaj ogromnych ładunków sieciowych

Wiemy, że duże zasoby mogą spowalniać wczytywanie stron internetowych. Mogą one kosztować użytkowników pieniądze i mieć duży wpływ na ich pakiety danych, dlatego warto o tym pamiętać.

Lighthouse wykrył problem z niektórymi danymi przesyłanymi przez sieć, korzystając z kontroli Ogromne dane przesyłane przez sieć.

Wykrywanie ogromnych ładunków sieciowych
Rys. 8. Wykrywanie ogromnych ładunków sieciowych

W tym przypadku okazało się, że przesyłamy ponad 3 MB kodu, co jest dość dużą ilością, zwłaszcza na urządzeniach mobilnych.

Na samej górze tej listy Lighthouse wskazał, że mamy pakiet dostawcy JavaScriptu, który zawierał 2 MB nieskompresowanego kodu. Ten problem jest też wyróżniony przez webpack.

Jak mówi powiedzenie: najszybsze żądanie to takie, które nie zostało wysłane.

Najlepiej byłoby mierzyć wartość każdego zasobu, który udostępniasz użytkownikom, mierzyć wydajność tych zasobów i decydować, czy warto je dostarczać w ramach początkowego doświadczenia. Czasami te zasoby można odroczyć, załadować leniwie lub przetworzyć w czasie bezczynności.

W naszym przypadku, ponieważ mamy do czynienia z dużą liczbą pakietów JavaScriptu, mieliśmy szczęście, bo społeczność JavaScriptu dysponuje bogatym zestawem narzędzi do kontroli pakietów JavaScriptu.

Audyt pakietów JavaScriptu
Rys. 9. Kontrola pakietów JavaScriptu

Zaczęliśmy od narzędzia webpack bundle analyzer, które poinformowało nas, że uwzględniamy zależność o nazwie unicode, która zawierała 1,6 MB przeanalizowanego kodu JavaScript, czyli całkiem sporo.

Następnie przeszliśmy do edytora i za pomocą wtyczki Import Cost do Visual Studio Code mogliśmy zwizualizować koszt każdego importowanego modułu. Dzięki temu dowiedzieliśmy się, który komponent zawierał kod odwołujący się do tego modułu.

Następnie przełączyliśmy się na inne narzędzie, BundlePhobia. To narzędzie, które pozwala wpisać nazwę dowolnego pakietu NPM i sprawdzić, jaki jest jego szacowany rozmiar po minifikacji i skompresowaniu za pomocą Gzip. Znaleźliśmy dobrą alternatywę dla używanego przez nas modułu slug, która ważyła tylko 2,2 kB, więc ją wybraliśmy.

Miało to duży wpływ na nasze wyniki. Dzięki tej zmianie i innym możliwościom zmniejszenia rozmiaru pakietu JavaScript udało nam się zaoszczędzić 2, 1 MB kodu.

Po uwzględnieniu rozmiaru tych pakietów po skompresowaniu i zminimalizowaniu zauważyliśmy ogólną poprawę o 65%. Okazało się, że ten proces był naprawdę wart zachodu.

Ogólnie rzecz biorąc, staraj się eliminować niepotrzebne pobieranie w witrynach i aplikacjach. Sporządzenie spisu komponentów i pomiar ich wpływu na skuteczność może przynieść znaczące korzyści, dlatego warto regularnie przeprowadzać audyt komponentów.

Skracanie czasu uruchamiania JavaScriptu dzięki podziałowi kodu

Chociaż duże ładunki sieciowe mogą mieć duży wpływ na naszą aplikację, jest jeszcze jedna rzecz, która może mieć naprawdę duży wpływ, a jest nią JavaScript.

JavaScript to najbardziej kosztowny zasób. Jeśli na urządzeniach mobilnych wysyłasz duże pakiety JavaScriptu, może to opóźnić moment, w którym użytkownicy będą mogli wejść w interakcję z komponentami interfejsu. Oznacza to, że mogą klikać elementy interfejsu, nie wywołując żadnych istotnych działań. Dlatego ważne jest, abyśmy zrozumieli, dlaczego JavaScript jest tak kosztowny.

W ten sposób przeglądarka przetwarza JavaScript.

Przetwarzanie JavaScriptu
Rys. 10. przetwarzanie JavaScriptu.

Najpierw musimy pobrać ten skrypt. Mamy silnik JavaScriptu, który musi przeanalizować ten kod, skompilować go i wykonać.

Te fazy nie zajmują dużo czasu na urządzeniach wysokiej klasy, takich jak komputery stacjonarne czy laptopy, a nawet telefony z wyższej półki. Jednak na przeciętnym telefonie komórkowym ten proces może potrwać od 5 do 10 razy dłużej. To właśnie opóźnia interaktywność, dlatego ważne jest, aby spróbować to ograniczyć.

Aby ułatwić Ci wykrywanie tych problemów w aplikacji, wprowadziliśmy w Lighthouse nową kontrolę czasu uruchamiania JavaScriptu.

Czas uruchamiania JavaScriptu
Rys. 11. Kontrola czasu uruchamiania JavaScriptu

W przypadku aplikacji Oodle dowiedzieliśmy się, że uruchamianie JavaScriptu zajmuje 1,8 sekundy. Okazało się, że importowaliśmy statycznie wszystkie nasze trasy i komponenty do jednego monolitycznego pakietu JavaScriptu.

Jedną z technik pozwalających obejść ten problem jest dzielenie kodu.

Dzielenie kodu jest jak pizza

Podział kodu to koncepcja, zgodnie z którą zamiast dawać użytkownikom całą pizzę kodu JavaScript, dajemy im po jednym kawałku, gdy tego potrzebują.

Podział kodu można zastosować na poziomie trasy lub komponentu. Świetnie współpracuje z React i React Loadable, Vue.js, Angular, Polymer, Preact i wieloma innymi bibliotekami.

Wprowadziliśmy w aplikacji podział kodu, przeszliśmy z importu statycznego na dynamiczny, co pozwoliło nam na asynchroniczne leniwe ładowanie kodu w razie potrzeby.

Podział kodu z importem dynamicznym
Rys. 13. Podział kodu z importem dynamicznym

Dzięki temu udało się nie tylko zmniejszyć rozmiar pakietów, ale też skrócić czas uruchamiania kodu JavaScript. Udało się go skrócić do 0,78 sekundy, dzięki czemu aplikacja działa o 56% szybciej.

Ogólnie rzecz biorąc, jeśli tworzysz witrynę, która w dużym stopniu korzysta z JavaScriptu, upewnij się, że wysyłasz użytkownikowi tylko ten kod, którego potrzebuje.

Wykorzystaj takie koncepcje jak dzielenie kodu, poznaj takie pomysły jak eliminacja martwego kodu i zajrzyj do repozytorium webpack-libs-optimizations, aby dowiedzieć się, jak zmniejszyć rozmiar biblioteki, jeśli używasz webpacka.

Zoptymalizuj obrazy

Żart o wydajności wczytywania obrazów

W aplikacji Oodle używamy wielu obrazów. Niestety Lighthouse był znacznie mniej entuzjastyczny niż my. W rzeczywistości nie udało nam się przejść wszystkich 3 kontroli związanych z obrazami.

Zapomnieliśmy o optymalizacji obrazów, nie dostosowaliśmy ich rozmiaru, a poza tym moglibyśmy uzyskać pewne korzyści, używając innych formatów plików graficznych.

Audyty obrazów
Rys. 14. Audyty obrazów w Lighthouse

Zaczęliśmy od optymalizacji obrazów.

Do jednorazowej optymalizacji możesz użyć narzędzi wizualnych, takich jak ImageOptim lub XNConvert.

Bardziej zautomatyzowanym podejściem jest dodanie do procesu kompilacji kroku optymalizacji obrazów za pomocą bibliotek takich jak imagemin.

Dzięki temu obrazy dodane w przyszłości będą optymalizowane automatycznie. Niektóre sieci CDN, np. Akamai, lub rozwiązania innych firm, takie jak Cloudinary, Fastly czy Uploadcare, oferują kompleksowe rozwiązania do optymalizacji obrazów, dzięki czemu możesz po prostu hostować obrazy w tych usługach.

Jeśli nie chcesz tego robić ze względu na koszty lub problemy z opóźnieniami, projekty takie jak Thumbor lub Imageflow oferują alternatywne rozwiązania hostowane samodzielnie.

Przed optymalizacją i po niej
Rys. 15. Przed optymalizacją i po niej

Nasz plik PNG tła został oznaczony w webpacku jako duży i słusznie. Po odpowiednim dopasowaniu go do widocznego obszaru i przepuszczeniu przez ImageOptim zmniejszyliśmy go do 100 KB, co jest akceptowalne.

Powtórzenie tego procesu w przypadku wielu obrazów w naszej witrynie pozwoliło nam znacznie zmniejszyć ogólną wagę strony.

Używanie odpowiedniego formatu treści animowanych

GIF-y mogą być bardzo drogie. Co ciekawe, format GIF nigdy nie był pomyślany jako platforma animacji. Dlatego przejście na bardziej odpowiedni format wideo pozwala znacznie zmniejszyć rozmiar pliku.

W aplikacji Oodle używaliśmy GIF-a jako sekwencji wprowadzającej na stronie głównej. Według Lighthouse możemy zaoszczędzić ponad 7 MB, przechodząc na bardziej wydajny format wideo. Nasz klip ważył około 7,3 MB, czyli zdecydowanie za dużo jak na jakąkolwiek rozsądną witrynę, więc zamiast tego przekształciliśmy go w element wideo z 2 plikami źródłowymi – MP4 i WebM, aby zapewnić szerszą obsługę przeglądarek.

Zastąp animowane GIF-y filmami
Rys. 16. Zastąp animowane GIF-y filmami

Do przekonwertowania animowanego GIF-a na plik MP4 użyliśmy narzędzia FFmpeg. Format WebM zapewnia jeszcze większe oszczędności – interfejs ImageOptim API może wykonać taką konwersję za Ciebie.

ffmpeg -i animation.gif -b:v 0 -crf 40 -vf scale=600:-1 video.mp4

Dzięki tej konwersji udało nam się zmniejszyć ogólną wagę o ponad 80%. Dzięki temu udało nam się zmniejszyć rozmiar do około 1 MB.

Jednak 1 MB to duży zasób do przesłania, zwłaszcza w przypadku użytkownika z ograniczoną przepustowością. Na szczęście możemy użyć interfejsu Effective Type API, aby wykryć, że użytkownik ma wolne połączenie, i wyświetlić mu znacznie mniejszy plik JPEG.

Ten interfejs używa efektywnego czasu błądzenia i wartości pobierania do oszacowania typu sieci, z której korzysta użytkownik. Zwraca on po prostu ciąg tekstowy: slow 2G, 2G, 3G lub 4G. W zależności od tej wartości, jeśli użytkownik ma połączenie wolniejsze niż 4G, możemy zastąpić element wideo obrazem.

if (navigator.connection.effectiveType) { ... }

Może to nieco pogorszyć wrażenia użytkownika, ale przynajmniej witryna będzie działać na wolnym połączeniu.

Leniwe ładowanie obrazów poza ekranem

Karuzela, suwaki lub bardzo długie strony często wczytują obrazy, mimo że użytkownik nie widzi ich od razu na stronie.

Lighthouse oznaczy to zachowanie w audycie obrazów poza ekranem. Możesz je też zobaczyć w panelu Sieć w Narzędziach deweloperskich. Jeśli widzisz, że wczytuje się wiele obrazów, ale tylko kilka jest widocznych na stronie, rozważ zastosowanie leniwego ładowania.

Leniwe ładowanie nie jest jeszcze obsługiwane natywnie w przeglądarce, więc musimy użyć JavaScriptu, aby dodać tę funkcję. Użyliśmy biblioteki Lazysizes, aby dodać leniwe ładowanie do okładek Oodle.

<!-- Import library -->
import lazysizes from 'lazysizes'  <!-- or -->
<script src="lazysizes.min.js"></script>

<!-- Use it -->

<img data-src="image.jpg" class="lazyload"/>
<img class="lazyload"
    data-sizes="auto"
    data-src="image2.jpg"
    data-srcset="image1.jpg 300w,
    image2.jpg 600w,
    image3.jpg 900w"/>

Lazysizes to inteligentne rozwiązanie, ponieważ nie tylko śledzi zmiany widoczności elementu, ale też aktywnie pobiera elementy, które znajdują się blisko widoku, aby zapewnić optymalne wrażenia użytkownika. Umożliwia też opcjonalną integrację z IntersectionObserver, co zapewnia bardzo wydajne wyszukiwanie widoczności.

Po tej zmianie nasze obrazy są pobierane na żądanie. Jeśli chcesz dowiedzieć się więcej na ten temat, zajrzyj na stronę images.guide, która jest bardzo przydatnym i kompleksowym źródłem informacji.

Pomóż przeglądarce we wcześniejszym dostarczaniu zasobów o znaczeniu krytycznym

Nie każdy bajt wysyłany do przeglądarki ma takie samo znaczenie i przeglądarka o tym wie. Wiele przeglądarek korzysta z heurystyki, aby określić, co należy pobrać najpierw. Dlatego czasami pobierają CSS przed obrazami lub skryptami.

Przydatne może być poinformowanie przeglądarki przez nas, jako autorów strony, co jest dla nas naprawdę ważne. Na szczęście w ciągu ostatnich kilku lat dostawcy przeglądarek dodali wiele funkcji, które nam w tym pomagają, np. wskazówki dotyczące zasobów, takie jak link rel=preconnect, preload lub prefetch.

Te funkcje, które zostały wprowadzone na platformie internetowej, pomagają przeglądarce pobierać odpowiednie elementy we właściwym czasie i mogą być nieco bardziej wydajne niż niektóre niestandardowe podejścia oparte na logice, które są realizowane za pomocą skryptu.

Zobaczmy, jak Lighthouse pomaga nam skutecznie korzystać z niektórych z tych funkcji.

Pierwszą rzeczą, którą Lighthouse zaleca, jest unikanie wielokrotnych kosztownych operacji w obie strony do dowolnego źródła.

Unikaj wielokrotnych, kosztownych operacji w obie strony do dowolnego źródła.
Rys. 17. Unikaj wielokrotnych, kosztownych podróży w obie strony do dowolnego punktu początkowego.

W przypadku aplikacji Oodle intensywnie korzystamy z Google Fonts. Za każdym razem, gdy umieszczasz arkusz stylów Google Fonts na swojej stronie, łączy się on z maksymalnie 2 subdomenami. Lighthouse informuje nas, że gdybyśmy mogli rozgrzać to połączenie, moglibyśmy zaoszczędzić nawet 300 milisekund w początkowym czasie połączenia.

Wykorzystując link rel preconnect, możemy skutecznie zamaskować opóźnienie połączenia.

Może to mieć duże znaczenie zwłaszcza w przypadku Google Fonts, gdzie arkusz CSS kroju czcionki jest hostowany na googleapis.com, a zasoby czcionek są hostowane na Gstatic. Zastosowaliśmy tę optymalizację i udało nam się skrócić czas o kilkaset milisekund.

Kolejną rzeczą, którą sugeruje Lighthouse, jest wstępne wczytywanie żądań kluczy.

Wstępne wczytywanie żądań kluczy
Rys. 18. Wstępnie wczytuj żądania kluczy

<link rel=preload> jest bardzo przydatny, ponieważ informuje przeglądarkę, że zasób jest potrzebny w ramach bieżącej nawigacji, i stara się, aby przeglądarka pobrała go jak najszybciej.

Lighthouse informuje nas, że powinniśmy wstępnie wczytywać kluczowe zasoby czcionek internetowych, ponieważ wczytujemy 2 czcionki internetowe.

Wstępne wczytywanie czcionki internetowej wygląda tak: określasz rel=preload, przekazujesz as z typem czcionki, a następnie określasz typ czcionki, którą chcesz wczytać, np. woff2.

Może to mieć bardzo duży wpływ na Twoją stronę.

Wpływ wstępnego wczytywania zasobów
Rys. 19. Wpływ wstępnego wczytywania zasobów

Zwykle, bez użycia link rel preload, jeśli czcionki internetowe są kluczowe dla Twojej strony, przeglądarka musi najpierw pobrać kod HTML, przeanalizować kod CSS i dopiero później pobrać czcionki internetowe.

Dzięki link rel preload, gdy tylko przeglądarka przeanalizuje kod HTML, może zacząć pobierać czcionki internetowe znacznie wcześniej. W przypadku naszej aplikacji pozwoliło to skrócić o sekundę czas renderowania tekstu przy użyciu czcionek internetowych.

Jeśli chcesz wstępnie wczytywać czcionki za pomocą Google Fonts, nie jest to takie proste. Jest pewien haczyk.

Adresy URL Google Fonts, które określamy w naszych arkuszach stylów, są dość regularnie aktualizowane przez zespół ds. czcionek. Te adresy URL mogą wygasać lub być regularnie aktualizowane, dlatego jeśli chcesz mieć pełną kontrolę nad wczytywaniem czcionek, zalecamy samodzielne hostowanie czcionek internetowych. To świetne rozwiązanie, ponieważ daje dostęp do takich elementów jak link rel preload.

W naszym przypadku bardzo przydatne okazało się narzędzie Google Web Fonts Helper, które pomogło nam pobrać niektóre czcionki internetowe i skonfigurować je lokalnie. Warto je sprawdzić.

Niezależnie od tego, czy używasz czcionek internetowych jako części zasobów krytycznych, czy też jest to JavaScript, postaraj się pomóc przeglądarce w jak najszybszym dostarczeniu zasobów krytycznych.

Funkcja eksperymentalna: wskazówki dotyczące priorytetów

Mamy dla Ciebie coś wyjątkowego. Oprócz funkcji takich jak wskazówki dotyczące zasobów i wstępne wczytywanie pracujemy nad zupełnie nową eksperymentalną funkcją przeglądarki, którą nazywamy wskazówkami dotyczącymi priorytetów.

Ustaw priorytet dla treści widocznych na początku
Rys. 20. Wskazówki dotyczące priorytetów

To nowa funkcja, która pozwala podpowiedzieć przeglądarce, jak ważny jest dany zasób. Udostępnia ona nowy atrybut – importance – z wartościami low, high lub auto.

Dzięki temu możemy obniżyć priorytet mniej ważnych zasobów, takich jak niekrytyczne style, obrazy czy wywołania interfejsu API, aby zmniejszyć rywalizację o zasoby. Możemy też zwiększyć priorytet ważniejszych elementów, takich jak banery powitalne.

W przypadku naszej aplikacji Oodle doprowadziło to do znalezienia jednego praktycznego miejsca, które mogliśmy zoptymalizować.

Ustaw priorytet dla treści widocznych na początku
Rys. 21. Ustaw priorytet dla treści widocznych na początku.

Zanim dodaliśmy leniwe ładowanie do naszych obrazów, przeglądarka pobierała wszystkie obrazy z karuzeli z doodle'ami na samym początku z wysokim priorytetem. Niestety dla użytkownika najważniejsze były obrazy w środku karuzeli. Ustawiliśmy więc bardzo niską ważność obrazów tła i bardzo wysoką ważność obrazów na pierwszym planie. Dzięki temu w przypadku wolnego połączenia 3G udało się skrócić czas pobierania i renderowania obrazów o 2 sekundy. To było bardzo pozytywne doświadczenie.

Mamy nadzieję, że w ciągu kilku tygodni uda nam się udostępnić tę funkcję w Canary.

Strategia wczytywania czcionek internetowych

Typografia jest podstawą dobrego projektu. Jeśli używasz czcionek internetowych, najlepiej nie blokować renderowania tekstu i na pewno nie wyświetlać niewidocznego tekstu.

Teraz zwracamy na to uwagę w Lighthouse, przeprowadzając kontrolę unikaj niewidocznego tekstu podczas wczytywania czcionek internetowych.

Unikaj niewidocznego tekstu podczas ładowania czcionek internetowych
Rys. 22. Unikaj niewidocznego tekstu podczas ładowania czcionek internetowych

Jeśli wczytujesz czcionki internetowe za pomocą bloku font-face, pozwalasz przeglądarce zdecydować, co zrobić, gdy pobieranie czcionki internetowej trwa zbyt długo. Niektóre przeglądarki czekają na to do 3 sekund, zanim przełączą się na czcionkę systemową, a gdy czcionka zostanie pobrana, zastępują ją.

Staramy się unikać niewidocznego tekstu, więc w tym przypadku nie moglibyśmy zobaczyć klasycznych doodle z tego tygodnia, gdyby czcionka internetowa ładowała się zbyt długo. Na szczęście dzięki nowej funkcji o nazwie font-display masz znacznie większą kontrolę nad tym procesem.

    @font-face {
      font-family: 'Montserrat';
      font-style: normal;
      font-display: swap;
      font-weight: 400;
      src: local('Montserrat Regular'), local('Montserrat-Regular'),
          /* Chrome 26+, Opera 23+, Firefox 39+ */
          url('montserrat-v12-latin-regular.woff2') format('woff2'),
            /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
          url('montserrat-v12-latin-regular.woff') format('woff');
    }

Właściwość font-display pomaga określić, jak czcionki internetowe będą renderowane lub zastępowane w zależności od tego, ile czasu zajmuje ich zamiana.

W tym przypadku używamy właściwości font-display: swap. Wartość swap ustawia okres blokowania czcionki na 0 sekund, a okres zamiany na nieskończoność. Oznacza to, że przeglądarka natychmiast wyświetli tekst, używając czcionki zastępczej, jeśli wczytywanie czcionki będzie trwało zbyt długo. Zostanie on zamieniony, gdy tylko będzie dostępny.

W przypadku naszej aplikacji było to świetne rozwiązanie, ponieważ pozwoliło nam bardzo szybko wyświetlić istotny tekst i przejść na czcionkę internetową, gdy była gotowa.

Wynik wyświetlania czcionki
Rys. 23. Wynik wyświetlania czcionki

Ogólnie rzecz biorąc, jeśli używasz czcionek internetowych, tak jak duża część internetu, miej dobrą strategię ich wczytywania.

Istnieje wiele funkcji platformy internetowej, których możesz użyć do optymalizacji wczytywania czcionek, ale warto też zajrzeć do repozytorium Web Font Recipes Zacha Leathermana, bo jest naprawdę świetne.

Ogranicz skrypty blokujące renderowanie

Są inne części naszej aplikacji, które moglibyśmy przesunąć wcześniej w łańcuchu pobierania, aby zapewnić przynajmniej podstawowe wrażenia użytkownika nieco wcześniej.

Na pasku osi czasu Lighthouse widać, że przez pierwsze kilka sekund, gdy ładują się wszystkie zasoby, użytkownik nie widzi żadnych treści.

Możliwość ograniczenia arkuszy stylów blokujących renderowanie
Rys. 24. Możliwość ograniczenia arkuszy stylów blokujących renderowanie

Pobieranie i przetwarzanie zewnętrznych arkuszy stylów blokuje proces renderowania.

Możemy spróbować zoptymalizować krytyczną ścieżkę renderowania, dostarczając niektóre style nieco wcześniej.

Jeśli wyodrębnimy style odpowiedzialne za początkowe renderowanie i umieścimy je w kodzie HTML, przeglądarka będzie mogła je od razu wyrenderować bez czekania na zewnętrzne arkusze stylów.

W naszym przypadku użyliśmy modułu NPM o nazwie Critical, aby umieścić kluczowe treści w pliku index.html podczas etapu kompilacji.

Chociaż ten moduł wykonał za nas większość pracy, nadal było trochę trudno sprawić, aby działał płynnie na różnych trasach.

Jeśli nie zachowasz ostrożności lub struktura Twojej witryny jest bardzo złożona, wprowadzenie tego typu wzorca może być bardzo trudne, jeśli od początku nie zaplanowano architektury powłoki aplikacji.

Dlatego tak ważne jest, aby od samego początku brać pod uwagę kwestie związane z wydajnością. Jeśli nie zaprojektujesz aplikacji pod kątem wydajności od samego początku, istnieje duże prawdopodobieństwo, że później napotkasz problemy.

Ostatecznie ryzyko się opłaciło – udało nam się to zrobić i aplikacja zaczęła dostarczać treści znacznie wcześniej, co znacznie skróciło czas pierwszego wyrenderowania elementu znaczącego.

Wynik

To była długa lista optymalizacji wydajności, które zastosowaliśmy w naszej witrynie. Zobaczmy, jaki był wynik. Tak wyglądało ładowanie naszej aplikacji na średniej wielkości urządzeniu mobilnym w sieci 3G przed optymalizacją i po niej.

Wynik wydajności Lighthouse wzrósł z 23 do 91. To całkiem niezły postęp, jeśli chodzi o szybkość. Wszystkie zmiany zostały wprowadzone na podstawie ciągłego sprawdzania i przestrzegania raportu Lighthouse. Jeśli chcesz sprawdzić, jak technicznie wdrożyliśmy wszystkie ulepszenia, zajrzyj do naszego repozytorium, a zwłaszcza do znajdujących się tam żądań pull.

Przewidywalna wydajność – wrażenia użytkownika oparte na danych

Wierzymy, że uczenie maszynowe to ekscytująca technologia, która w przyszłości znajdzie zastosowanie w wielu dziedzinach. Mamy nadzieję, że w przyszłości będzie więcej eksperymentów, ponieważ prawdziwe dane mogą naprawdę pomóc w tworzeniu wrażeń użytkowników.

Obecnie podejmujemy wiele arbitralnych decyzji dotyczących tego, czego użytkownik może chcieć lub potrzebować, a więc co warto pobrać z wyprzedzeniem, wstępnie wczytać lub wstępnie buforować. Jeśli zgadniemy, możemy nadać priorytet niewielkiej ilości zasobów, ale naprawdę trudno jest to przeskalować na całą witrynę.

Mamy już dane, które pozwalają nam lepiej optymalizować kampanie. Korzystając z interfejsu API do raportowania Google Analytics, możemy sprawdzić następną najpopularniejszą stronę i procent wyjść dla dowolnego adresu URL w naszej witrynie, a następnie wyciągnąć wnioski, które zasoby powinniśmy traktować priorytetowo.

Jeśli połączymy to z dobrym modelem prawdopodobieństwa, unikniemy marnowania danych użytkownika przez agresywne pobieranie zbyt dużej ilości treści z wyprzedzeniem. Możemy wykorzystać te dane Google Analytics oraz uczenie maszynowe i modele takie jak łańcuchy Markowa czy sieć neuronowa, aby wdrożyć takie modele.

Grupowanie aplikacji internetowych na podstawie danych
Rys. 25. Grupowanie aplikacji internetowych na podstawie danych

Aby ułatwić te eksperymenty, z przyjemnością ogłaszamy nową inicjatywę o nazwie Guess.js.

Guess.js
Rys. 26. Guess.js

Guess.js to projekt skupiający się na wrażeniach użytkowników internetu opartych na danych. Mamy nadzieję, że zainspiruje to do wykorzystywania danych do poprawy wydajności stron internetowych i nie tylko. Wszystko jest open source i dostępne na GitHubie. Został on stworzony we współpracy ze społecznością open source przez Minko Gecheva, Kyle'a Matthewsa z Gatsby, Katie Hempenius i wiele innych osób.

Wypróbuj Guess.js i daj nam znać, co o nim sądzisz.

Podsumowanie

Wyniki i wskaźniki pomagają zwiększyć szybkość działania internetu, ale są tylko środkiem, a nie celem samym w sobie.

Wszyscy doświadczyliśmy powolnego wczytywania stron na urządzeniach mobilnych, ale teraz mamy możliwość zapewnienia użytkownikom lepszych wrażeń dzięki szybkiemu wczytywaniu.

Zwiększanie skuteczności to proces. Wiele małych zmian może przynieść duże korzyści. Korzystając z odpowiednich narzędzi do optymalizacji i analizując raporty Lighthouse, możesz zapewnić użytkownikom lepsze i bardziej inkluzywne wrażenia.

With special thanks to: Ward Peeters, Minko Gechev, Kyle Mathews, Katie Hempenius, Dom Farolino, Yoav Weiss, Susie Lu, Yusuke Utsunomiya, Tom Ankers, Lighthouse & Google Doodles.