Vantaggi di Webpack per la memorizzazione nella cache degli asset
L'aspetto successivo (dopo aver ottimizzato le dimensioni dell'app migliora il tempo di caricamento dell'app e la memorizzazione nella cache. Utilizzala per mantenere alcune parti dell'app ed evitare di scaricarli di nuovo ogni volta.
Usa il controllo delle versioni del bundle e le intestazioni cache
L'approccio comune della memorizzazione nella cache consiste nel:
indica al browser di memorizzare nella cache un file per un periodo molto lungo (ad es. un anno):
# Server header Cache-Control: max-age=31536000
Se non conosci la funzione di
Cache-Control
, leggi l'articolo di Jake Archibald eccellente post sulla memorizzazione nella cache pratiche.e rinomina il file quando viene modificato per forzare un nuovo download:
<!-- Before the change --> <script src="./index-v15.js"></script> <!-- After the change --> <script src="./index-v16.js"></script>
Questo approccio indica al browser di scaricare il file JS, memorizzarlo nella cache e utilizzare copia cache. Il browser raggiungerà la rete solo se il nome del file cambia. (o se passa un anno).
Con webpack fai lo stesso, ma invece di un numero di versione, specifichi la
hash del file. Per includere l'hash nel nome del file, utilizza
[chunkhash]
:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
}
};
Se hai bisogno
il nome del file per inviarlo al client, usa HtmlWebpackPlugin
o
WebpackManifestPlugin
.
Il HtmlWebpackPlugin
è un
un approccio semplice, ma meno flessibile. Durante la compilazione, questo plug-in genera
File HTML che include tutte le risorse compilate. Se la logica del server non è
complesso, per te dovrebbe essere sufficiente:
<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
La
WebpackManifestPlugin
è un approccio più flessibile, utile se si dispone di una parte server complessa.
Durante la creazione, genera un file JSON con un mapping tra i nomi dei file.
senza hash e nomi dei file con hash. Usa questo JSON sul server per scoprirlo
con quale file lavorare:
// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}
Per approfondire
- Jake Archibald sulla migliore memorizzazione nella cache pratiche
Estrai le dipendenze e il runtime in un file separato
Dipendenze
Le dipendenze dell'app tendono a cambiare meno spesso rispetto all'effettivo codice dell'app. Se ti sposti in un file separato, il browser potrà memorizzarle nella cache separatamente. e non verranno scaricati nuovamente ogni volta che cambia il codice dell'app.
Per estrarre le dipendenze in un blocco separato, esegui tre passaggi:
Sostituisci il nome file di output con
[name].[chunkname].js
:// webpack.config.js module.exports = { output: { // Before filename: 'bundle.[chunkhash].js', // After filename: '[name].[chunkhash].js' } };
Quando Webpack crea l'app, sostituisce
[name]
con il nome di un frammento. Se non aggiungiamo la parte[name]
, avremo differenziare i blocchi in base al loro hash, il che è abbastanza difficile.Converti il campo
entry
in un oggetto:// webpack.config.js module.exports = { // Before entry: './index.js', // After entry: { main: './index.js' } };
In questo snippet, "main" è il nome di un blocco. Questo nome verrà sostituito in posizione di
[name]
nel passaggio 1.A questo punto, se crei l'app, questo blocco includerà l'intero codice dell'app, come se non avessimo fatto questi passaggi. Ma questo cambierà in un secondo.
In webpack 4, aggiungi l'opzione
optimization.splitChunks.chunks: 'all'
nella configurazione del webpack:// webpack.config.js (for webpack 4) module.exports = { optimization: { splitChunks: { chunks: 'all' } } };
Questa opzione abilita la suddivisione smart code. con cui, webpack estrae il codice del fornitore diventa più grande di 30 kB (prima della minimizzazione e di gzip). Estrarre anche il codice comune, Ciò è utile se la tua build produce diversi bundle (ad es. se suddividi la tua app in percorsi).
In Webpack 3, aggiungi
CommonsChunkPlugin
:// webpack.config.js (for webpack 3) module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ // A name of the chunk that will include the dependencies. // This name is substituted in place of [name] from step 1 name: 'vendor', // A function that determines which modules to include into this chunk minChunks: module => module.context && module.context.includes('node_modules'), }) ] };
Questo plug-in accetta tutti i moduli che includono i percorsi
node_modules
e le sposta in un file separato denominatovendor.[chunkhash].js
.
Dopo queste modifiche, ogni build genererà due file anziché uno: main.[chunkhash].js
e
vendor.[chunkhash].js
(vendors~main.[chunkhash].js
per webpack 4). Nel caso di webpack 4,
il bundle del fornitore potrebbe non essere generato se le dipendenze sono ridotte, e va bene così:
$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
Asset Size Chunks Chunk Names
./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main
./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor
Il browser memorizza nella cache questi file separatamente e scarica nuovamente solo il codice che cambia.
Codice di runtime Webpack
Purtroppo, estrarre solo il codice del fornitore non è sufficiente. Se provi a modificare qualcosa nel codice dell'app:
// index.js
…
…
// E.g. add this:
console.log('Wat');
noterai che anche l'hash vendor
cambia:
Asset Size Chunks Chunk Names
./vendor.d9e134771799ecdf9483.js 47 kB 1 [emitted] vendor
↓
Asset Size Chunks Chunk Names
./vendor.e6ea4504d61a1cc1c60b.js 47 kB 1 [emitted] vendor
Questo accade perché il bundle webpack, a parte il codice dei moduli, ha un runtime: una piccola porzione di codice che gestisce l'esecuzione del modulo. Quando suddividi il codice in più file, questa porzione di codice inizia a includere una mappatura tra ID chunk e file corrispondenti:
// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
"0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";
Webpack include questo runtime nell'ultimo blocco generato, che è vendor
nel nostro caso. E ogni volta che un blocco viene modificato, cambia anche questa porzione di codice,
causando il cambiamento dell'intero blocco vendor
.
Per risolvere il problema, spostiamo il runtime in un file separato. Nel Webpack 4,
ottenuto attivando l'opzione optimization.runtimeChunk
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};
In webpack 3, per farlo crea un blocco aggiuntivo vuoto con CommonsChunkPlugin
:
// webpack.config.js (for webpack 3)
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => module.context && module.context.includes('node_modules')
}),
// This plugin must come after the vendor one (because webpack
// includes runtime into the last chunk)
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
// minChunks: Infinity means that no app modules
// will be included into this chunk
minChunks: Infinity
})
]
};
Dopo queste modifiche, ogni build genererà tre file:
$ webpack
Hash: ac01483e8fec1fa70676
Version: webpack 3.8.1
Time: 3816ms
Asset Size Chunks Chunk Names
./main.00bab6fd3100008a42b0.js 82 kB 0 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 1 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
Includili in index.html
nell'ordine inverso. Ecco fatto:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>
Per approfondire
- Guida webpack sulla memorizzazione nella cache a lungo termine
- Documentazione Webpack su runtime e file manifest
- "Come ottenere il massimo CommonsChunkPlugin"
- Come funzionano
optimization.splitChunks
eoptimization.runtimeChunk
Runtime Webpack in linea per salvare una richiesta HTTP aggiuntiva
Per migliorare ulteriormente le cose, prova a incorporare il runtime webpack nell'HTML risposta. Ad esempio, invece di questo:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
fai questo:
<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>
Le dimensioni del runtime sono ridotte e la loro presenza consente di salvare una richiesta HTTP ( sono importanti con HTTP/1; meno importante con HTTP/2 ma potrebbe comunque svolgere un ).
Ecco come fare.
Se generi il codice HTML con htmlWebpackplugin
Se utilizzi HtmlWebpackPlugin da generare un file HTML, InlineSourcePlugin è tutto quello di cui hai bisogno:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
inlineSource: 'runtime~.+\\.js',
}),
new InlineSourcePlugin()
]
};
Se generi il codice HTML utilizzando una logica server personalizzata
Con Webpack 4:
Aggiungi il parametro
WebpackManifestPlugin
per conoscere il nome generato del blocco di runtime:// webpack.config.js (for webpack 4) const ManifestPlugin = require('webpack-manifest-plugin'); module.exports = { plugins: [ new ManifestPlugin() ] };
Una build con questo plug-in creerebbe un file simile al seguente:
// manifest.json { "runtime~main.js": "runtime~main.8e0d62a03.js" }
Incorpora il contenuto del blocco del runtime in modo pratico. Ad es. con Node.js ed Express:
// server.js const fs = require('fs'); const manifest = require('./manifest.json'); const runtimeContent = fs.readFileSync(manifest['runtime~main.js'], 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
Oppure con webpack 3:
Rendi il nome del runtime statico specificando
filename
:module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, filename: 'runtime.js' }) ] };
Incorpora i contenuti di
runtime.js
in modo pratico. Ad es. con Node.js ed Express:// server.js const fs = require('fs'); const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
Codice di caricamento lento che non ti serve adesso
A volte, una pagina è composta da parti più e meno importanti:
- Se carichi una pagina video su YouTube, ti interessa di più il video che commenti. In questo caso, il video è più importante dei commenti.
- Se apri un articolo su un sito di notizie, ti interessa di più il testo del invece che sugli annunci. In questo caso, il testo è più importante degli annunci.
In questi casi, per migliorare le prestazioni di caricamento iniziale, scarica solo il file
gli elementi più importanti e il caricamento lento delle parti rimanenti in un secondo momento. Utilizza il
import()
e
code-splitting per:
// videoPlayer.js
export function renderVideoPlayer() { … }
// comments.js
export function renderComments() { … }
// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();
// …Custom event listener
onShowCommentsClick(() => {
import('./comments').then((comments) => {
comments.renderComments();
});
});
import()
indica che vuoi caricare un modulo specifico in modo dinamico. Quando
webpack rileva import('./module.js')
, sposta questo modulo in un
chunk:
$ webpack
Hash: 39b2a53cb4e73f0dc5b2
Version: webpack 3.8.1
Time: 4273ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./main.f7e53d8e13e9a2745d6d.js 60 kB 1 [emitted] main
./vendor.4f14b6326a80f4752a98.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
e lo scarica solo quando l'esecuzione raggiunge la funzione import()
.
In questo modo il bundle main
ridurrà le dimensioni, migliorando il tempo di caricamento iniziale.
Inoltre, migliorerà la memorizzazione nella cache: se modifichi il codice nel blocco principale,
blocco dei commenti non verrà influenzato.
Per approfondire
- Documentazione Webpack per
import()
funzione - La proposta JavaScript per l'implementazione di
import()
sintassi
Suddividi il codice in percorsi e pagine
Se la tua app ha più percorsi o pagine, ma esiste un solo file JS con
del codice (un singolo blocco main
), è probabile che vengano pubblicati byte aggiuntivi
per ogni richiesta. Ad esempio, quando un utente visita la home page del tuo sito:
non devono caricare il codice per il rendering di un articolo che si trova su un pagina, ma la caricheranno. Inoltre, se l'utente visita sempre solo la casa pagina e si apporta una modifica al codice dell'articolo, webpack invaliderà il l'intero bundle e l'utente dovrà scaricare di nuovo l'intera app.
Se l'app viene suddivisa in pagine (o percorsi, se si tratta di un'app di una sola pagina), l'utente scaricherà solo il codice pertinente. Inoltre, il browser memorizzerà nella cache il codice dell'app migliore: se modifichi il codice della home page, webpack invaliderà solo con il frammento corrispondente.
Per le app di una sola pagina
Per suddividere le app a pagina singola per percorsi, usa import()
(consulta la sezione "Codice di caricamento lento)
che non ti servono adesso"). Se usi un framework,
potrebbe esistere una soluzione per questo problema:
- "Codice
Suddivisione"
nei documenti di
react-router
(per React) - "Caricamento lento
Routes" in
Documenti di
vue-router
(per Vue.js)
Per le app multipagina tradizionali
Per suddividere le app tradizionali per pagina, utilizza la casella di controllo punti di accesso. Se la tua app ha tre tipi di pagine: home page, pagina dell'articolo e pagina dell'account utente; deve avere tre voci:
// webpack.config.js
module.exports = {
entry: {
home: './src/Home/index.js',
article: './src/Article/index.js',
profile: './src/Profile/index.js'
}
};
Per ogni file delle voci, webpack crea un albero delle dipendenze separato e genera un bundle che include solo moduli utilizzati dalla voce in questione:
$ webpack
Hash: 318d7b8490a7382bf23b
Version: webpack 3.8.1
Time: 4273ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./home.91b9ed27366fe7e33d6a.js 18 kB 1 [emitted] home
./article.87a128755b16ac3294fd.js 32 kB 2 [emitted] article
./profile.de945dc02685f6166781.js 24 kB 3 [emitted] profile
./vendor.4f14b6326a80f4752a98.js 46 kB 4 [emitted] vendor
./runtime.318d7b8490a7382bf23b.js 1.45 kB 5 [emitted] runtime
Quindi, se solo la pagina dell'articolo utilizza Lodash, i bundle home
e profile
non la includeranno e l'utente non dovrà scaricare questa raccolta quando
visitando la home page.
Tuttavia, alberi delle dipendenze separati hanno i loro svantaggi. Se due punti di ingresso utilizzano
Lodash e non hai spostato le dipendenze in un bundle di fornitori, entrambe le voci
i punti includeranno una copia di Lodash. Per risolvere il problema, in Webpack 4,aggiungi la
Opzione optimization.splitChunks.chunks: 'all'
nella configurazione webpack:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Questa opzione abilita la suddivisione smart code. Con questa opzione, Webpack verrebbe automaticamente cercare il codice comune ed estrarlo in file separati.
In alternativa, in webpack 3,utilizza CommonsChunkPlugin
- sposterà le dipendenze comuni in un nuovo file specificato:
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2 // 2 is the default value
})
]
};
Divertiti con il valore minChunks
per trovare quello migliore. In genere,
vuoi mantenerlo basso, ma aumentare se il numero di blocchi cresce. Per
Ad esempio, per 3 blocchi, minChunks
potrebbe essere 2, ma per 30 blocchi, potrebbe essere 8
– perché se lo mantieni a 2, troppi moduli finiranno nel file comune,
gonfiarlo troppo.
Per approfondire
- Documentazione Webpack sul concetto di voce punti di accesso
- Documentazione Webpack sulle CommonsChunkPlugin
- "Come ottenere il massimo CommonsChunkPlugin"
- Come funzionano
optimization.splitChunks
eoptimization.runtimeChunk
Rendi più stabili gli ID modulo
Durante la creazione del codice, webpack assegna un ID a ciascun modulo. In seguito, questi ID vengono
utilizzata in require()
all'interno del bundle. Di solito vedi gli ID nell'output della build
subito prima dei percorsi dei moduli:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.8ecaf182f5c85b7a8199.js 22.5 kB 0 [emitted]
./main.4e50a16675574df6a9e9.js 60 kB 1 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
↓ Qui
[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
[4] ./comments.js 58 kB {0} [built]
[5] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Per impostazione predefinita, gli ID vengono calcolati utilizzando un contatore (ad es. il primo modulo ha ID 0, il secondo ha ID 1 e così via). Il problema è che, quando aggiungi un nuovo modulo, potrebbe essere visualizzato al centro dell'elenco dei moduli, modificando tutte le moduli successivi ID:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.5c82c0f337fcb22672b5.js 22 kB 0 [emitted]
./main.0c8b617dfc40c2827ae3.js 82 kB 1 [emitted] main
./vendor.26886caf15818fa82dfa.js 46 kB 2 [emitted] vendor
./runtime.79f17c27b335abc7aaf4.js 1.45 kB 3 [emitted] runtime
[0] ./index.js 29 kB {1} [built]
[2] (webpack)/buildin/global.js 488 bytes {2} [built]
[3] (webpack)/buildin/module.js 495 bytes {2} [built]
↓ Abbiamo aggiunto una nuova modulo...
[4] ./webPlayer.js 24 kB {1} [built]
↓ E guarda cosa ha fatto! Ora comments.js
ha l'ID 5 anziché 4
[5] ./comments.js 58 kB {0} [built]
↓ ads.js
ora ha ID 6 anziché 5
[6] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Questa operazione invalida tutti i blocchi che includono o dipendono da moduli con ID modificati.
anche se il codice effettivo non è cambiato. Nel nostro caso, il blocco 0
(il blocco
con comments.js
) e il blocco main
(il blocco con l'altro codice dell'app)
invalidato, mentre solo il main
avrebbe dovuto esserlo.
Per risolvere il problema, cambia il modo in cui gli ID modulo vengono calcolati utilizzando il metodo
HashedModuleIdsPlugin
Sostituisce gli ID basati su contatori con hash di percorsi di moduli:
$ webpack
Hash: df3474e4f76528e3bbc9
Version: webpack 3.8.1
Time: 2150ms
Asset Size Chunks Chunk Names
./0.6168aaac8461862eab7a.js 22.5 kB 0 [emitted]
./main.a2e49a279552980e3b91.js 60 kB 1 [emitted] main
./vendor.ff9f7ea865884e6a84c8.js 46 kB 2 [emitted] vendor
./runtime.25f5d0204e4f77fa57a1.js 1.45 kB 3 [emitted] runtime
↓ Qui
[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Con questo approccio, l'ID di un modulo cambia solo se lo rinomini o sposti in maggior dettaglio più avanti in questo modulo. I nuovi moduli non incideranno su altri moduli ID.
Per attivare il plug-in, aggiungilo alla sezione plugins
della configurazione:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};
Per approfondire
- Documentazione Webpack sulle HashedModuleIdsPlugin
Riepilogo
- Memorizza nella cache il bundle e differenzia tra le versioni modificando il nome del bundle
- Suddividi il bundle in codice dell'app, codice del fornitore e runtime
- Incorpora il runtime per salvare una richiesta HTTP
- Caricamento lento di codice non critico con
import
- Suddividi il codice per route/pagine per evitare di caricare contenuti non necessari