Szybsze wczytywanie stron Next.js i Gatsby dzięki szczegółowemu podziałowi na segmenty

Nowsza strategia podziału pakietów internetowych na fragmenty w Next.js i Gatsby ogranicza liczbę powielania kodu i zwiększa wydajność wczytywania strony.

Chrome współpracuje z narzędziami w ekosystemie open source JavaScriptu. W ostatnim czasie wprowadziliśmy kilka nowszych optymalizacji dodano, by poprawić wydajność wczytywania Next.js oraz Gatsby W tym artykule omawiamy udoskonaloną strategię dzielenia na fragmenty który jest teraz domyślnie dostarczany w obu tych usługach.

Wprowadzenie

Podobnie jak wiele innych platform internetowych, Next.js i Gatsby korzystają z pakietu webpack . Wprowadzenie pakietu webpack v3 CommonsChunkPlugin, aby umożliwić moduły wyjściowe współdzielone między różnymi punktami wejścia w jednym (lub kilku) „wspólnych”, fragment (lub fragmenty). Współdzielony kod można pobrać oddzielnie i zapisać w pamięci podręcznej przeglądarki, dzięki czemu będzie można poprawia wydajność wczytywania.

Ten wzorzec stał się popularny, gdy na wielu platformach aplikacji jednostronicowych zastosowano punkt początkowy konfiguracja pakietu wyglądała tak:

Wspólna konfiguracja punktu wejścia i pakietu

Chociaż jest to praktyczne, koncepcja łączenia całego udostępnionego kodu modułu w jeden fragment ma swoje i ograniczeniach. Moduły nieudostępniane w każdym punkcie wejścia można pobrać dla tras, które z niego nie korzystają co powoduje pobieranie za dużo kodu. Na przykład: gdy wczytuje się page1 we fragmencie common, wczytuje kod dla moduleC, mimo że page1 nie używa moduleC. Z tego powodu (wraz z kilkoma innymi) pakiet internetowy w wersji 4 usunął wtyczkę na rzecz jeden: SplitChunksPlugin.

Ulepszone dzielenie na fragmenty

Domyślne ustawienia funkcji SplitChunksPlugin są odpowiednie dla większości użytkowników. Wiele podzielonych fragmentów jest tworzone w zależności od kilku warunków aby zapobiec pobieraniu zduplikowanego kodu na wielu trasach.

Jednak wiele platform internetowych, które korzystają z tej wtyczki, nadal korzysta z kodu „single-commons”. podejście do fragmentu dzielone na dwie części. Next.js wygenerowałby na przykład pakiet commons zawierający dowolny moduł, używane w ponad 50% stron i we wszystkich zależnościach od platformy (react, react-dom itd.).

const splitChunksConfigs = {
  …
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

Mimo że umieszczenie we wspólnym fragmencie kodu zależnego od platformy oznacza, że można go pobrać w pamięci podręcznej dla dowolnego punktu wejścia, opartą na wykorzystaniu heurystykę uwzględniania typowych modułów używanych w więcej niż połowa stron nie jest zbyt skuteczna. Zmiana tego współczynnika może przynieść tylko jeden z dwóch rezultatów:

  • Jeśli zmniejszysz współczynnik proporcji, zostanie pobrany więcej niepotrzebnego kodu.
  • Jeśli zwiększysz ten współczynnik, więcej kodu zostanie powielone na wielu trasach.

Aby rozwiązać ten problem, zespół Next.js wdrożył inny w wersji SplitChunksPlugin, która zmniejsza zbędny kod dla żadnej trasy.

  • Każdy wystarczająco duży moduł firmy zewnętrznej (większy niż 160 KB) jest dzielony na osobne moduły fragment
  • Tworzony jest oddzielny fragment frameworks dla zależności platformy (react, react-dom i i tak dalej).
  • Utworzono tyle udostępnianych fragmentów, ile jest potrzebnych (maksymalnie 25)
  • Minimalny rozmiar generowanego fragmentu zmienia się na 20 KB

Ta strategia szczegółowego dzielenia na fragmenty przynosi takie korzyści:

  • Czas wczytywania stron wydłużył się. Jeśli zamiast jednego wysyłam kilka wspólnych fragmentów, minimalizuje ilość zbędnego (lub zduplikowanego) kodu dla każdego punktu wejścia.
  • Poprawione buforowanie podczas nawigacji. Dzielenie dużych bibliotek i zależności od platform na oddzielne fragmenty zmniejsza prawdopodobieństwo unieważnienia pamięci podręcznej, ponieważ jest mało prawdopodobne, zmian do momentu przeprowadzenia uaktualnienia.

Możesz zobaczyć całą konfigurację wdrożoną przez Next.js w webpack-config.ts.

Więcej żądań HTTP

Organizacja SplitChunksPlugin określiła podstawę szczegółowego dzielenia na fragmenty i stosuje to podejście do platforma taka jak Next.js nie była zupełnie nową koncepcją. Wiele platform w dalszym ciągu za pomocą jednej heurystyki i typu „commons” strategii dotyczących pakietów z kilku powodów. Obejmuje to obawy, które znacznie więcej żądań HTTP może negatywnie wpłynąć na wydajność witryny.

Przeglądarki mogą otworzyć ograniczoną liczbę połączeń TCP z jednym punktem początkowym (6 w przypadku Chrome), więc zminimalizowanie liczby fragmentów generowanych przez narzędzie tworzące pakiet może sprawić, że całkowita liczba żądań nie przekracza tego progu. Dotyczy to jednak tylko protokołu HTTP/1.1. Multipleks w HTTP/2 umożliwia równoległe przesyłanie wielu żądań przez jedno połączenie pochodzeniu danych. Innymi słowy, nie musimy się martwić o ograniczenie liczby fragmentów. nasze usługi tworzące pakiet.

Wszystkie popularne przeglądarki obsługują HTTP/2. Zespoły Chrome i Next.js Postanowiłem sprawdzić, czy zwiększy to liczbę żądań, dzieląc pojedyncze zasoby „commons” firmy Next.js. pakiet na wiele współdzielonych fragmentów w żaden sposób wpłynie na wydajność wczytywania. Zaczęła od pomiaru wydajności pojedynczej witryny przy jednoczesnym modyfikowaniu maksymalnej liczby równoległych żądań za pomocą funkcji maxInitialRequests usłudze.

Wydajność wczytywania strony przy zwiększonej liczbie żądań

Średnio trzy uruchomienia wielu prób na jednej stronie internetowej load start-render i Pierwsze wyrenderowanie treści pozostały mniej więcej takie same przy różnicowaniu maksymalnej wartości początkowej liczby żądań (od 5 do 15). Co ciekawe, zaobserwowaliśmy tylko niewielki wzrost skuteczności po agresywnym podzieleniu ich na setki żądań.

Szybkość wczytywania strony przy setkach żądań

Pokazało to, że utrzymanie odpowiedniego progu (20–25 żądań) zapewniło odpowiednią równowagę między wydajnością wczytywania a wydajnością buforowania. Po przeprowadzeniu testów podstawowych wybrano 25 jako maxInitialRequest.

Zmodyfikowanie maksymalnej liczby żądań realizowanych równolegle spowodowało więcej niż jeden udostępniany pakiet, a oddzielenie go od punktu wejścia znacznie ograniczyło zbędny kod na stronie.

Redukcja ładunku JavaScript przez zwiększenie podziału na fragmenty

Eksperyment polegał wyłącznie na modyfikacji liczby żądań w celu sprawdzenia, czy będą one mają negatywny wpływ na wydajność wczytywania strony. Wyniki sugerują, że ustawienie maxInitialRequests na Komponent 25 na stronie testowej był optymalny, ponieważ zmniejszył rozmiar ładunku JavaScript bez spowolnienia w dół strony. Pozostała ilość kodu JavaScript potrzebna do nawodnienia strony co wyjaśnia, dlaczego wydajność wczytywania strony niekoniecznie poprawiała się z dużą ilością kodu.

Webpack używa domyślnego rozmiaru minimalnego do wygenerowania fragmentu 30 KB. Połączenie Parametr maxInitialRequests ma wartość 25 przy minimalnym rozmiarze 20 KB, ponieważ zapewnia lepsze buforowanie.

Zmniejszanie rozmiaru przy użyciu szczegółowych fragmentów

Wiele platform, w tym Next.js, korzysta z routingu po stronie klienta (obsługiwanego przez JavaScript) w celu wstrzykiwania nowszych tagów skryptu w przypadku każdego przejścia na trasie. Jak jednak wstępnie określają te dynamiczne fragmenty na etapie kompilacji?

Next.js korzysta z pliku manifestu kompilacji po stronie serwera, aby określić, które wysyłane fragmenty są używane przez różne punkty wejścia. Aby przekazać te informacje klientowi, skrócona wersja po jego stronie utworzono plik manifestu kompilacji w celu zmapowania wszystkich zależności dla każdego punktu wejścia.

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Dane wyjściowe wielu udostępnionych fragmentów w aplikacji Next.js.

Ta nowsza strategia szczegółowego dzielenia na fragmenty została po raz pierwszy wdrożona w Next.js za flagą, gdzie została przetestowana użytkowników wczesnej wersji. Wiele firm zaobserwowało znaczny spadek całkowitej liczby skryptów JavaScript używanych w swoich cała witryna:

Witryna Całkowita zmiana JS % różnicy
https://www.barnebys.com/ –238 KB –23%
https://sumup.com/ –220 KB –30%
https://www.hashicorp.com/ –11 MB –71%
Zmniejszenie rozmiaru pliku JavaScript – we wszystkich trasach (skompresowany)

Ostateczna wersja była domyślnie wysyłana w wersji 9.2.

Gatsby

W przypadku interfejsu Gatsby stosowane było to samo podejście: użycie heurystyka do definiowania wspólnych modułów:

config.optimization = {
  …
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

Optymalizacja konfiguracji pakietu internetowego pod kątem podobnej szczegółowej strategii dzielenia na fragmenty można zauważyć znaczny spadek JavaScriptu w wielu dużych witrynach:

Witryna Całkowita zmiana JS % różnicy
https://www.gatsbyjs.org/ –680 KB –22%
https://www.thirdandgrove.com/ –390 KB -25%
https://ghost.org/ -1,1 MB –35%
https://reactjs.org/ -80 KB -8%
Zmniejszenie rozmiaru pliku JavaScript – we wszystkich trasach (skompresowany)

Przeczytaj informacje PR, aby dowiedzieć się, jak wdrożyli tę logikę w konfiguracji pakietu internetowego, który jest wysyłany domyślnie w wersji 2.20.7.

Podsumowanie

Koncepcja wyświetlania szczegółowych informacji o dostawie nie dotyczy Next.js, Gatsby czy nawet pakietu internetowego. Wszyscy powinni rozważyć ulepszenie strategii podziału aplikacji na fragmenty, jeśli jest ona zgodna z rozsądnymi zasadami niezależnie od użytej platformy lub narzędzia do tworzenia pakietów modułów.

  • Jeśli chcesz zobaczyć te same optymalizacje w zakresie dzielenia na fragmenty zastosowane w aplikacji React obejrzyj tę przykładową reakcję Wykorzystuje uproszczona wersja strategii szczegółowego dzielenia na fragmenty i pomoże Ci zacząć stosować tę samą do Twojej witryny.
  • W przypadku podsumowania fragmenty są domyślnie tworzone szczegółowo z zachowaniem szczegółowości. Zwróć uwagę na: manualChunks, jeśli chcesz ręcznie nie musisz niczego konfigurować.