Una più recente strategia di chunking del webpack in Next.js e Gatsby riduce al minimo il codice duplicato per migliorare le prestazioni di caricamento delle pagine.
Chrome sta collaborando con strumenti e nell'ecosistema open source JavaScript. Di recente sono state eseguite diverse ottimizzazioni più recenti aggiunto per migliorare le prestazioni di caricamento di Next.js Gatsby. Questo articolo illustra una strategia di chunking granulare migliorata che ora viene fornito per impostazione predefinita in entrambi i framework.
Introduzione
Come molti framework web, Next.js e Gatsby utilizzano webpack come principale
bundler. introdotto webpack v3
CommonsChunkPlugin
per consentire di
moduli di output condivisi tra diversi punti di ingresso in uno (o pochi) "comuni" un chunk (o
chunking). Il codice condiviso può essere scaricato separatamente e memorizzato inizialmente nella cache del browser.
migliorano le prestazioni di caricamento.
Questo modello è diventato popolare con molti framework di applicazioni a pagina singola che adottavano un punto di ingresso e configurazione del bundle simile alla seguente:
Sebbene pratico, il concetto di raggruppare tutto il codice dei moduli condivisi in un unico blocco ha la sua
limitazioni. I moduli non condivisi in tutti i punti di ingresso possono essere scaricati per i percorsi che non li utilizzano
con conseguente download di più codice del necessario. Ad esempio, quando viene caricato page1
il blocco common
, carica il codice per moduleC
anche se page1
non utilizza moduleC
.
Per questo motivo, insieme ad alcuni altri, webpack v4 ha rimosso il plug-in in favore di un nuovo
Uno: SplitChunksPlugin
.
Chunking migliorato
Le impostazioni predefinite per SplitChunksPlugin
funzionano bene per la maggior parte degli utenti. Vengono suddivisi più blocchi
vengono create in base a una serie di condizioni
per impedire il recupero di codice duplicato su più route.
Tuttavia, molti framework web che utilizzano questo plug-in seguono ancora un "singolo-comune". di chunking
la suddivisione. Next.js, ad esempio, genera un bundle commons
contenente qualsiasi modulo
utilizzata in più del 50% delle pagine e in tutte le dipendenze del framework (react
, react-dom
e così via).
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)[\\/]/,
},
},
},
Anche se includere codice dipendente dal framework in un blocco condiviso significa che può essere scaricato e memorizzati nella cache per qualsiasi punto di ingresso, l'euristica basata sull'utilizzo di includere moduli comuni usati in più di metà delle pagine non è molto efficace. La modifica di questo rapporto può produrre solo uno dei due risultati seguenti:
- Se riduci le proporzioni, verrà scaricato un codice superfluo non necessario.
- Se aumenti il rapporto, più codice viene duplicato su più route.
Per risolvere il problema, Next.js ha adottato una diversa
configurazione perSplitChunksPlugin
che riduce
codice non necessario per qualsiasi route.
- Qualsiasi modulo di terze parti sufficientemente grande (superiore a 160 kB) viene suddiviso in un blocco
- Viene creato un blocco
frameworks
separato per le dipendenze del framework (react
,react-dom
e e così via) - Vengono creati tutti i blocchi condivisi necessari (fino a 25)
- La dimensione minima da generare per un blocco viene modificata in 20 kB
Questa strategia di chunking granulare offre i seguenti vantaggi:
- I tempi di caricamento delle pagine sono stati migliorati. L'emissione di più chunk condivisi, invece di uno solo, riduce al minimo la quantità di codice non necessario (o duplicato) per qualsiasi punto di ingresso.
- Memorizzazione nella cache migliorata durante le navigazioni. Suddivisione di librerie di grandi dimensioni e dipendenze del framework in blocchi separati riduce la possibilità di invalidazione della cache poiché è improbabile che modifiche finché non viene eseguito un upgrade.
Puoi vedere l'intera configurazione adottata da Next.js in webpack-config.ts
.
Altre richieste HTTP
SplitChunksPlugin
ha definito la base per il chunking granulare e applicando questo approccio a una
come Next.js, non era un concetto del tutto nuovo. Molti framework, tuttavia, hanno continuato a
usare un'unica euristica e uno "comune" di strategia di offerta per diversi motivi. È incluso il dubbio che
un numero maggiore di richieste HTTP può influire negativamente sulle prestazioni del sito.
I browser possono aprire solo un numero limitato di connessioni TCP a un'unica origine (6 per Chrome), quindi la riduzione al minimo del numero di blocchi restituiti da un bundler può garantire che il numero totale di richieste rimane al di sotto di questa soglia. Tuttavia, questo vale solo per HTTP/1.1. multiplexing in HTTP/2 consente lo streaming di più richieste in parallelo utilizzando una singola connessione su un origine dati. In altre parole, in genere non dobbiamo preoccuparci di limitare il numero di blocchi emessi dal nostro bundler.
Tutti i principali browser supportano HTTP/2. I team di Chrome e Next.js
Volevo verificare se un numero maggiore di richieste fosse aumentato dividendo il singolo "commons" di Next.js. gruppo
in più blocchi condivisi potrebbe influire in alcun modo sulle prestazioni di caricamento. Ha iniziato misurando
il rendimento di un singolo sito modificando il numero massimo di richieste parallele utilizzando
maxInitialRequests
proprietà.
In una media di tre esecuzioni di più prove su una singola pagina web,
load
,
start-render
e le volte di First Contentful Paint sono rimaste quasi uguali quando si variava il valore iniziale massimo
numero di richieste (da 5 a 15). È interessante notare che abbiamo notato solo un leggero overhead delle prestazioni
dopo la suddivisione aggressiva in centinaia di richieste.
Ciò ha dimostrato che rimanere sotto una soglia affidabile (20 ~ 25 richieste) ha trovato il giusto equilibrio
tra le prestazioni di caricamento
e l'efficienza della memorizzazione nella cache. Dopo alcuni test di base, è stato selezionato 25
il conteggio di maxInitialRequest
.
La modifica del numero massimo di richieste che si verificano in parallelo ha generato più di una singola richiesta un bundle condiviso, separandoli in modo appropriato per ciascun punto di ingresso una quantità di codice non necessario per la stessa pagina.
Questo esperimento riguardava solo la modifica del numero di richieste per verificare se ce ne fossero
un effetto negativo sulle prestazioni di caricamento pagina. I risultati suggeriscono che l'impostazione di maxInitialRequests
su
25
sulla pagina di test è stato ottimale perché ha ridotto le dimensioni del payload JavaScript senza rallentamenti
in tutta la pagina. È rimasta la quantità totale di JavaScript necessaria per eseguire l'hydration della pagina
più o meno la stessa cosa, il che spiega perché il rendimento del caricamento delle pagine non è necessariamente migliorato con la riduzione
una certa quantità di codice.
webpack usa 30 kB come dimensione minima predefinita per la generazione di un blocco. Tuttavia, l'accoppiamento di
Un valore di maxInitialRequests
pari a 25 con una dimensione minima di 20 kB ha migliorato la memorizzazione nella cache.
Riduzioni delle dimensioni con blocchi granulari
Molti framework, incluso Next.js, si basano sul routing lato client (gestito da JavaScript) per inserire nuovi tag di script per ogni transizione di percorso. Ma come fanno a predeterminare questi blocchi dinamici al momento della creazione?
Next.js utilizza un file manifest della build lato server per determinare quali blocchi di output vengono utilizzati da diversi punti di ingresso. Per fornire queste informazioni anche al client, una versione ridotta del lato client è stato creato un file manifest di compilazione per mappare tutte le dipendenze per ogni punto di ingresso.
// 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}`)) || []
)
}
Questa nuova strategia di chunking granulare è stata implementata per la prima volta in Next.js dietro un flag, in cui è stata testata su un di early adopter. Molti hanno riscontrato riduzioni significative del totale di JavaScript utilizzato per la propria intero sito:
Sito web | Variazione JS totale | % di differenza |
---|---|---|
https://www.barnebys.com/ | -238 kB | 23% |
https://sumup.com/ | - 220 kB | 30% |
https://www.hashicorp.com/ | -11 MB | 71% |
La versione finale è stata fornita per impostazione predefinita con la versione 9.2.
Gatsby
Gatsby seguiva lo stesso approccio di un modello di attribuzione euristica per la definizione di moduli comuni:
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)[\\/]/,
},
Ottimizzando la configurazione Webpack per adottare una strategia di chunking granulare simile, hanno anche notato notevoli riduzioni di JavaScript in molti siti di grandi dimensioni:
Sito web | Variazione JS totale | % di differenza |
---|---|---|
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% |
Dai un'occhiata al PR per capire come ha implementato questa logica nella configurazione webpack, fornita per impostazione predefinita nella versione 2.20.7.
Conclusione
Il concetto di spedizione di blocchi granulari non è specifico per Next.js, Gatsby o persino webpack. Per tutti dovrebbe prendere in considerazione di migliorare la strategia di chunking della propria applicazione se segue una grande varietà di "comune" approccio in bundle, indipendentemente dal framework o dal bundler di moduli utilizzato.
- Se vuoi vedere le stesse ottimizzazioni di chunking applicate a un'applicazione Vanilla React, dai un'occhiata a questo esempio di reazione Google Cloud. Utilizza un una versione semplificata della strategia di chunking granulare e può aiutarti ad applicare tipo di logica al tuo sito.
- Per l'aggregazione, i blocchi vengono creati in modo granulare per impostazione predefinita. Consulta questi dati:
manualChunks
per eseguire manualmente per configurare il comportamento.