Gdy tworzymy witryny, które w dużym stopniu opierają się na języku JavaScript, czasami płacimy za to, co wysyłamy, w sposób, który nie zawsze jest łatwy do zauważenia. W tym artykule wyjaśnimy, dlaczego odrobina dyscypliny może pomóc, jeśli chcesz, aby Twoja witryna szybko się wczytywała i była interaktywna na urządzeniach mobilnych. Dostarczanie mniejszej ilości kodu JavaScript może oznaczać krótszy czas transmisji sieciowej, mniej czasu poświęcanego na dekompresję kodu oraz krótszy czas analizowania i kompilowania tego kodu.
Sieć
Gdy większość deweloperów myśli o koszcie JavaScriptu, myśli o koszcie pobierania i wykonywania. Przesyłanie większej liczby bajtów kodu JavaScript przez sieć trwa dłużej, im wolniejsze jest połączenie użytkownika.

Może to być problem, ponieważ efektywny typ połączenia sieciowego użytkownika może nie być połączeniem 3G, 4G ani Wi-Fi. Możesz korzystać z Wi-Fi w kawiarni, ale mieć połączenie z hotspotem komórkowym o szybkości 2G.
Możesz obniżyć koszt przesyłania kodu JavaScript przez sieć, wykonując te czynności:
- Wysyłanie tylko kodu, którego potrzebuje użytkownik.
- Użyj dzielenia kodu, aby podzielić kod JavaScript na krytyczny i niekrytyczny. Narzędzia do tworzenia pakietów modułów, takie jak webpack, obsługują dzielenie kodu.
- Leniwe ładowanie kodu, który nie jest kluczowy.
- Minifikacja
- Użyj UglifyJS do minifikacji kodu ES5.
- Aby zmniejszyć kod ES2015+, użyj babel-minify lub uglify-es.
- Kompresja
- Usuwanie nieużywanego kodu.
- Zidentyfikuj fragmenty kodu, które można usunąć lub ładować leniwie, korzystając z pokrycia kodu w Narzędziach deweloperskich.
- Używaj babel-preset-env i browserlist, aby uniknąć transpilacji funkcji, które są już dostępne w nowoczesnych przeglądarkach. Zaawansowani deweloperzy mogą przeprowadzić dokładną analizę pakietów webpack, aby zidentyfikować możliwości usunięcia niepotrzebnych zależności.
- Jeśli chodzi o usuwanie kodu, zapoznaj się z eliminacją martwego kodu, zaawansowanymi optymalizacjami Closure Compiler i wtyczkami do przycinania bibliotek, takimi jak lodash-babel-plugin lub ContextReplacementPlugin webpacka w przypadku bibliotek takich jak Moment.js.
- Zapisywanie kodu w pamięci podręcznej w celu zminimalizowania liczby żądań sieciowych.
- Używaj buforowania HTTP, aby przeglądarki skutecznie zapisywały odpowiedzi w pamięci podręcznej. Określ optymalny czas życia skryptów (max-age) i podaj tokeny weryfikacyjne (ETag), aby uniknąć przesyłania niezmienionych bajtów.
- Pamięć podręczna skryptu service worker może zwiększyć odporność aplikacji na problemy z siecią i zapewnić szybki dostęp do funkcji takich jak pamięć podręczna kodu V8.
- Używaj długiego buforowania, aby uniknąć ponownego pobierania zasobów, które nie uległy zmianie. Jeśli używasz Webpack, zapoznaj się z informacjami o haszowaniu nazw plików.
Analiza/kompilacja
Po pobraniu jednym z największych kosztów JavaScriptu jest czas, jaki silnik JS potrzebuje na przeanalizowanie/skompilowanie tego kodu. W Narzędziach deweloperskich w Chrome analizowanie i kompilowanie są częścią żółtego czasu „Skryptowanie” w panelu Wydajność.

Karty Od dołu i Drzewo wywołań pokazują dokładne czasy analizowania i kompilowania:
Dlaczego to takie ważne?

Długi czas analizowania i kompilowania kodu może znacznie opóźnić moment, w którym użytkownik może wejść w interakcję z witryną. Im więcej kodu JavaScript wyślesz, tym dłużej będzie trwało jego analizowanie i kompilowanie, zanim witryna stanie się interaktywna.
Bajt po bajcie, przetwarzanie kodu JavaScript jest dla przeglądarki bardziej kosztowne niż obrazu lub czcionki internetowej o takim samym rozmiarze – Tom Dale
W porównaniu z JavaScriptem przetwarzanie obrazów o podobnym rozmiarze wiąże się z wieloma kosztami (nadal trzeba je zdekodować), ale na przeciętnym sprzęcie mobilnym JS częściej negatywnie wpływa na interaktywność strony.
Gdy mówimy o powolnym analizowaniu i kompilowaniu, ważny jest kontekst – mamy na myśli przeciętne telefony komórkowe. Przeciętni użytkownicy mogą mieć telefony z wolnymi procesorami i GPU, bez pamięci podręcznej L2/L3, a nawet z ograniczoną pamięcią.
Możliwości sieci i urządzenia nie zawsze są takie same. Użytkownik z doskonałym połączeniem światłowodowym niekoniecznie musi mieć najlepszy procesor do analizowania i oceniania kodu JavaScript wysyłanego na jego urządzenie. To samo dotyczy sytuacji odwrotnej: fatalne połączenie sieciowe, ale błyskawiczny procesor. – Kristofer Baxter, LinkedIn
Poniżej widać koszt analizy ok. 1 MB zdekompresowanego (prostego) kodu JavaScript na sprzęcie niższej i wyższej klasy. Czas potrzebny na przeanalizowanie i skompilowanie kodu na najszybszych telefonach na rynku jest od 2 do 5 razy krótszy niż na przeciętnych telefonach.
A co z prawdziwą witryną, np. CNN.com?
Na wysokiej klasy iPhonie 8 analiza i kompilacja kodu JS CNN zajmuje tylko ok. 4 sekund, podczas gdy na przeciętnym telefonie (Moto G4) trwa to ok. 13 sekund. Może to znacząco wpłynąć na to, jak szybko użytkownik może w pełni korzystać z witryny.
To pokazuje, jak ważne jest testowanie na przeciętnym sprzęcie (np. Moto G4), a nie tylko na telefonie, który masz w kieszeni. Kontekst ma jednak znaczenie: optymalizuj pod kątem urządzeń i warunków sieciowych użytkowników.
Czy naprawdę wysyłamy za dużo kodu JavaScript? Err, possibly :)
Korzystając z archiwum HTTP (ok. 500 tys. najpopularniejszych witryn) do analizy stanu JavaScriptu na urządzeniach mobilnych, możemy zauważyć, że w przypadku 50% witryn uzyskanie interaktywności zajmuje ponad 14 sekund. Te witryny poświęcają nawet 4 sekundy na samo analizowanie i kompilowanie kodu JS.

Jeśli weźmiemy pod uwagę czas potrzebny na pobranie i przetworzenie kodu JS oraz innych zasobów, nie powinno dziwić, że użytkownicy muszą czasem poczekać, zanim strony będą gotowe do użycia. Zdecydowanie możemy to poprawić.
Usunięcie ze stron niekrytycznego kodu JavaScript może skrócić czas transmisji, zmniejszyć obciążenie procesora podczas analizowania i kompilowania oraz ograniczyć potencjalne wykorzystanie pamięci. Dzięki temu strony szybciej stają się interaktywne.
Czas wykonywania
Kosztowne mogą być nie tylko analizowanie i kompilowanie. Wykonywanie kodu JavaScript (uruchamianie kodu po jego przeanalizowaniu/skompilowaniu) to jedna z operacji, które muszą być wykonywane w głównym wątku. Długi czas wykonywania może też opóźnić moment, w którym użytkownik może wejść w interakcję z witryną.

Jeśli skrypt wykonuje się dłużej niż 50 ms, czas do pełnej interaktywności jest opóźniony o cały czas potrzebny na pobranie, skompilowanie i wykonanie kodu JS – Alex Russell
Aby temu zapobiec, warto podzielić kod JavaScript na małe fragmenty, co pozwoli uniknąć blokowania wątku głównego. Sprawdź, czy możesz zmniejszyć ilość pracy wykonywanej podczas realizacji.
Inne koszty
JavaScript może wpływać na wydajność strony na inne sposoby:
- Pamięć. Strony mogą często zacinać się lub zatrzymywać z powodu GC (garbage collection). Gdy przeglądarka odzyskuje pamięć, wykonywanie kodu JS jest wstrzymywane, więc przeglądarka, która często zbiera niepotrzebne dane, może wstrzymywać wykonywanie kodu częściej, niż byśmy chcieli. Unikaj wycieków pamięci i częstych przerw na odśmiecanie pamięci, aby strony działały płynnie.
- Podczas działania długo działający kod JavaScript może blokować główny wątek, co powoduje, że strony nie reagują. 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 interakcji do kolejnego wyrenderowania (INP).
Wzorce zmniejszania kosztów dostarczania kodu JavaScript
Jeśli chcesz, aby czas analizowania/kompilowania i transmisji sieciowej kodu JavaScript był krótki, możesz skorzystać z takich wzorców jak dzielenie na fragmenty na podstawie trasy lub PRPL.
PRPL
PRPL (Push, Render, wstępne wczytywanie, Lazy-load) to wzorzec, który optymalizuje interaktywność dzięki agresywnemu dzieleniu kodu i buforowaniu:

Zobaczmy, jaki może mieć wpływ.
Analizujemy czas wczytywania popularnych witryn mobilnych i progresywnych aplikacji internetowych, korzystając ze statystyk wywołań środowiska wykonawczego V8. Jak widać, czas analizy (zaznaczony na pomarańczowo) stanowi znaczną część czasu, który wiele z tych witryn poświęca na:

Wego, witryna korzystająca z PRPL, utrzymuje krótki czas analizowania tras, dzięki czemu bardzo szybko staje się interaktywna. Wiele innych witryn wymienionych powyżej zastosowało podział kodu i budżety wydajności, aby obniżyć koszty JS.
Stopniowe uruchamianie
Wiele witryn optymalizuje widoczność treści kosztem interaktywności. Aby uzyskać szybkie pierwsze wyrenderowanie, gdy masz duże pakiety JavaScriptu, programiści czasami stosują renderowanie po stronie serwera, a następnie „ulepszają” je, aby dołączyć moduły obsługi zdarzeń, gdy JavaScript zostanie ostatecznie pobrany.
Zachowaj ostrożność – wiąże się to z kosztami. Zazwyczaj 1) wysyłasz większą odpowiedź HTML, która może zwiększyć interaktywność, 2) możesz pozostawić użytkownika w stanie, w którym połowa interfejsu nie może być interaktywna, dopóki JavaScript nie zakończy przetwarzania.
Lepszym rozwiązaniem może być progresywne uruchamianie. Wysyłaj stronę o minimalnej funkcjonalności (składającą się tylko z kodu HTML/JS/CSS potrzebnego do obsługi bieżącej trasy). W miarę pojawiania się kolejnych zasobów aplikacja może leniwie ładować i odblokowywać więcej funkcji.
Wczytywanie kodu proporcjonalnie do tego, co jest widoczne, to święty Graal. Wzorce PRPL i progresywnego uruchamiania mogą pomóc w osiągnięciu tego celu.
Podsumowanie
Rozmiar transmisji ma kluczowe znaczenie w przypadku sieci o niskiej przepustowości. Czas analizy jest ważny w przypadku urządzeń z ograniczeniami procesora. Utrzymanie ich na niskim poziomie ma znaczenie.
Zespoły odniosły sukces, wprowadzając rygorystyczne budżety wydajności, aby utrzymać niskie czasy przesyłania i analizowania/kompilowania kodu JavaScript. Zobacz artykuł Alexa Russella „Can You Afford It?: Real-world Web Performance Budgets" (Budżety wydajności stron internetowych w rzeczywistych warunkach), aby dowiedzieć się więcej o budżetach na urządzenia mobilne.
Jeśli tworzysz witrynę przeznaczoną na urządzenia mobilne, postaraj się, aby działała na reprezentatywnym sprzęcie, utrzymuj krótki czas analizowania i kompilowania kodu JavaScript oraz przyjmij budżet wydajności, aby Twój zespół mógł kontrolować koszty JavaScriptu.
Więcej informacji
- Chrome Dev Summit 2017 – sprawdzone metody nowoczesnego wczytywania
- Wydajność uruchamiania JavaScriptu
- Solving the web performance crisis – Nolan Lawson
- Czy Cię na to stać? Real-world performance budgets – Alex Russell
- Evaluating web frameworks and libraries – Kristofer Baxter
- Wyniki eksperymentów Cloudflare z kompresją Brotli (pamiętaj, że dynamiczna kompresja Brotli o wyższej jakości może opóźnić początkowe renderowanie strony, więc dokładnie to przeanalizuj). Prawdopodobnie chcesz użyć kompresji statycznej).
- Performance Futures – Sam Saccone