Wstępnie wczytuj moduły

Sérgio Gomes

Data publikacji: 23 listopada 2024 r.

Programowanie w ramach modułów ma wiele zalet związanych z możliwością wykorzystania pamięci podręcznej, co pomaga zmniejszyć liczbę bajtów, które trzeba przesłać do użytkowników. Większa szczegółowość kodu ułatwia też wczytywanie, ponieważ pozwala określić priorytety kodu krytycznego w aplikacji.

Jednak zależności modułów powodują problemy z wczytywaniem, ponieważ przeglądarka musi poczekać na załadowanie modułu, zanim dowie się, jakie są jego zależności. Jednym ze sposobów na obejście tego problemu jest wstępne załadowanie zależności, aby przeglądarka znała wszystkie pliki z wyprzedzeniem i mogła utrzymać połączenie.

<link rel="preload"> to sposób na deklarowanie zasobów z wyprzedzeniem, zanim przeglądarka ich potrzebuje.

<head>
  <link rel="preload" as="style" href="critical-styles.css">
  <link rel="preload" as="font" crossorigin type="font/woff2" href="myfont.woff2">
</head>

Jest to szczególnie przydatne w przypadku zasobów takich jak czcionki, które są często ukryte w plikach CSS, czasem na kilku poziomach. W takim przypadku przeglądarka musiałaby czekać na wiele wymian, zanim dowie się, że musi pobrać duży plik czcionki, podczas gdy mogłaby wykorzystać ten czas na rozpoczęcie pobierania i wykorzystanie pełnej przepustowości połączenia.

<link rel="preload"> i jego odpowiednik w nagłówku HTTP zapewniają prostą, deklaratywną metodę informowania przeglądarki o istotnych plikach, które będą potrzebne w ramach bieżącej nawigacji. Gdy przeglądarka wykryje wstępny odczyt, rozpoczyna pobieranie zasobu z wysokim priorytetem, dzięki czemu w momencie, gdy zasobów będzie potrzebnych, będą one już pobrane lub będą w części dostępne. Nie dotyczy to jednak modułów.

Tutaj sprawy się komplikują. Istnieją różne tryby danych logowania w przypadku zasobów. Aby uzyskać trafienie w pamięci podręcznej, muszą one być zgodne. W przeciwnym razie zasób zostanie pobrany dwukrotnie. Nie trzeba chyba nikomu tłumaczyć, że podwójne pobieranie jest niekorzystne, ponieważ marnuje przepustowość użytkownika i zmusza go do dłuższego oczekiwania bez żadnego powodu.

W przypadku tagów <script> i <link> możesz ustawić tryb danych uwierzytelniających za pomocą atrybutu crossorigin. Okazuje się jednak, że element <script type="module"> bez atrybutu crossorigin wskazuje tryb danych logowania omit, który nie istnieje w przypadku <link rel="preload">. Oznacza to, że musisz zmienić atrybut crossorigin w elementach <script><link> na jedną z innych wartości. Może się okazać, że nie będzie to łatwe, jeśli element, który próbujesz wstępnie wczytać, jest zależny od innych modułów.

Pobieranie pliku to dopiero pierwszy krok do jego uruchomienia. Najpierw musi go przeanalizować i skompilować. Najlepiej zrobić to z wyprzedzeniem, aby w momencie, gdy moduł będzie potrzebny, kod był gotowy do uruchomienia. Jednak V8 (mechanizm JavaScriptu w Chrome) analizuje i kompiluje moduły inaczej niż inne JavaScript. <link rel="preload"> nie zapewnia żadnego sposobu na wskazanie, że wczytywany plik jest modułem, więc przeglądarka może tylko załadować plik i umieścić go w pamięci podręcznej. Gdy skrypt zostanie załadowany za pomocą tagu <script type="module"> (lub zostanie załadowany przez inny moduł), przeglądarka przeanalizuje i skompiluje kod jako moduł JavaScript.

Krótko mówiąc, tak. Dzięki temu, że mamy określony typ link do wczytywania modułów w tle, możemy pisać prosty kod HTML bez obaw o to, z jakiego trybu poświadczeń korzystamy. Domyślne wartości działają bez zarzutu.

<head>
  <link rel="modulepreload" href="super-critical-stuff.mjs">
</head>
[...]
<script type="module" src="super-critical-stuff.mjs">

Ponieważ przeglądarka wie, że wczytujesz moduł, może go przeanalizować i skompilować, gdy tylko zakończy pobieranie, zamiast czekać, aż spróbuje go uruchomić.

Obsługa przeglądarek

  • Chrome: 66.
  • Edge: ≤79.
  • Firefox: 115.
  • Safari: 17.

Źródło

A co z zależnościami modułów?

Ciekawe, że o to pytasz. W tym artykule nie omówiliśmy jednego tematu: rekurencji.

Specyfikacja <link rel="modulepreload"> umożliwia opcjonalne wczytywanie nie tylko żądanego modułu, ale też całego drzewa zależności. Przeglądarki nie muszą tego robić, ale mogą.

Jakie rozwiązanie umożliwiające wstępne wczytywanie modułu i drzewa zależności, które działa w różnych przeglądarkach, będzie najlepsze, skoro do uruchomienia aplikacji potrzebne jest pełne drzewo zależności?

Przeglądarki, które wybierają rekurencyjne wstępne wczytywanie zależności, powinny mieć solidną funkcję deduplikacji modułów. Dlatego ogólnie zalecamy deklarowanie modułu i listy płaskiej jego zależności oraz zaufanie przeglądarce, że nie pobierze ona tego samego modułu dwa razy.

<head>
  <!-- dog.js imports dog-head.js, which in turn imports
       dog-head-mouth.js, which imports dog-head-mouth-tongue.js. -->
  <link rel="modulepreload" href="dog-head-mouth-tongue.mjs">
  <link rel="modulepreload" href="dog-head-mouth.mjs">
  <link rel="modulepreload" href="dog-head.mjs">
  <link rel="modulepreload" href="dog.mjs">
</head>

Czy wstępne wczytywanie modułów poprawia wydajność?

W przypadku wstępnego wczytywania przeglądarka wie, co ma pobrać, dzięki czemu może zmaksymalizować wykorzystanie przepustowości. Dzięki temu nie będzie bezczynnie czekać na długie wczytywanie. Jeśli eksperymentujesz z modułami i masz problemy z wydajnością z powodu rozbudowanych drzew zależności, utworzenie płaskiej listy wstępnie załadowanych zasobów może Ci pomóc.

Nadal jednak pracujemy nad wydajnością modułów, więc dokładnie sprawdź, co dzieje się w aplikacji za pomocą Narzędzi dla deweloperów. Warto też w tym czasie podzielić aplikację na kilka części. W Chrome jest jednak wiele innych modułów, nad którymi pracujemy, więc zbliżamy się do momentu, w którym będziemy mogli dać twórcom pakietów zasłużoną przerwę.