Optymalizacja uruchamiania JavaScript podczas uruchamiania

Addy Osmani
Addy Osmani

Tworzymy witryny w większym stopniu bazujące na języku JavaScript, więc czasem płacimy za to, co przesyłamy w sposób, który nie zawsze jest łatwo widoczny. Z tego artykułu dowiesz się, dlaczego warto odrobić dyscyplinę, jeśli chcesz, by Twoja witryna się ładowała i działała szybko na urządzeniach mobilnych. Im mniej kodu JavaScript, tym mniej czasu poświęcanego na transmisję danych w sieci, mniej czasu poświęcanego na dekompresję kodu oraz mniej czasu na analizowanie i kompilowanie tego kodu.

Sieć

Większość programistów bierze pod uwagę koszty JavaScriptu w świetle kosztów pobierania i wykonywania. Wysyłanie większej liczby bajtów JavaScriptu przez kabel trwa dłużej, im wolniejsze jest połączenie.

Gdy przeglądarka wysyła żądanie zasobu, musi on zostać pobrany, a następnie zdekompresowany. Zasoby takie jak JavaScript muszą zostać przeanalizowane i skompilowane przed wykonaniem.

Może to być problem, ponieważ efektywny typ połączenia sieciowego użytkownika może nie być 3G, 4G ani Wi-Fi. Możesz korzystać z kawiarni przez Wi-Fi, ale możesz też połączyć się z komórkowym hotspotem o szybkości 2G.

Możesz obniżyć koszt przesyłania danych w sieci JavaScriptu poprzez:

  • Wysyłanie tylko kodu potrzebnego użytkownikowi.
    • Użyj podziału kodu, aby podzielić JavaScript na elementy o znaczeniu krytycznym, a które nie. Moduły do tworzenia pakietów, takie jak webpack, obsługują dzielenie kodu.
    • Leniwe ładowanie w kodzie, który nie jest krytyczny.
  • Minifikacja
  • Kompresja
    • Do skompresowania zasobów tekstowych używaj co najmniej narzędzia gzip.
    • Rozważ użycie Brotli ~q11. Pod względem stopnia kompresji Brotli przebija gzip. Dzięki temu udało się zaoszczędzić 17% rozmiaru skompresowanych bajtów JS, a LinkedIn zmniejszyć czas wczytywania o 4%.
  • Usuwanie nieużywanego kodu
  • Buforowanie kodu w celu zminimalizowania ruchu sieciowego.
    • Używaj buforowania HTTP, aby zapewnić skuteczne odpowiedzi przeglądarki w pamięci podręcznej. Aby uniknąć przenoszenia niezmienionych bajtów, ustal optymalny czas trwania skryptów (max-age) i tokeny weryfikacyjne (ETag).
    • Buforowanie skryptu service worker może zwiększyć stabilność sieci aplikacji i zapewnić Ci łatwy dostęp do funkcji takich jak pamięć podręczna kodu V8.
    • Używaj długoterminowego buforowania, aby uniknąć konieczności ponownego pobierania zasobów, które się nie zmieniły. Jeśli korzystasz z pakietu Webpack, zapoznaj się z informacjami na temat szyfrowania nazw plików.

Analiza/kompilacja

Jednym z największych kosztów JavaScriptu po pobraniu jest jego analiza/skompilowanie. W Narzędziach deweloperskich w Chrome analiza i kompilacja wchodzą w skład żółtego czasu „Obsługa skryptów” w panelu wydajności.

ALT_TEXT_HERE

Karty Dół Up i Drzewo połączeń pokazują dokładne czasy analizy/kompilacji:

ALT_TEXT_HERE
Chrome Panel wydajności Narzędzi deweloperskich > Od dołu do góry. Po włączeniu statystyk połączeń w czasie działania środowiska wykonawczego V8 możemy obserwować czas spędzony w fazach takich jak analiza i kompilacja.

Dlaczego jednak to takie ważne?

ALT_TEXT_HERE

Jeśli poświęcisz dużo czasu na analizowanie lub kompilowanie kodu, może to znacznie opóźnić czas, przez jaki użytkownik będzie mógł wejść w interakcję z Twoją witryną. Im więcej kodu JavaScript wyślesz, tym dłużej potrwa jego przeanalizowanie i skompilowanie, zanim witryna stanie się interaktywna.

Bajt na bajt – obsługa języka JavaScript jest droższa w przeglądarce niż odpowiedni rozmiar obrazu lub czcionki internetowej – Tom Dale

W porównaniu z JavaScriptem przetwarzanie obrazów o równej wielkości wiąże się z dużymi kosztami (konieczne jest ich zdekodowanie), jednak przeciętnie w przypadku sprzętu mobilnego JS może mieć negatywny wpływ na interaktywność strony.

ALT_TEXT_HERE
Koszty JavaScriptu i bajtów obrazów różnią się od siebie. Obrazy zwykle nie blokują głównego wątku ani nie uniemożliwiają interakcji interfejsów podczas dekodowania i rasteryzacji. JS może jednak opóźnić interaktywność z powodu kosztów analizy, kompilacji i wykonania.

Gdy mówimy o powolnym analizowaniu i kompilowaniu danych, ważny jest kontekst – rozmawiamy tu o przeciętnych telefonach komórkowych. Przeciętny użytkownik ma telefony z wolnym procesorem i układem GPU, bez pamięci podręcznej L2/L3, a nawet z ograniczoną pamięcią.

Możliwości sieciowe i możliwości urządzeń nie zawsze są zgodne. Użytkownik ze świetnym połączeniem światłowodowym nie musi mieć najlepszego procesora do przeanalizowania i oceny kodu JavaScript wysłanego na jego urządzenie. To samo dzieje się również przy kiepskim połączeniu sieciowym, ale o niesamowicie szybkim procesorze. – KristoferBaxter, LinkedIn

Poniżej możesz zobaczyć koszt analizy ok. 1 MB zdekompresowanego (prostego) JavaScriptu na słabszych i zaawansowanych sprzęcie. Najszybsze telefony na rynku i przeciętne telefony o nazwie 2–5 razy różnią się od siebie o 2–5 razy różnicy w czasie.

ALT_TEXT_HERE
Ten wykres przedstawia czas analizy pakietu JavaScript o wielkości 1 MB (ok. 250 KB w pliku gzip) na komputerach i urządzeniach mobilnych należących do różnych klas. Patrząc na koszt analizy, jest to zdekompresowane wartości, które należy wziąć pod uwagę np. 250 KB pliku JS spakowanego w formacie gzip zdekompresowane do ok. 1 MB kodu.

A co z prawdziwymi stronami, np. CNN.com?

W zaawansowanych telefonach iPhone 8 przetworzenie lub skompilowanie kodu JS CNN zajmuje zaledwie 4 s, a w przypadku przeciętnego telefonu (Moto G4) trwa to ok. 13 s. Może to znacząco wpłynąć na to, jak szybko użytkownik będzie mógł w pełni korzystać z tej witryny.

ALT_TEXT_HERE
Powyżej widać porównanie wydajności układu Apple A11 Bionic z układem Snapdragon 617 w bardziej przeciętnym sprzęcie z Androidem.

To pokazuje, jak ważne jest testowanie przeciętnego sprzętu (np. Moto G4), a nie tylko telefonu, który możesz mieć w kieszeni. Kontekst ma jednak znaczenie: optymalizuj użytkowników pod kątem stanu urządzenia i sieci.

ALT_TEXT_HERE
Google Analytics może dostarczyć Ci wgląd w klasy urządzeń mobilnych, z którymi Twoi prawdziwi użytkownicy uzyskują dostęp do Twojej witryny. Może to pomóc w zrozumieniu rzeczywistych ograniczeń procesora i GPU, z którymi współpracują.

Czy naprawdę wysyłamy zbyt dużo kodu JavaScript? Eee, być może :)

Korzystając z archiwum HTTP (około 500 tys. pierwszych witryn) do analizy stanu JavaScriptu na urządzeniach mobilnych, widzimy, że 50% witryn wymaga 14-sekundowej interaktywności. Takie witryny potrzebują do 4 sekund na analizowanie i kompilowanie kodu JS.

ALT_TEXT_HERE

Weź pod uwagę czas potrzebny na pobieranie i przetwarzanie kodu JS oraz innych zasobów. Nic dziwnego, że użytkownicy mogą poczekać, aż strony będą gotowe do użycia. Tutaj z pewnością sobie poradzimy.

Usunięcie niekrytycznego kodu JavaScript ze stron może skrócić czas transmisji oraz intensywnie przetwarzać procesor i skompilować kod JavaScript, a także zmniejszyć ilość pamięci. Przyspiesza to też interakcję między stronami.

Czas wykonywania

Nie tylko analizowanie i kompilowanie wiąże się z kosztami. Wykonanie JavaScriptu (po przeanalizowaniu/skompilowaniu kodu) to jedna z operacji, które muszą zostać wykonane w wątku głównym. Długi czas wykonywania kodu może też wpłynąć na to, jak szybko użytkownik będzie mógł wejść w interakcję z witryną.

ALT_TEXT_HERE

Jeśli skrypt jest wykonywany przez ponad 50 ms, czas do pełnej interaktywności jest opóźniony o całkowity czas potrzebny na pobranie, skompilowanie i wykonanie kodu JS – Alex Russell.

Aby rozwiązać ten problem, JavaScript powinien być tworzony w małych fragmentach, co pozwala uniknąć zablokowania głównego wątku. Sprawdź, czy możesz zmniejszyć ilość pracy wykonywanej podczas wykonywania zadania.

Inne koszty

JavaScript może wpływać na wydajność strony w inny sposób:

  • Pamięć. Strony mogą się często zawieszać lub zacinać się z powodu czyszczenia pamięci (GC). Gdy przeglądarka odzyska pamięć, wykonanie kodu JS jest wstrzymywane, aby przeglądarka często zbierająca śmieci mogła wstrzymywać wykonywanie kodu częściej niż powinna. Unikaj wycieków pamięci i częstych wstrzymywania GC, aby uniknąć zacinania się stron.
  • W czasie działania długo działający JavaScript może zablokować wątek główny, powodując, że strony nie odpowiadają. Podzielenie zadania na mniejsze części (za pomocą requestAnimationFrame() lub requestIdleCallback() do planowania) może zminimalizować problemy z czasem reakcji, co może poprawić interakcje z kolejnym wyrenderowaniem (INP).

Wzorce obniżania kosztów dostarczania JavaScript

Jeśli próbujesz utrzymać powolne czasy analizy/kompilowania i przesyłania danych w sieci w przypadku JavaScriptu, możesz korzystać z wzorców, które ułatwiają dzielenie się fragmentami na podstawie trasy lub PRPL.

Przygotowanie do egzaminów PRPL

PRPL (Push, Render, Pre-cache, Lazy-load) to wzorzec, który optymalizuje pod kątem interaktywności przez agresywny podział kodu i buforowanie w pamięci podręcznej:

ALT_TEXT_HERE

Zobaczmy, jaki może to mieć wpływ.

Analizujemy czas wczytywania popularnych stron mobilnych i progresywnych aplikacji internetowych, korzystając ze statystyk wywołań środowiska wykonawczego w V8. Jak widać, analizowanie czasu (zaznaczone na pomarańczowo) stanowi znaczną część czasu spędzanego w wielu witrynach:

ALT_TEXT_HERE

Wego, witryna wykorzystująca PRPL, utrzymuje krótki czas analizy tras, dzięki czemu bardzo szybko się uaktywnia. Wiele z powyższych witryn wdrożyło podział kodu i budżety wydajności, aby spróbować obniżyć koszty kodu JavaScript.

Wczytywanie progresywne

Wiele witryn optymalizuje widoczność treści kosztem interakcji. Aby uzyskać szybkie pierwsze wyrenderowanie, gdy masz duże pakiety JavaScriptu, programiści stosują czasem renderowanie po stronie serwera, a następnie „uaktualnianie” go w celu dołączenia modułów obsługi zdarzeń po ostatecznym pobraniu kodu JavaScript.

Zachowaj ostrożność, ponieważ wiąże się to z własnymi kosztami. 1) Na ogół wysyłasz większą odpowiedź HTML, która może zwiększyć naszą interaktywność, 2) może pozostawić użytkownika w niesamowitej dolinie, w której połowa reklamy nie będzie interaktywna do czasu zakończenia przetwarzania JavaScriptu.

Lepszym rozwiązaniem może być progresywne wczytywanie. Wyślij w dół stronę o minimalistycznym działaniu (składaną wyłącznie z kodu HTML/JS/CSS niezbędnych dla bieżącej trasy). W miarę pojawiania się kolejnych zasobów aplikacja będzie mogła leniwie ładować się i odblokowywać kolejne funkcje.

ALT_TEXT_HERE
Progresywne bootstrapping (Paul Lewis)

Wczytywanie kodu proporcjonalne do tego, co widać, to święty Graal. W tym celu mogą pomóc PRPL i progresywne wczytywanie.

Podsumowanie

Rozmiar transmisji ma kluczowe znaczenie w sieciach niskiej jakości. Czas analizy jest ważny w przypadku urządzeń powiązanych z CPU. Utrzymywanie tych niskich wartości jest istotne.

Zespoły z powodzeniem stosują wysokie budżety wydajności, aby utrzymać krótki czas przesyłania oraz analizowania i kompilacji JavaScriptu. Zobacz film Alexa Russella „Can You Afford It?: Rzeczywista skuteczność w internecie Budżety” w celu uzyskania wskazówek dotyczących budżetów na reklamy mobilne.

ALT_TEXT_HERE
Warto się zastanowić, jak duże możliwości związane z architekturą pozostawią nam w zakresie logiki aplikacji w językach JavaScript.

Jeśli tworzysz witrynę na urządzenia mobilne, postaw na reprezentatywny sprzęt, skróć czas analizy i kompilacji JavaScriptu oraz zastosuj budżet wydajności, który pozwoli Twojemu zespołowi monitorować koszty JavaScriptu.

Więcej informacji