Dowiedz się, czym jest skaner wstępnego wczytywania w przeglądarce i w jaki sposób zwiększa wydajność, oraz dowiedz się, jak się przed nim chronić.
Jedną z pominiętych aspektów optymalizacji szybkości strony jest wiedza o funkcjach wewnętrznych przeglądarki. Przeglądarki przeprowadzają pewne optymalizacje poprawiające wydajność w sposób, którego my nie możemy wspomóc dla programistów, ale tylko pod warunkiem, że optymalizacje nie będą nieumyślnie zakłócane.
Jedną z wewnętrznych metod optymalizacji, która pozwala zrozumieć działanie przeglądarek, jest skaner wstępnego wczytywania. Z tego posta dowiesz się, jak działa skaner wstępnego wczytywania i przede wszystkim, jak możesz go uniknąć.
Czym jest skaner wstępnego wczytywania?
Każda przeglądarka ma podstawowy parser HTML, który tokenizuje nieprzetworzone znaczniki i przetwarza je w modelu obiektowym. Wszystko to przebiega płynnie, dopóki parser nie zatrzyma się, gdy znajdzie zasób blokujący, np. arkusz stylów załadowany z elementem <link>
lub skrypt załadowany z elementem <script>
bez atrybutu async
lub defer
.
W przypadku plików CSS zarówno analizowanie, jak i renderowanie jest blokowane, aby zapobiec przebłysku niesformatowanej treści (FOUC), czyli sytuacji, gdy niesformatowana wersja strony może zostać na krótko widoczna przed zastosowaniem stylów.
Przeglądarka blokuje również analizowanie i renderowanie strony, jeśli natrafi na elementy <script>
bez atrybutu defer
lub async
.
Powodem jest to, że przeglądarka nie ma pewności, czy dany skrypt będzie modyfikować DOM, podczas gdy podstawowy parser HTML nadal wykonuje swoje zadanie. Właśnie dlatego tak powszechną praktyką jest ładowanie kodu JavaScript na końcu dokumentu, by skutki zablokowania analizy i renderowania były marginalne.
Z tych powodów przeglądarka powinna blokować zarówno analizowanie, jak i renderowanie. Jednak zablokowanie jednego z tych ważnych kroków jest niepożądane, ponieważ może opóźnić odkrywanie innych ważnych zasobów. Na szczęście przeglądarki starają się rozwiązać te problemy, korzystając z dodatkowego parsera HTML nazywanego skanerem wstępnego wczytywania.
Rola skanera wstępnego ładowania ma charakter spekulacyjny, co oznacza, że analizuje nieprzetworzone znaczniki w celu znalezienia zasobów do pobrania, zanim podstawowy parser HTML wykryje je w innym przypadku.
Jak sprawdzić, czy skaner wstępnego wczytywania działa
Skaner wstępnego wczytywania istnieje ze względu na zablokowane renderowanie i analizę. Gdyby te 2 problemy z wydajnością nie występowały, skaner wstępnego wczytywania nie byłby przydatny. Kryterium tych zjawisk blokujących jest kluczowe dla określenia, czy użycie skanera wstępnego wczytywania jest korzystne dla strony. Aby to zrobić, możesz wprowadzić sztuczne opóźnienie w odpowiedzi na żądania sprawdzenia, gdzie działa skaner wstępnego wczytywania.
Dla przykładu przyjrzyj się tej stronie zawierającej podstawowy tekst i obrazy z arkuszem stylów. Pliki CSS blokują renderowanie i analizę, dlatego wprowadzasz sztuczne opóźnienie arkusza stylów o 2 sekundy za pośrednictwem usługi pośredniczącej. Dzięki temu opóźnienie w kaskadzie sieci jest łatwiejsze, w którym działa skaner wstępnego wczytywania.
Jak widać w kaskadzie, skaner wstępnego wczytywania wykrywa element <img>
, nawet jeśli renderowanie i analiza dokumentów są zablokowane. Bez tej optymalizacji przeglądarka nie jest w stanie pobierać dodatkowych elementów w okresie blokowania, a więcej żądań zasobów byłoby następujących po sobie, a nie równocześnie.
Skoro już omówiliśmy ten przykład z zabawkami, spójrzmy na pewne realistyczne wzorce, w których skaner wstępnie ładowany może się skończyć oraz co można zrobić, by rozwiązać ten problem.
Wstrzyknięto async
skryptu
Załóżmy, że w pliku <head>
masz kod HTML zawierający wbudowany JavaScript, taki jak:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Domyślnie wstrzyknięty skrypt ma wartość async
, więc po wstrzykiwaniu będzie on zachowywać się tak, jakby został do niego zastosowany atrybut async
. Oznacza to, że będzie ona uruchamiana jak najszybciej i nie będzie blokowała renderowania. Brzmi optymalnie, prawda? Jeśli jednak przy założeniu, że ten wbudowany <script>
występuje po elemencie <link>
, który wczytuje zewnętrzny plik CSS, otrzymasz nieoptymalny wynik:
Opiszmy to, co się tutaj wydarzyło:
- W momencie 0 sekund wymagane jest przesłanie dokumentu głównego.
- Po 1, 4 sekundy pojawia się pierwszy bajt żądania nawigacji.
- W 2, 0 sekundy są wysyłane żądania CSS i obraz.
- Parser jest zablokowany wczytywaniu arkusza stylów, a wbudowany kod JavaScript, który wstawia skrypt
async
, pojawia się później od 2,6 sekundy tego arkusza, dlatego funkcje tego skryptu nie są dostępne tak szybko, jak to możliwe.
Nie jest to optymalne, ponieważ żądanie skryptu następuje dopiero po zakończeniu pobierania arkusza stylów. To spowoduje opóźnienie wykonania skryptu tak szybko, jak to możliwe. Element <img>
jest natomiast wykrywalny w znacznikach dostarczanych przez serwer. Dlatego skaner wstępnego wczytywania wykrywa go.
Co się więc stanie, gdy użyjesz zwykłego tagu <script>
z atrybutem async
, a nie wstawić skrypt do DOM?
<script src="/yall.min.js" async></script>
Oto rezultat:
Niektórzy użytkownicy mogą sugerować, że te problemy można rozwiązać za pomocą narzędzia rel=preload
. To oczywiście zadziała, ale może wiązać się z pewnymi skutkami ubocznymi. Po co więc używać rel=preload
do rozwiązania problemu, którego można uniknąć, nie wstrzykiując elementu <script>
do DOM?
Wstępne wczytywanie „poprawek” tutaj pojawia się nowy problem, ale pojawia się nowy problem: skrypt async
w 2 pierwszych wersjach demonstracyjnych (mimo załadowania w <head>
) jest wczytywany „Niski” a arkusz stylów ma wartość „Najwyższa” . W ostatniej wersji demonstracyjnej, w której skrypt async
jest wstępnie wczytywany, arkusz stylów jest nadal wczytywany na poziomie „Najwyższa” ale priorytet skryptu został zwiększony do „Wysoki”.
Gdy priorytet zasobu jest podniesiony, przeglądarka przydziela mu większą przepustowość. Oznacza to, że chociaż arkusz stylów ma najwyższy priorytet, zwiększony priorytet skryptu może powodować rywalizację o przepustowość. Może to powodować powolne połączenia lub bardzo duże zasoby.
Odpowiedź jest prosta: jeśli podczas uruchamiania potrzebny jest skrypt, nie pokonuj skanera wstępnego wczytywania, wstrzykijąc go do DOM. W razie potrzeby poeksperymentuj z umiejscowieniem elementu <script>
oraz takimi atrybutami jak defer
i async
.
Leniwe ładowanie z użyciem JavaScriptu
Leniwe ładowanie to świetna metoda zachowania danych, którą często stosuje się do obrazów. Czasami jednak leniwe ładowanie jest nieprawidłowo stosowane do obrazów w części strony widocznej na ekranie.
Stwarza to potencjalne problemy z wykrywaniem zasobów w przypadku skanera wstępnego ładowania i może niepotrzebnie opóźniać znalezienie odniesienia do obrazu, pobranie go, zdekodowanie i prezentowanie. Weźmy na przykład te znaczniki obrazu:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Prefiks data-
jest często używany przez leniwe ładowanie oparte na języku JavaScript. Gdy obraz znajdzie się w widocznym obszarze, leniwe ładowanie usuwa prefiks data-
, co oznacza, że w poprzednim przykładzie data-src
zmienia się w src
. Ta aktualizacja spowoduje, że przeglądarka pobierze zasób.
Ten wzorzec nie stanowi problemu, dopóki nie zostanie zastosowany do obrazów, które znajdują się w widocznym obszarze podczas uruchamiania. Skaner wstępnego wczytywania nie odczytuje atrybutu data-src
w taki sam sposób jak w atrybutach src
(lub srcset
), dlatego odwołanie do obrazu nie zostało wykryte wcześniej. Co gorsza, obraz może się wczytać dopiero po pobraniu, skompilowaniu i wykonaniu kodu JavaScript przez leniwe ładowanie.
W zależności od rozmiaru obrazu – który może zależeć od rozmiaru widocznego obszaru – może on być elementem kandydującym do największego wyrenderowania treści (LCP). Gdy skaner wstępnego wczytywania nie może spekulacyjnie pobrać zasobu obrazu z wyprzedzeniem – na przykład w momencie, w którym doszło do renderowania blokowego arkuszy stylów strony – na tym korzystnie wpływa ten wskaźnik.
Rozwiązaniem jest zmiana znaczników obrazu:
<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
To optymalny wzorzec dla obrazów, które znajdują się w widocznym obszarze podczas uruchamiania, ponieważ skaner wstępnego wczytywania szybciej wykrywa i pobiera zasób obrazu.
W wyniku tego uproszczonego przykładu mamy do czynienia z poprawką wskaźnika LCP o 100 milisekund w przypadku wolnego połączenia. Może się to wydawać niezwykłe, ale okazuje się, że rozwiązaniem jest szybka poprawka znaczników, a większość stron internetowych jest bardziej złożona niż ten zestaw przykładów. Oznacza to, że kandydaci LCP być może będą musieli konkurować o przepustowość z wieloma innymi zasobami, a takie optymalizacje stają się coraz ważniejsze.
Obrazy tła CSS
Pamiętaj, że skaner wstępnie wczytuje znaczniki w przeglądarce. Nie skanuje innych typów zasobów, takich jak CSS, co może wymagać pobierania obrazów, do których odwołuje się właściwość background-image
.
Podobnie jak HTML, przeglądarki przetwarzają kod CSS na własny model obiektowy, nazywany CSSOM. Jeśli zasoby zewnętrzne zostaną wykryte podczas tworzenia CSSOM, są one żądane w momencie wykrycia, a nie przez skaner wstępnego wczytywania.
Załóżmy, że kandydat LCP na Twojej stronie jest elementem z właściwością CSS background-image
. Oto co dzieje się podczas wczytywania zasobów:
W takim przypadku skaner wstępnego wczytywania nie jest tak zaniedbany, jak żaden. Mimo to, jeśli kandydat LCP na stronie pochodzi z właściwości CSS background-image
, warto wstępnie wczytać ten obraz:
<!-- Make sure this is in the <head> below any
stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">
Ta wskazówka rel=preload
jest krótka, ale pomaga przeglądarce wykryć obraz szybciej niż w innym przypadku:
Wskazówka rel=preload
sprawia, że kandydat LCP jest wykrywany szybciej, co skraca czas LCP. Ta wskazówka pomaga rozwiązać ten problem, ale lepszą opcją może być sprawdzenie, czy kandydat LCP obrazu musi zostać załadowany z CSS. Tag <img>
daje Ci większą kontrolę nad ładowaniem obrazu odpowiedniego do widocznego obszaru, a jednocześnie umożliwia wykrywanie tego przez skaner wstępnie załadowanego.
Wbudowano zbyt wiele zasobów
Wbudowanie to metoda polegająca na umieszczaniu zasobu w kodzie HTML. Możesz wstawiać arkusze stylów w elementach <style>
, skrypty w elementach <script>
, a także praktycznie w dowolnych innych zasobach za pomocą kodowania base64.
Wbudowane zasoby mogą być szybsze niż ich pobieranie, ponieważ dla zasobu nie jest wysyłane osobne żądanie. Znajduje się od razu w dokumencie i od razu się wczytuje. Istnieje jednak kilka istotnych wad:
- Jeśli nie umieszczasz kodu HTML w pamięci podręcznej (i nie możesz tego zrobić, jeśli odpowiedź HTML jest dynamiczna), wbudowane zasoby nigdy nie są zapisywane w pamięci podręcznej. Wpływa to na wydajność, ponieważ wbudowanych zasobów nie nadają się do wielokrotnego użytku.
- Nawet jeśli możesz zapisać kod HTML w pamięci podręcznej, zasoby wbudowane nie są współdzielone między dokumentami. Zmniejsza to wydajność buforowania w porównaniu z plikami zewnętrznymi, które można przechowywać w pamięci podręcznej i ponownie wykorzystywać w całym punkcie początkowym.
- Jeśli w tekście jest za dużo treści, skaner wstępnego wczytywania nie wykrywa zasobów w dalszej części dokumentu, ponieważ pobieranie tych treści trwa dłużej.
Dla przykładu przyjrzyj się tej stronie. W pewnych warunkach kandydat LCP jest obrazem na górze strony, a plik CSS znajduje się w osobnym pliku wczytywanym przez element <link>
. Strona korzysta również z czterech czcionek internetowych, które są żądane jako osobne pliki z zasobem CSS.
Co się teraz stanie, jeśli CSS i wszystkie czcionki będą wstawiane jako zasoby base64?
W tym przykładzie wpływ wbudowywania ma negatywne konsekwencje dla LCP, a także na ogólnie wyniki. Wersja strony, która nie ma w tekście żadnego elementu wbudowanego, obrazuje obraz LCP w ciągu ok.3,5 sekundy. Strona, na której znajdziesz wszystkie elementy, nie maluje obrazu LCP przed ponad 7 sekundami.
Nie tylko skaner wstępnego wczytywania. Wbudowanie czcionek nie jest dobrym rozwiązaniem, ponieważ base64 nie jest wydajnym formatem dla zasobów binarnych. Innym czynnikiem jest to, że zewnętrzne zasoby czcionek nie są pobierane, jeśli nie zostaną uznane za konieczne przez CSSOM. Czcionki te są pobierane w standardzie base64 i niezależnie od tego, czy są potrzebne na bieżącej stronie.
Czy wstępne załadowanie może coś tu poprawić? Jasne. Możesz wstępnie wczytać obraz LCP i skrócić czas LCP, ale przesłanie kodu HTML, którego nie można zapisać w pamięci podręcznej, za pomocą wbudowanych zasobów ma inne negatywne konsekwencje związane z wydajnością. Ten wzorzec ma również wpływ na pierwsze wyrenderowanie treści (FCP). W wersji strony, na której nic nie znajduje się w tekście, wskaźnik FCP wynosi około 2,7 sekundy. W wersji, w której wszystko jest w tekście, FCP wynosi około 5,8 sekundy.
Zachowaj szczególną ostrożność przy wbudowywaniu treści do kodu HTML, zwłaszcza w przypadku zasobów zakodowanych w formacie base64. Ogólnie nie jest to zalecane z wyjątkiem bardzo małych zasobów. Wbudowane jak najmniej, ponieważ za dużo w treści to ingerencje w świat ognia.
Renderowanie znaczników za pomocą kodu JavaScript po stronie klienta
Nie ma co do tego wątpliwości: JavaScript ma zdecydowanie wpływ na szybkość działania strony. Deweloperzy nie tylko polegają na niej, aby zapewnić interaktywność, ale też na samo dostarczanie treści. Pod wieloma względami zapewnia to deweloperom lepsze wrażenia. ale korzyści dla programistów nie zawsze przekładają się na korzyści dla użytkowników.
Jednym z wzorców, który może pokonać skaner wstępnego wczytywania, jest renderowanie znaczników za pomocą JavaScriptu po stronie klienta:
Gdy ładunki znaczników są w przeglądarce zawarte i w pełni renderowane przez JavaScript, wszelkie zawarte w nich zasoby są w praktyce niewidoczne dla skanera wstępnego wczytywania. Opóźnia to wykrywanie ważnych zasobów, co z pewnością wpływa na LCP. W tych przykładach żądanie obrazu LCP jest znacznie opóźnione w porównaniu z odpowiednim renderowaniem przez serwer, które nie wymaga JavaScriptu.
Ten artykuł odbiega od tematu tego artykułu, ale wpływ renderowania znaczników na klienta jest znacznie bardziej skomplikowany niż skaner wstępnego wczytywania. Po pierwsze, wprowadzenie JavaScriptu w celu zwiększenia wydajności, które nie wymaga więcej czasu, wiąże się z wprowadzaniem niepotrzebnego czasu przetwarzania, który może mieć wpływ na interakcję z następnym wyrenderowaniem (INP). Renderowanie bardzo dużych ilości znaczników na kliencie daje większe prawdopodobieństwo wygenerowania długich zadań niż tyle samo znaczników wysyłanych przez serwer. Powodem, oprócz dodatkowego przetwarzania, który wymaga JavaScript, jest to, że przeglądarki przesyłają strumieniowo znaczniki z serwera i dzielą renderowanie w sposób, który zazwyczaj ogranicza długie zadania. Znaczniki renderowane przez klienta są natomiast obsługiwane jako pojedyncze, monolityczne zadanie, które może wpływać na wartość INP strony.
Rozwiązanie tego problemu zależy od odpowiedzi na to pytanie: Czy istnieje powód, dla którego serwer nie może zapewnić znaczników strony zamiast renderowania na kliencie? Jeśli odpowiedź brzmi „nie”, w miarę możliwości warto wziąć pod uwagę renderowanie po stronie serwera (SSR) lub statycznie wygenerowane znaczniki. Pomoże to skanerowi wstępnego wczytywania i oszczędnego pobrania ważnych zasobów z wyprzedzeniem.
Jeśli Twoja strona wymaga JavaScriptu, by dodać funkcje do niektórych części znaczników strony, nadal możesz to zrobić za pomocą SSR – za pomocą JavaScriptu waniliowego lub hydratacji – pozwoli to wykorzystać oba te elementy.
Pomóż skanerowi wstępnego wczytywania
Skaner wstępnego wczytywania to bardzo skuteczna optymalizacja przeglądarki, która przyspiesza wczytywanie stron podczas uruchamiania. Unikając wzorców, które uniemożliwiałyby poznanie ważnych zasobów z wyprzedzeniem, nie tylko ułatwiasz sobie programowanie, ale także zapewniasz użytkownikom lepsze wrażenia, co przekłada się na lepsze wyniki pod względem wielu rodzajów danych, w tym niektórych wskaźników internetowych.
Oto czego należy się z niego dowiedzieć:
- Skaner wstępnego wczytywania przeglądarki to dodatkowy parser HTML, który skanuje przed nadrzędnym, jeśli jest zablokowany, aby umożliwić wyszukiwanie zasobów, które może pobrać wcześniej.
- Skaner wstępnego wczytywania nie wykrywa zasobów, których nie ma w znacznikach podanych przez serwer w pierwszym żądaniu nawigacji. Skaner wstępnego załadowania może być ukryty między innymi z następujących powodów:
- Wstrzykiwanie do DOM za pomocą JavaScriptu zasobów, np. skryptów, obrazów, arkuszy stylów lub innych elementów, które lepiej sprawdzi się w początkowym ładunku znaczników z serwera.
- Leniwe ładowanie obrazów w części strony widocznej na ekranie lub elementów iframe za pomocą rozwiązania JavaScript.
- Znaczniki renderowania po stronie klienta, które mogą zawierać odwołania do zasobów podrzędnych dokumentu za pomocą JavaScriptu.
- Skaner wstępnego wczytywania skanuje tylko kod HTML. Nie analizuje zawartości innych zasobów – w szczególności CSS – które mogą zawierać odwołania do ważnych zasobów, w tym kandydatów LCP.
Jeśli z jakiegokolwiek powodu nie możesz uniknąć wzorca, który negatywnie wpływa na zdolność skanera wstępnego wczytywania, zastosuj wskazówkę dotyczącą zasobów rel=preload
. Jeśli używasz metody rel=preload
, przetestuj ją w narzędziach laboratoryjnych, aby upewnić się, że przynosi pożądany efekt. Nie ładuj wstępnie zbyt wielu zasobów, bo jeśli ustalisz priorytety dla wszystkich, nic się nie stanie.
Zasoby
- „Skrypty niesynchroniczne” wstawiane przez skrypt uznawane za szkodliwe
- Jak moduł wstępnego ładowania przeglądarki przyspiesza wczytywanie stron
- Wstępnie wczytuj najważniejsze zasoby, aby przyspieszyć wczytywanie
- Nawiąż połączenie sieciowe, aby zwiększyć postrzeganą szybkość działania stron
- Optymalizowanie największego wyrenderowania treści
Baner powitalny z filmu Unsplash, autor: Mohammad Rahmani .