Nowsza strategia dzielenia pakietów webpack w Next.js i Gatsby minimalizuje duplikowanie kodu, aby poprawić wydajność wczytywania strony.
Chrome współpracuje z narzędziami i platformami w ekosystemie open source JavaScript. Niedawno dodaliśmy kilka nowych optymalizacji, aby poprawić wydajność wczytywania Next.js i Gatsby. W tym artykule opisujemy ulepszoną strategię dzielenia na mniejsze części, która jest teraz domyślnie stosowana w obu platformach.
Wprowadzenie
Podobnie jak wiele platform internetowych, Next.js i Gatsby używają webpacka jako podstawowego narzędzia do łączenia plików. W webpacku w wersji 3 wprowadzono CommonsChunkPlugin
, aby umożliwić generowanie modułów udostępnianych między różnymi punktami wejścia w jednym (lub kilku) wspólnym fragmencie kodu. Wspólny kod można pobrać osobno i wcześniej zapisać w pamięci podręcznej przeglądarki, co może zwiększyć szybkość wczytywania.
Ten wzorzec stał się popularny, ponieważ wiele platform aplikacji jednostronicowych przyjęło konfigurację punktu wejścia i pakietu, która wyglądała tak:
Koncepcja łączenia całego kodu modułu udostępnionego w jednym bloku jest praktyczna, ale ma swoje ograniczenia. Moduły, które nie są udostępniane w każdym punkcie wejścia, można pobrać w przypadku tras, które ich nie używają, co powoduje pobranie większej ilości kodu niż jest to konieczne. Na przykład gdy page1
wczytuje fragment common
, wczytuje kod dla moduleC
, mimo że page1
nie używa moduleC
.
Z tego i kilku innych powodów w webpacku w wersji 4 usunięto wtyczkę na rzecz nowej: SplitChunksPlugin
.
Ulepszone dzielenie na części
Ustawienia domyślne SplitChunksPlugin
sprawdzają się w przypadku większości użytkowników. Wiele podzielonych fragmentów jest tworzonych w zależności od liczby warunków, aby zapobiec pobieraniu zduplikowanego kodu w wielu trasach.
Wiele platform internetowych, które korzystają z tej wtyczki, nadal stosuje jednak podejście „single-commons” do dzielenia fragmentów. Na przykład Next.js wygeneruje pakiet commons
, który będzie zawierać wszystkie moduły używane na ponad 50% stron oraz wszystkie zależności platformy (react
, react-dom
itp.).
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)[\\/]/,
},
},
},
Chociaż umieszczenie w udostępnionym bloku kodu zależnego od frameworka oznacza, że można go pobrać i zapisać w pamięci podręcznej dla dowolnego punktu wejścia, heurystyka oparta na użyciu, która obejmuje wspólne moduły używane na ponad połowie stron, nie jest zbyt skuteczna. Zmiana tego współczynnika może przynieść tylko 2 rezultaty:
- Jeśli zmniejszysz ten współczynnik, pobieranych będzie więcej niepotrzebnego kodu.
- Jeśli zwiększysz ten współczynnik, więcej kodu zostanie powielone na różnych trasach.
Aby rozwiązać ten problem, Next.js zastosował inną konfiguracjęSplitChunksPlugin
, która ogranicza ilość niepotrzebnego kodu w przypadku każdej trasy.
- Każdy wystarczająco duży moduł innej firmy (większy niż 160 KB) jest dzielony na osobny fragment.
- Dla zależności platformy (
react
,react-dom
itp.) tworzony jest osobny fragmentframeworks
. - Tworzonych jest tyle udostępnionych fragmentów, ile jest potrzebnych (maksymalnie 25).
- Minimalny rozmiar fragmentu, który ma zostać wygenerowany, został zmieniony na 20 KB.
Ta szczegółowa strategia dzielenia na części zapewnia te korzyści:
- Czas wczytywania strony jest krótszy. Wysyłanie wielu wspólnych fragmentów zamiast jednego minimalizuje ilość niepotrzebnego (lub zduplikowanego) kodu dla każdego punktu wejścia.
- Ulepszone buforowanie podczas nawigacji. Podzielenie dużych bibliotek i zależności platformy na osobne części zmniejsza prawdopodobieństwo unieważnienia pamięci podręcznej, ponieważ obie te części rzadko się zmieniają, dopóki nie zostanie przeprowadzona aktualizacja.
Całą konfigurację przyjętą przez Next.js możesz zobaczyć w webpack-config.ts
.
Więcej żądań HTTP
SplitChunksPlugin
zdefiniował podstawy szczegółowego dzielenia na części, a zastosowanie tego podejścia do platformy takiej jak Next.js nie było całkowicie nową koncepcją. Wiele platform nadal jednak stosowało jedną heurystykę i strategię pakietu „commons” z kilku powodów. Obejmuje to obawę, że znacznie większa liczba żądań HTTP może negatywnie wpłynąć na wydajność witryny.
Przeglądarki mogą otwierać tylko ograniczoną liczbę połączeń TCP z jednym źródłem (w przypadku Chrome jest to 6), więc zminimalizowanie liczby fragmentów wyjściowych modułu pakującego może zapewnić, że łączna liczba żądań nie przekroczy tego progu. Dotyczy to jednak tylko protokołu HTTP/1.1. Multipleksowanie w HTTP/2 umożliwia przesyłanie strumieniowe wielu żądań równolegle przy użyciu jednego połączenia z jednym źródłem. Inaczej mówiąc, nie musimy się zwykle martwić ograniczaniem liczby fragmentów emitowanych przez nasz moduł.
Wszystkie główne przeglądarki obsługują HTTP/2. Zespoły Chrome i Next.js chciały sprawdzić, czy zwiększenie liczby żądań przez podzielenie pojedynczego pakietu „commons” Next.js na kilka udostępnionych fragmentów wpłynie w jakikolwiek sposób na wydajność ładowania. Zaczęli od pomiaru skuteczności pojedynczej witryny, modyfikując maksymalną liczbę równoległych żądań za pomocą właściwości maxInitialRequests
.
Średnio w 3 seriach wielu prób na jednej stronie internetowej czasy load
, start-render i pierwszego wyrenderowania treści pozostawały mniej więcej takie same przy zmianie maksymalnej liczby początkowych żądań (od 5 do 15). Co ciekawe, niewielki narzut na wydajność zauważyliśmy dopiero po agresywnym podzieleniu na setki żądań.
Wyniki pokazały, że utrzymywanie się poniżej wiarygodnego progu (20–25 żądań) zapewnia odpowiednią równowagę między wydajnością ładowania a efektywnością buforowania. Po przeprowadzeniu testów podstawowych wybrano liczbę 25 jako maxInitialRequest
.
Zmiana maksymalnej liczby równoległych żądań spowodowała powstanie więcej niż jednego wspólnego pakietu. Odpowiednie rozdzielenie ich dla każdego punktu wejścia znacznie zmniejszyło ilość niepotrzebnego kodu na tej samej stronie.
Ten eksperyment polegał tylko na modyfikowaniu liczby żądań, aby sprawdzić, czy będzie to miało negatywny wpływ na wydajność wczytywania strony. Wyniki sugerują, że ustawienie wartości maxInitialRequests
na 25
na stronie testowej było optymalne, ponieważ zmniejszyło rozmiar ładunku JavaScript bez spowalniania strony. Całkowita ilość JavaScriptu potrzebna do nawodnienia strony pozostała mniej więcej taka sama, co wyjaśnia, dlaczego wydajność wczytywania strony niekoniecznie poprawiła się wraz ze zmniejszeniem ilości kodu.
webpack używa 30 KB jako domyślnego minimalnego rozmiaru fragmentu, który ma zostać wygenerowany. Jednak połączenie wartości maxInitialRequests
= 25 z minimalnym rozmiarem 20 KB przyniosło lepsze wyniki w zakresie buforowania.
Zmniejszanie rozmiaru dzięki szczegółowym fragmentom
Wiele platform, w tym Next.js, korzysta z routingu po stronie klienta (obsługiwanego przez JavaScript), aby wstawiać nowsze tagi skryptu przy każdej zmianie trasy. Jak jednak z wyprzedzeniem określa się te dynamiczne fragmenty w czasie kompilacji?
Next.js używa pliku manifestu kompilacji po stronie serwera, aby określić, które wygenerowane fragmenty są używane przez różne punkty wejścia. Aby przekazać te informacje również klientowi, utworzono skrócony plik manifestu kompilacji po stronie klienta, który mapuje wszystkie 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}`)) || []
)
}

Ta nowa strategia dzielenia na mniejsze części została po raz pierwszy wprowadzona w Next.js za pomocą flagi, gdzie była testowana przez wielu użytkowników wczesnej wersji. Wielu z nich odnotowało znaczne zmniejszenie całkowitej ilości kodu JavaScript używanego w całej witrynie:
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% |
Wersja ostateczna została domyślnie wprowadzona w wersji 9.2.
Gatsby
Gatsby stosował wcześniej to samo podejście, czyli heurystykę opartą na użyciu, 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)[\\/]/,
},
Dzięki optymalizacji konfiguracji webpacka pod kątem podobnej strategii szczegółowego dzielenia na fragmenty zauważyli też znaczne zmniejszenie ilości 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% |
Zapoznaj się z prośbą o scalenie, aby dowiedzieć się, jak zaimplementowano tę logikę w konfiguracji webpacka, która jest domyślnie dostarczana w wersji 2.20.7.
Podsumowanie
Koncepcja wysyłania szczegółowych fragmentów nie jest specyficzna dla Next.js, Gatsby ani nawet webpacka. Każdy powinien rozważyć ulepszenie strategii dzielenia aplikacji na części, jeśli stosuje podejście dużego pakietu „commons”, niezależnie od używanego frameworka lub narzędzia do pakowania modułów.
- Jeśli chcesz zobaczyć, jak te same optymalizacje dzielenia na mniejsze części są stosowane w czystej aplikacji React, zapoznaj się z tym przykładowym projektem React. Używa on uproszczonej wersji strategii szczegółowego dzielenia na mniejsze części i może pomóc Ci w zastosowaniu podobnej logiki w Twojej witrynie.
- W przypadku funkcji Rollup fragmenty są domyślnie tworzone w sposób szczegółowy. Jeśli chcesz ręcznie skonfigurować to działanie, zapoznaj się z tym artykułem:
manualChunks