Ponieważ strony, które tworzymy, są coraz bardziej zależne od JavaScriptu, czasami płacimy za to, co wysyłamy, w sposób, który nie zawsze jest łatwy do zauważenia. Z tego artykułu dowiesz się, dlaczego odrobina dyscypliny może Ci pomóc, jeśli chcesz, aby Twoja witryna wczytywała się i działała szybko na urządzeniach mobilnych. Przesyłanie mniejszej ilości kodu JavaScriptu może oznaczać krótszy czas przesyłania przez sieć, mniejszy czas potrzebny na rozpakowanie kodu oraz krótszy czas potrzebny na analizowanie i kompilowanie tego kodu.
Sieć
Gdy większość deweloperów myśli o kosztach JavaScriptu, ma na myśli koszt pobierania i wykonywania. Wysyłanie większej liczby bajtów kodu JavaScriptu przez sieć zajmuje więcej czasu, gdy połączenie użytkownika jest wolniejsze.
Może to stanowić problem, ponieważ skuteczny typ połączenia z siecią użytkownika może nie być 3G, 4G ani Wi-Fi. Możesz korzystać z Wi-Fi w kawiarni, ale być połączony z hotspotem komórkowym z szybkością 2G.
Możesz obniżyć koszty przesyłania JavaScriptu w sieci, wykonując te czynności:
- Wysyłanie tylko kodu potrzebnego użytkownikowi.
- Użyj dzielenia kodu, aby podzielić kod JavaScript na część krytyczne i niekrytyczne. Pakiety modułów, takie jak webpack, obsługują dzielenie kodu.
- Leniwe ładowanie kodu, który nie jest krytyczny.
- Minifikacja
- Użyj narzędzia UglifyJS do skracania kodu ES5.
- Aby zminimalizować ES2015+, użyj narzędzia babel-minify lub uglify-es.
- Kompresja
- Do kompresji zasobów tekstowych należy używać co najmniej gzip.
- Rozważ użycie Brotli.~q11. Brotli jest lepszy od gzip pod względem współczynnika kompresji. Dzięki temu CertSimple udało się zaoszczędzić 17% na rozmiarze skompresowanych bajtów kodu JS, a LinkedIn zaoszczędził 4% na czasie wczytywania.
- Usuwanie nieużywanego kodu.
- Wykorzystaj pokrywanie kodu w Narzędziach deweloperskich, aby zidentyfikować kod, który można usunąć lub wczytywać leniwie.
- Użyj poleceń babel-preset-env i browserlist, aby uniknąć transpilacji funkcji, które są już dostępne w nowoczesnych przeglądarkach. Deweloperzy zaawansowani mogą zauważyć, że dokładna analiza pakietów webpack pomaga im wykrywać możliwości usunięcia zbędących zależności.
- Aby usunąć kod, użyj tree-shakingu, zaawansowanych optymalizacji Closure Compiler i wtyczek do obcinania bibliotek, takich jak lodash-babel-plugin czy ContextReplacementPlugin webpacka do bibliotek takich jak Moment.js.
- Korzystanie z pamięci podręcznej kodu w celu zminimalizowania połączeń z internetem.
- Użyj pamięci podręcznej HTTP, aby zapewnić skuteczne przechowywanie odpowiedzi w pamięci podręcznej przeglądarki. Określ optymalny czas życia skryptów (max-age) i dostarczaj tokeny walidacyjne (ETag), aby uniknąć przesyłania niezmienionych bajtów.
- Buforowanie w usłudze workera może zwiększyć odporność sieci aplikacji i zapewnić Ci szybki 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 używasz Webpacka, zapoznaj się z artykułem o hashowaniu nazw plików.
Analizowanie/kompilowanie
Po pobraniu jednym z największych kosztów JavaScriptu jest czas potrzebny na przeanalizowanie i skompilowanie kodu przez silnik JS. W Narzędziach deweloperskich Chrome parsowanie i kompilacja są częścią żółtego czasu „Skryptowanie” w panelu Wydajność.
Karty Od dołu i Drzewo wywołań podają dokładne czasy analizowania i kompilowania:

Ale dlaczego to ma znaczenie?
Długi czas analizowania i kompilowania kodu może znacznie opóźnić interakcję użytkownika z Twoją witryną. Im więcej kodu JavaScriptu przesyłasz, tym dłużej trwa jego analizowanie i skompilowanie, zanim witryna stanie się interaktywna.
„Byte-for-byte, JavaScript is more expensive for the browser to process than the equivalently sized image or Web Font” – Tom Dale
W porównaniu z JavaScriptem przetwarzanie obrazów o równych rozmiarach wiąże się z licznymi kosztami (trzeba je jeszcze zdekodować), ale na przeciętnym sprzęcie mobilnym JavaScript może negatywnie wpływać na interaktywność strony.

Gdy mówimy o powolnym parsowaniu i kompilowaniu, kontekst jest ważny – mówimy tu o przeciętnych telefonach komórkowych. Użytkownicy korzystający z telefonów z wolnymi procesorami i kartami graficznymi, bez pamięci podręcznej L2/L3, a czasem nawet z ograniczoną ilością pamięci.
Możliwości sieci i urządzeń nie zawsze są zgodne. Użytkownik z niesamowitym połączeniem światłowodowym niekoniecznie ma najlepszy procesor do analizowania i interpretowania kodu JavaScript wysyłanego na jego urządzenie. To samo dotyczy odwrotnej sytuacji: kiepskie połączenie z internetem, ale błyskawicznie szybki procesor. – Kristofer Baxter, LinkedIn
Poniżej możesz zobaczyć koszt parsowania około 1 MB skompresowanego (prostego) kodu JavaScript na sprzęcie niskiego i wysokiego poziomu. Czas analizowania i kompilowania kodu jest 2–5 razy dłuższy na najszybszych telefonach na rynku niż na przeciętnych telefonach.

A co z witryną w świecie rzeczywistym, np. CNN.com?
Na zaawansowanym telefonie iPhone 8 parsowanie i skompilowanie kodu JS z serwisu CNN zajmuje tylko ok. 4 s, podczas gdy na przeciętnym telefonie (Moto G4) trwa to ok. 13 s. Może to mieć znaczący wpływ na szybkość, z jaką użytkownik może w pełni korzystać z tej witryny.

Dlatego ważne jest, aby testować na przeciętnym sprzęcie (np. Moto G4), a nie tylko na telefonie, który masz w kieszeni. Kontekst ma znaczenie: optymalizuj pod kątem urządzenia i warunków sieci, z których korzystają użytkownicy.

Czy naprawdę wysyłamy zbyt dużo kodu JavaScript? Yyy, possibly :)
Korzystając z archiwum HTTP (około 500 najpopularniejszych witryn), przeanalizowaliśmy stan JavaScriptu na urządzeniach mobilnych. Okazało się, że 50% witryn potrzebuje ponad 14 sekund na wczytanie. Te witryny poświęcają na analizowanie i kompilowanie kodu JS nawet 4 sekundy.
Dodaj do tego czas potrzebny na pobieranie i przetwarzanie kodu JavaScript i innych zasobów, a nie będzie Cię dziwić, że użytkownicy muszą czekać, zanim będą mogli korzystać z witryn. Z pewnością możemy to zrobić lepiej.
Usunięcie z stron niekrytycznych skryptów JavaScript może skrócić czas transmisji, analizowania i kompilowania oraz potencjalne obciążenie pamięci. Pomaga to też szybciej wczytywać interaktywne elementy strony.
Czas wykonywania
Koszt może się wiązać nie tylko z analizą i skompilowaniem. Wykonanie kodu JavaScript (uruchamianie kodu po przeanalizowaniu i skompilowaniu) to jedna z operacji, które muszą być wykonywane w wątku głównym. Długi czas wykonywania może też opóźniać czas, po którym użytkownik może wejść w interakcję z Twoją witryną.
Jeśli skrypt jest wykonywany przez ponad 50 ms, czas do interakcji wydłuża się o cały czas potrzebny na pobranie, skompilowanie i wykonanie kodu JS.
Aby tego uniknąć, kod JavaScriptu powinien być podzielony na małe fragmenty, aby nie blokować wątku głównego. Sprawdź, czy możesz zmniejszyć ilość pracy wykonywanej podczas wykonywania kodu.
Inne koszty
JavaScript może wpływać na wydajność strony na inne sposoby:
- Pamięć. Ze względu na GC (garbage collection) strony mogą wydawać się niepłynne lub często się zatrzymywać. Gdy przeglądarka odzyskuje pamięć, wykonywanie kodu JS jest wstrzymywane, więc przeglądarka często zbierająca śmieci może wstrzymywać wykonywanie kodu częściej, niż byśmy chcieli. Aby uniknąć wycieku pamięci i częstych przerw w działaniu GC, zadbaj o płynne działanie stron.
- Podczas działania długotrwały kod JavaScript może blokować wątek główny, powodując, że strony przestają odpowiadać. Dzielenie pracy na mniejsze części (za pomocą
requestAnimationFrame()
lubrequestIdleCallback()
do planowania) może zminimalizować problemy z responsywnością, co może pomóc w poprawie czasu od interakcji do kolejnego wyrenderowania (INP).
Sposoby zmniejszania kosztów dostarczania kodu JavaScript
Jeśli chcesz, aby czasy analizowania/kompilowania i przesyłania sieciowego kodu JavaScript były jak najkrótsze, możesz skorzystać z takich wzorów jak podział na fragmenty na podstawie trasy lub PRPL.
PRPL
PRPL (Push, Render, Pre-cache, Lazy-load) to wzór, który optymalizuje interaktywność dzięki agresywnemu dzieleniu kodu i używaniu pamięci podręcznej:
Zobaczmy, jaki może mieć wpływ.
Korzystając z funkcji Runtime Call Stats w V8, analizujemy czas wczytywania popularnych witryn mobilnych i progresywnych aplikacji internetowych. Jak widać, czas analizowania (podany na pomarańczowo) zajmuje znaczną część czasu, jaki użytkownicy spędzają na tych stronach:
Wego to witryna, która korzysta z PRPL. Dzięki temu udało się jej utrzymać krótki czas analizowania tras, dzięki czemu użytkownicy mogą bardzo szybko uzyskać interaktywność. Wiele z tych witryn wdrożyło podział kodu i budżety na wydajność, aby obniżyć koszty kodu JS.
Bootstrapping progresywny
Wiele witryn optymalizuje widoczność treści kosztem interakcji. Aby uzyskać szybkie wyświetlenie pierwszego pokolorowania, gdy masz duże pakiety JavaScriptu, deweloperzy czasami stosują renderowanie po stronie serwera, a potem „ulepszają” je, aby dołączyć przetwarzanie zdarzeń, gdy kod JavaScriptu zostanie w końcu pobrany.
Uważaj – wiąże się to z dodatkowymi kosztami. 1) zwykle wysyłasz większy kod HTML, który może zwiększać interaktywność, 2) możesz pozostawić użytkownika w niezwykłej dolinie, w której połowa interakcji nie może być interaktywna, dopóki JavaScript nie zakończy przetwarzania.
Lepszym rozwiązaniem może być uruchamianie aplikacji stopniowo. Przesyłanie strony o minimalnej funkcjonalności (składającej się tylko z kodu HTML/JS/CSS potrzebnego do bieżącej ścieżki). Gdy do aplikacji docierają kolejne zasoby, może ona ładować je z opóźnieniem i odblokowywać kolejne funkcje.

Ładowanie kodu proporcjonalnie do tego, co jest widoczne, to święty Graal. W tym celu możesz wykorzystać metody PRPL i progresywnego uruchamiania.
Podsumowanie
Rozmiar transmisji ma kluczowe znaczenie w przypadku sieci niskiego poziomu. Czas analizowania jest ważny w przypadku urządzeń z procesorem ograniczonym. Utrzymywanie ich na niskim poziomie jest ważne.
Zespół odniósł sukces dzięki zastosowaniu ścisłych budżetów skuteczności, które pozwoliły skrócić czas przesyłania i analizowania/kompilowania kodu JavaScript. Zobacz film „Can You Afford It?: „Real-world Web Performance Budgets” (Budżety wydajności witryn w rzeczywistych warunkach)

Jeśli tworzysz witrynę przeznaczoną na urządzenia mobilne, staraj się robić to na reprezentatywnym sprzęcie, utrzymuj krótki czas analizowania i kompilowania kodu JavaScript oraz stosuj budżet wydajności, aby Twój zespół mógł kontrolować koszty kodu JavaScript.
Więcej informacji
- Chrome Dev Summit 2017 – nowoczesne metody ładowania – najlepsze praktyki
- Prędkość uruchamiania kodu JavaScript
- Rozwiązywanie kryzysu związanego z wydajnością stron internetowych – Nolan Lawson
- Czy możesz sobie na to pozwolić? Real-world performance budgets — Alex Russell
- Ocena frameworków i bibliotek internetowych – Krzysztof Baxter
- Wyniki eksperymentów Cloudflare z Brotli w zakresie kompresji (uwaga: dynamiczny Brotli o wyższej jakości może opóźnić początkowe renderowanie strony, więc należy to dokładnie ocenić). Prawdopodobnie lepiej będzie skompresować je statycznie.)
- Performance Futures — Sam Saccone