Optymalizacja wczytywania zasobów

W poprzednim module omówiliśmy teorię dotyczącą ścieżki krytycznego renderowania oraz to, jak zasoby blokujące renderowanie i parsowanie mogą opóźniać początkowe renderowanie strony. Teraz, gdy znasz już teorię, możesz poznać techniki optymalizacji krytycznej ścieżki renderowania.

Podczas wczytywania strony w jej kodzie HTML pojawia się wiele odwołań do zasobów, które nadają stronie wygląd i układ za pomocą CSS, a także interaktywność za pomocą JavaScriptu. W tym module omówimy kilka ważnych pojęć związanych z tymi zasobami i ich wpływem na czas wczytywania strony.

Blokowanie renderowania

Jak wspomnieliśmy w poprzednim module, CSS to zasób blokujący renderowanie, ponieważ uniemożliwia przeglądarce renderowanie treści, dopóki nie zostanie utworzony model obiektowy CSS (CSSOM). Przeglądarka blokuje renderowanie, aby zapobiec błyskowi nieostylowanych treści (FOUC), który jest niepożądany z punktu widzenia użytkownika.

W poprzednim filmie widać krótki moment FOUC, w którym strona jest wyświetlana bez żadnych stylów. Następnie wszystkie style są stosowane po zakończeniu wczytywania kodu CSS strony z sieci, a wersja strony bez stylów jest natychmiast zastępowana wersją ze stylami.

Zazwyczaj nie widzisz FOUC, ale warto zrozumieć to pojęcie, aby wiedzieć, dlaczego przeglądarka blokuje renderowanie strony, dopóki nie pobierze i nie zastosuje arkusza CSS. Blokowanie renderowania nie jest koniecznie niepożądane, ale warto zminimalizować czas jego trwania, optymalizując pliki CSS.

Blokowanie parsera

Zasób blokujący analizator przerywa działanie analizatora HTML, np. element <script> bez atrybutów async lub defer. Gdy parser napotka element <script>, przeglądarka musi ocenić i wykonać skrypt, zanim przejdzie do analizowania pozostałej części kodu HTML. Jest to celowe działanie, ponieważ skrypty mogą modyfikować lub uzyskiwać dostęp do DOM w czasie, gdy jest on jeszcze tworzony.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

W przypadku korzystania z zewnętrznych plików JavaScript (bez async ani defer) parser jest blokowany od momentu wykrycia pliku do momentu jego pobrania, przeanalizowania i wykonania. W przypadku używania wbudowanego JavaScriptu parser jest podobnie blokowany, dopóki wbudowany skrypt nie zostanie przeanalizowany i wykonany.

Skaner wstępnego wczytywania

Skaner wstępnego wczytywania to optymalizacja przeglądarki w postaci dodatkowego analizatora HTML, który skanuje surową odpowiedź HTML, aby znaleźć i spekulatywnie pobrać zasoby, zanim odkryje je główny analizator HTML. Na przykład skaner wstępnego wczytywania umożliwia przeglądarce rozpoczęcie pobierania zasobu określonego w elemencie <img>, nawet gdy parser HTML jest zablokowany podczas pobierania i przetwarzania zasobów, takich jak CSS i JavaScript.

Aby skorzystać ze skanera wstępnego ładowania, ważne zasoby powinny być uwzględnione w znacznikach HTML wysyłanych przez serwer. Skaner wstępnego wczytywania nie wykrywa tych wzorców wczytywania zasobów:

  • Obrazy wczytywane przez CSS za pomocą właściwości background-image. Te odwołania do obrazów znajdują się w CSS i nie mogą zostać wykryte przez skaner wstępnego wczytywania.
  • Skrypty ładowane dynamicznie w postaci znaczników elementu <script> wstawianych do DOM za pomocą JavaScriptu lub modułów ładowanych za pomocą dynamicznego import().
  • HTML renderowany po stronie klienta za pomocą JavaScriptu. Takie znaczniki znajdują się w ciągach w zasobach JavaScript i nie są wykrywane przez skaner wstępnego wczytywania.
  • deklaracje @import usługi porównywania cen.

Wszystkie te wzorce ładowania zasobów są zasobami wykrytymi z opóźnieniem, dlatego nie korzystają ze skanera wstępnego ładowania. W miarę możliwości unikaj ich. Jeśli jednak nie można uniknąć takich wzorców, możesz użyć wskazówki preload, aby uniknąć opóźnień w odkrywaniu zasobów.

CSS

CSS określa prezentację i układ strony. Jak wspomnieliśmy wcześniej, CSS jest zasobem blokującym renderowanie, więc optymalizacja CSS może mieć znaczący wpływ na ogólny czas wczytywania strony.

Minifikacja

Minifikacja plików CSS zmniejsza rozmiar pliku zasobu CSS, dzięki czemu można go szybciej pobrać. Osiąga się to głównie przez usunięcie z pliku CSS źródłowego treści takich jak spacje i inne niewidoczne znaki oraz zapisanie wyniku w nowym, zoptymalizowanym pliku:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

W najprostszej postaci minifikacja CSS to skuteczna optymalizacja, która może poprawić FCP witryny, a w niektórych przypadkach nawet LCP. Narzędzia takie jak bundlery mogą automatycznie przeprowadzać tę optymalizację w wersjach produkcyjnych.

Usuwanie nieużywanego kodu CSS

Zanim przeglądarka wyrenderuje jakiekolwiek treści, musi pobrać i przeanalizować wszystkie arkusze stylów. Czas potrzebny na zakończenie analizowania obejmuje też style, które nie są używane na bieżącej stronie. Jeśli używasz narzędzia do łączenia, które łączy wszystkie zasoby CSS w jeden plik, użytkownicy prawdopodobnie pobierają więcej CSS, niż jest to potrzebne do renderowania bieżącej strony.

Aby wykryć nieużywany kod CSS na bieżącej stronie, użyj narzędzia Pokrycie w Narzędziach deweloperskich Chrome.

Zrzut ekranu narzędzia do sprawdzania pokrycia w Narzędziach deweloperskich w Chrome. W dolnym panelu jest wybrany plik CSS, który zawiera znaczną ilość kodu CSS nieużywanego przez bieżący układ strony.
Narzędzie do sprawdzania zasięgu w Narzędziach deweloperskich w Chrome przydaje się do wykrywania kodu CSS (i JavaScriptu) nieużywanego przez bieżącą stronę. Można go używać do dzielenia plików CSS na wiele zasobów, które będą wczytywane przez różne strony. Dzięki temu nie trzeba przesyłać znacznie większego pakietu CSS, który może opóźniać renderowanie strony.

Usuwanie nieużywanego kodu CSS ma podwójny efekt: oprócz skrócenia czasu pobierania optymalizujesz tworzenie drzewa renderowania, ponieważ przeglądarka musi przetwarzać mniej reguł CSS.

Unikaj deklaracji CSS @import

Chociaż może się to wydawać wygodne, unikaj deklaracji @import w CSS:

/* Don't do this: */
@import url('style.css');

Podobnie jak element <link> w HTML, deklaracja @import w CSS umożliwia importowanie zewnętrznego zasobu CSS z arkusza stylów. Główna różnica między tymi dwoma podejściami polega na tym, że element HTML <link> jest częścią odpowiedzi HTML, a więc jest wykrywany znacznie wcześniej niż plik CSS pobrany przez deklarację @import.

Dzieje się tak, ponieważ aby można było wykryć deklarację @import, plik CSS, który ją zawiera, musi zostać najpierw pobrany. W rezultacie powstaje tzw. łańcuch żądań, który w przypadku CSS opóźnia czas początkowego renderowania strony. Kolejną wadą jest to, że arkusze stylów wczytywane za pomocą deklaracji @import nie mogą być wykrywane przez skaner wstępnego wczytywania, a tym samym stają się zasobami blokującymi renderowanie, które są wykrywane z opóźnieniem.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

W większości przypadków możesz zastąpić element @import elementem <link rel="stylesheet">. Elementy <link> umożliwiają pobieranie arkuszy stylów równocześnie i skracają ogólny czas wczytywania w przeciwieństwie do deklaracji @import, które pobierają arkusze stylów kolejno.

Wbudowany krytyczny CSS

Czas pobierania plików CSS może zwiększyć FCP strony. Wstawianie krytycznych stylów w dokumencie <head> eliminuje żądanie sieciowe dotyczące zasobu CSS i – jeśli jest prawidłowo wykonane – może skrócić czas początkowego ładowania, gdy pamięć podręczna przeglądarki użytkownika nie jest jeszcze wypełniona. Pozostałe CSS można wczytać asynchronicznie lub dołączyć na końcu elementu <body>.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

Z drugiej strony wstawianie dużej ilości kodu CSS zwiększa liczbę bajtów w początkowej odpowiedzi HTML. Zasoby HTML często nie mogą być przechowywane w pamięci podręcznej przez długi czas lub wcale, co oznacza, że wbudowany kod CSS nie jest przechowywany w pamięci podręcznej w przypadku kolejnych stron, które mogą używać tego samego kodu CSS w zewnętrznych arkuszach stylów. Testuj i mierz wydajność strony, aby mieć pewność, że kompromisy są warte wysiłku.

Dema CSS

JavaScript

JavaScript odpowiada za większość interaktywności w internecie, ale ma swoją cenę. Wysyłanie zbyt dużej ilości kodu JavaScript może spowolnić działanie strony internetowej podczas wczytywania, a nawet powodować problemy z reaktywnością, które spowalniają interakcje. Oba te problemy mogą być frustrujące dla użytkowników.

Blokowanie renderowania JavaScript

Podczas ładowania elementów <script> bez atrybutów defer lub async przeglądarka blokuje analizowanie i renderowanie, dopóki skrypt nie zostanie pobrany, przeanalizowany i wykonany. Podobnie skrypty wbudowane blokują parser do momentu, aż skrypt zostanie przeanalizowany i wykonany.

asyncdefer

asyncdefer umożliwiają wczytywanie skryptów zewnętrznych bez blokowania analizatora HTML, a skrypty (w tym skrypty wbudowane) z atrybutem type="module" są automatycznie odraczane. Jednak asyncdefer różnią się od siebie w kilku ważnych aspektach.

Ilustracja różnych mechanizmów wczytywania skryptów, z których każdy szczegółowo opisuje role analizatora, pobierania i wykonywania na podstawie różnych używanych atrybutów, takich jak async, defer, type=&#39;module&#39; i kombinacja wszystkich trzech.
Źródło: https://html.spec.whatwg.org/multipage/scripting.html

Skrypty wczytywane za pomocą async są analizowane i wykonywane natychmiast po pobraniu, a skrypty wczytywane za pomocą defer są wykonywane po zakończeniu analizowania dokumentu HTML – dzieje się to w tym samym czasie co zdarzenie DOMContentLoaded w przeglądarce. Dodatkowo skrypty async mogą być wykonywane w innej kolejności, a skrypty defer są wykonywane w kolejności, w jakiej występują w znacznikach.

Renderowanie po stronie klienta

Zasadniczo należy unikać używania JavaScriptu do renderowania krytycznych treści lub elementu LCP strony. Jest to tzw. renderowanie po stronie klienta, czyli technika szeroko stosowana w aplikacjach jednostronicowych.

Znaczniki renderowane przez JavaScript omijają skaner wstępnego wczytywania, ponieważ zawarte w nich zasoby nie są wykrywane przez ten skaner. Może to opóźnić pobieranie kluczowych zasobów, takich jak obraz LCP. Przeglądarka rozpoczyna pobieranie obrazu LCP dopiero po wykonaniu skryptu i dodaniu elementu do DOM. Z kolei skrypt można wykonać dopiero po jego wykryciu, pobraniu i przeanalizowaniu. Jest to tzw. łańcuch żądań krytycznych, którego należy unikać.

Dodatkowo renderowanie znaczników za pomocą JavaScriptu częściej generuje długie zadania niż znaczniki pobrane z serwera w odpowiedzi na żądanie nawigacji. Intensywne korzystanie z renderowania HTML po stronie klienta może negatywnie wpłynąć na opóźnienie interakcji. Jest to szczególnie ważne w przypadku, gdy DOM strony jest bardzo duży, co powoduje znaczne obciążenie renderowaniem, gdy JavaScript modyfikuje DOM.

Minifikacja

Podobnie jak w przypadku CSS minifikacja JavaScriptu zmniejsza rozmiar pliku zasobu skryptu. Może to przyspieszyć pobieranie, dzięki czemu przeglądarka będzie mogła szybciej przejść do analizowania i kompilowania JavaScriptu.

Minifikacja JavaScriptu jest bardziej zaawansowana niż minifikacja innych zasobów, takich jak CSS. Podczas minifikacji JavaScriptu usuwane są nie tylko spacje, tabulatory i komentarze, ale też skracane są symbole w źródłowym JavaScript. Ten proces jest czasem nazywany uglification. Aby zobaczyć różnicę, weź pod uwagę ten kod źródłowy JavaScriptu:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

Po zminimalizowaniu powyższego kodu źródłowego JavaScriptu wynik może wyglądać podobnie do tego fragmentu kodu:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

W powyższym fragmencie kodu widać, że zmienna czytelna dla człowieka scriptElement w źródle została skrócona do t. W przypadku zastosowania tej metody w dużej kolekcji skryptów oszczędności mogą być znaczne, bez wpływu na funkcje, jakie zapewnia produkcyjny kod JavaScript witryny.

Jeśli do przetwarzania kodu źródłowego witryny używasz narzędzia do łączenia plików, w przypadku kompilacji produkcyjnych proces ten jest często wykonywany automatycznie. Uglifiery, takie jak np. Terser, są też wysoce konfigurowalne, co pozwala dostosować agresywność algorytmu do maksymalnych oszczędności. Jednak domyślne ustawienia każdego narzędzia do zaciemniania kodu są zwykle wystarczające, aby osiągnąć odpowiednią równowagę między rozmiarem danych wyjściowych a zachowaniem możliwości.

Dema JavaScript

Sprawdź swoją wiedzę

Jaki jest najlepszy sposób wczytywania wielu plików CSS w przeglądarce?

Wiele elementów <link>.
Deklaracja CSS @import.

Do czego służy skaner wstępnego załadowania przeglądarki?

Wykrywa elementy <link rel="preload"> w zasobie HTML.
Jest to dodatkowy parser HTML, który analizuje surowy kod, aby odkrywać zasoby, zanim zrobi to parser DOM, dzięki czemu można je szybciej wykrywać.

Dlaczego przeglądarka domyślnie tymczasowo blokuje analizowanie kodu HTML podczas pobierania zasobów JavaScript?

Dzieje się tak, ponieważ skrypty mogą modyfikować model DOM lub uzyskiwać do niego dostęp.
Aby zapobiec błyskowi nieostylowanych treści (FOUC).
Ponieważ ocena kodu JavaScript jest bardzo wymagającym zadaniem dla procesora, a wstrzymanie analizowania kodu HTML daje procesorowi więcej przepustowości na dokończenie wczytywania skryptów.

Następny temat: Pomoc dla przeglądarki za pomocą wskazówek dotyczących zasobów

Teraz, gdy wiesz już, jak zasoby wczytywane w elemencie <head> mogą wpływać na początkowe wczytywanie strony i różne dane, możesz przejść dalej. W następnym module omówimy wskazówki dotyczące zasobów i sposoby, w jakie mogą one dostarczać przeglądarce cennych informacji, aby szybciej rozpoczynała wczytywanie zasobów i otwieranie połączeń z serwerami z innych domen.