Avantages de webpack pour la mise en cache des éléments
Après avoir optimisé la taille de l'application, améliore le temps de chargement de l'application est la mise en cache. Utilisez-le pour conserver des parties de l'application sur le et évitez de les télécharger à nouveau à chaque fois.
Utiliser la gestion des versions de bundle et les en-têtes de cache
L'approche courante de la mise en cache consiste à:
au navigateur de mettre en cache un fichier très longtemps (un an, par exemple):
# Server header Cache-Control: max-age=31536000
Si vous ne savez pas ce que fait
Cache-Control
, consultez les excellent post sur la mise en cache. pratiques.puis renommez le fichier une fois modifié pour forcer le retéléchargement:
<!-- Before the change --> <script src="./index-v15.js"></script> <!-- After the change --> <script src="./index-v16.js"></script>
Cette approche indique au navigateur de télécharger le fichier JS, de le mettre en cache et d'utiliser en cache la copie en cache. Le navigateur n'atteindra le réseau que si le nom du fichier change (ou si une année s'est écoulée).
Avec webpack, vous faites la même chose, mais au lieu d'un numéro de version, vous spécifiez
le hachage de fichier. Pour inclure le hachage dans le nom du fichier, utilisez
[chunkhash]
:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.[chunkhash].js' // → bundle.8e0d62a03.js
}
};
Si vous avez besoin
nom de fichier pour l'envoyer au client, utilisez la commande HtmlWebpackPlugin
ou
WebpackManifestPlugin
Le HtmlWebpackPlugin
est un
simple mais moins flexible. Lors de la compilation, ce plug-in génère
Fichier HTML qui inclut toutes les ressources compilées. Si la logique de votre serveur n'est pas
il est alors assez complexe pour vous:
<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>
La
WebpackManifestPlugin
est une approche plus flexible qui est utile
si vous avez une partie de serveur complexe.
Pendant la compilation, il génère un fichier JSON avec un mappage entre les noms de fichiers
sans hachage et les noms
de fichiers avec hachage. Utilisez ce code JSON sur le serveur pour le savoir.
le fichier à utiliser:
// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}
Documentation complémentaire
- Jake Archibald concerne les meilleures mises en cache pratiques
Extraire les dépendances et l'environnement d'exécution dans un fichier distinct
Dépendances
Les dépendances des applications ont tendance à changer moins souvent que le code réel de l'application. Si vous déménagez dans un fichier séparé, le navigateur pourra les mettre en cache séparément. et ne les télécharge pas à nouveau chaque fois que le code de l'application est modifié.
Pour extraire les dépendances dans un fragment distinct, effectuez trois étapes:
Remplacez le nom de fichier de sortie par
[name].[chunkname].js
:// webpack.config.js module.exports = { output: { // Before filename: 'bundle.[chunkhash].js', // After filename: '[name].[chunkhash].js' } };
Lorsque webpack compile l'application, il remplace
[name]
. avec le nom d'un fragment. Si nous n'ajoutons pas la partie[name]
, nous obtenons de différencier les fragments par leur hachage, ce qui est assez difficile !Convertissez le champ
entry
en objet:// webpack.config.js module.exports = { // Before entry: './index.js', // After entry: { main: './index.js' } };
Dans cet extrait, "main" est le nom d'un bloc. Ce nom sera remplacé dans à la place de
[name]
de l'étape 1.À présent, si vous compilez l'application, ce fragment inclura le code complet de l'application, juste comme si nous n'avions pas fait ces étapes. Mais ça va changer dans une seconde.
Dans webpack 4, ajoutez l'option
optimization.splitChunks.chunks: 'all'
. dans votre configuration webpack:// webpack.config.js (for webpack 4) module.exports = { optimization: { splitChunks: { chunks: 'all' } } };
Cette option active la division intelligente du code. Webpack extrait alors le code du fournisseur si elle dépasse 30 Ko (avant la minimisation et l'enregistrement gzip). Elle extrait également le code commun : Cela est utile si votre build génère plusieurs bundles (par exemple, si vous divisez votre application en routes).
Dans webpack 3, ajoutez
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'), }) ] };
Ce plug-in utilise tous les modules dont les chemins d'accès incluent
node_modules
et les déplace dans un fichier distinct appelévendor.[chunkhash].js
.
Après ces modifications, chaque compilation générera deux fichiers au lieu d'un: main.[chunkhash].js
et
vendor.[chunkhash].js
(vendors~main.[chunkhash].js
pour Webpack 4). Dans le cas de webpack 4,
le bundle du fournisseur peut ne pas être généré si les dépendances sont petites, et ce n'est pas un problème:
$ 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
Le navigateur met alors en cache ces fichiers séparément et ne télécharge à nouveau que le code modifié.
Code d'exécution Webpack
Malheureusement, il ne suffit pas d'extraire le code du fournisseur. Si vous essayez de modifier un élément dans le code de l'application:
// index.js
…
…
// E.g. add this:
console.log('Wat');
vous remarquerez que le hachage vendor
change également:
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
En effet, le bundle webpack, hormis le code des modules, possède un environnement d'exécution : un petit extrait de code qui gère l'exécution du module. Lorsque vous divisez le code en plusieurs fichiers, ce morceau de code commence à inclure un mappage entre les ID de bloc et fichiers correspondants:
// vendor.e6ea4504d61a1cc1c60b.js
script.src = __webpack_require__.p + chunkId + "." + {
"0": "2f2269c7f0a55a5c1871"
}[chunkId] + ".js";
Webpack inclut cet environnement d'exécution dans le dernier fragment généré, à savoir vendor
dans notre cas. Et chaque fois qu'un bloc change, cet extrait de code change également,
entraînant la modification de l'ensemble du fragment vendor
.
Pour résoudre ce problème, nous allons déplacer l'environnement d'exécution dans un fichier distinct. Dans webpack 4,il s'agit de
en activant l'option optimization.runtimeChunk
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};
Dans webpack 3,créez un fragment vide supplémentaire avec 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
})
]
};
Après ces modifications, chaque compilation générera trois fichiers:
$ 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
Ajoutez-les dans index.html
dans l'ordre inverse et vous avez terminé:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
<script src="./vendor.26886caf15818fa82dfa.js"></script>
<script src="./main.00bab6fd3100008a42b0.js"></script>
Documentation complémentaire
- Guide Webpack sur la mise en cache à long terme
- la documentation Webpack sur l'environnement d'exécution webpack et fichier manifeste
- "Exploiter tout le potentiel CommonsChunkPlugin"
- Fonctionnement de
optimization.splitChunks
etoptimization.runtimeChunk
Environnement d'exécution webpack intégré pour enregistrer une requête HTTP supplémentaire
Pour améliorer les choses, essayez d'intégrer l'environnement d'exécution webpack dans le code HTML. de réponse. Par exemple, au lieu de ceci:
<!-- index.html -->
<script src="./runtime.79f17c27b335abc7aaf4.js"></script>
procédez comme suit:
<!-- index.html -->
<script>
!function(e){function n(r){if(t[r])return t[r].exports;…}} ([]);
</script>
L'environnement d'exécution est petit, et son intégration vous aidera à enregistrer une requête HTTP (ce qui est important avec HTTP/1. moins important avec HTTP/2, mais il peut tout de même lire un l'effet inverse).
Voici comment procéder.
Si vous générez du code HTML avec le plug-in HTMLWebpackPlugin
Si vous utilisez les HtmlWebpackPlugin, qui permet de générer un fichier HTML, InlineSourcePlugin est tout ce dont vous avez besoin:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
inlineSource: 'runtime~.+\\.js',
}),
new InlineSourcePlugin()
]
};
Si vous générez du code HTML à l'aide d'une logique de serveur personnalisée
Avec webpack 4:
Ajoutez le
WebpackManifestPlugin
pour connaître le nom généré du fragment d'exécution:// webpack.config.js (for webpack 4) const ManifestPlugin = require('webpack-manifest-plugin'); module.exports = { plugins: [ new ManifestPlugin() ] };
Une compilation avec ce plug-in génère un fichier qui se présente comme suit:
// manifest.json { "runtime~main.js": "runtime~main.8e0d62a03.js" }
Intégrez facilement le contenu du fragment d'exécution. Par exemple, avec Node.js et 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> … `); });
Ou avec webpack 3:
Rendez le nom de l'environnement d'exécution statique en spécifiant
filename
:module.exports = { plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, filename: 'runtime.js' }) ] };
Intégrez facilement le contenu
runtime.js
. Par exemple, avec Node.js et Express:// server.js const fs = require('fs'); const runtimeContent = fs.readFileSync('./runtime.js', 'utf-8'); app.get('/', (req, res) => { res.send(` … <script>${runtimeContent}</script> … `); });
Le code à chargement différé dont vous n'avez pas besoin pour le moment
Parfois, une page contient des parties plus et moins importantes:
- Si vous chargez une page de vidéo sur YouTube, vous vous intéressez plus à la vidéo commentaires. Ici, la vidéo est plus importante que les commentaires.
- Si vous ouvrez un article sur un site d'actualités, vous vous souciez davantage du texte plutôt que sur les annonces. Ici, le texte est plus important que les annonces.
Dans ce cas, améliorez les performances de chargement initial en ne téléchargeant que le fichier
les éléments les plus importants en premier et
le chargement différé des parties restantes plus tard. Utilisez les
fonction import()
et
de code-splitting:
// 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()
indique que vous souhaitez charger un module spécifique de manière dynamique. Quand ?
webpack voit import('./module.js')
, il déplace ce module dans un autre
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
et ne le télécharge que lorsque l'exécution atteint la fonction import()
.
Le bundle main
sera ainsi plus petit, ce qui améliorera le temps de chargement initial.
Plus encore, cela améliorera la mise en cache : si vous modifiez le code dans le bloc principal,
le bloc de commentaires
ne sera pas affecté.
Documentation complémentaire
- Documentation Webpack pour
import()
- La proposition JavaScript pour implémenter
import()
syntaxe
Diviser le code en plusieurs routes et pages
Si votre application comporte plusieurs routes ou pages, mais qu'il n'y a qu'un seul fichier JS avec
le code (un seul bloc main
), il est probable que vous utilisiez des octets supplémentaires
chaque requête. Par exemple, lorsqu'un utilisateur visite une page d'accueil de votre site:
ils n'ont pas besoin de charger le code pour afficher un article qui se trouve sur une autre mais ils la chargent. De plus, si l'utilisateur ne visite que la maison et que vous modifiez le code de l'article, webpack invalide le site l'intégralité du pack, et l'utilisateur devra télécharger à nouveau l'intégralité de l'application.
Si l'application est divisée en pages (ou en routes, s'il s'agit d'une application monopage), l'utilisateur télécharge uniquement le code approprié. De plus, le navigateur met en cache le code de l'application mieux: si vous modifiez le code de la page d'accueil, webpack invalidera uniquement le bloc correspondant.
Pour les applications monopages
Pour diviser les applications monopages en fonction des itinéraires, utilisez import()
(voir la section Code de chargement différé
dont vous n'avez pas besoin pour le moment"). Si vous utilisez un framework,
il existe peut-être une solution à ce problème:
- "Code
Fractionnement"
dans la documentation de
react-router
(pour React) - "Chargement différé
Routes" dans
Documentation de
vue-router
(pour Vue.js)
Pour les applications multipages traditionnelles
Pour répartir les applications traditionnelles par pages, utilisez la fonction entrée de webpack points d'accès. Si votre application comporte trois la page d'accueil, la page de l'article et la page du compte utilisateur. doit comporter trois entrées:
// webpack.config.js
module.exports = {
entry: {
home: './src/Home/index.js',
article: './src/Article/index.js',
profile: './src/Profile/index.js'
}
};
Pour chaque fichier d'entrée, webpack crée une arborescence de dépendances distincte et génère Un bundle qui ne comprend que les modules utilisés par cette entrée:
$ 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
Ainsi, si seule la page de l'article utilise Lodash, les bundles home
et profile
ne l'inclut pas, et l'utilisateur n'aura pas à télécharger cette bibliothèque
à la page d'accueil.
Toutefois, les arborescences de dépendances distinctes présentent des inconvénients. Si deux points d'entrée utilisent
Lodash, et que vous n'avez pas déplacé vos dépendances dans un bundle de fournisseurs,
points inclut une copie de Lodash. Pour résoudre ce problème, dans webpack 4,ajoutez le paramètre
optimization.splitChunks.chunks: 'all'
dans votre configuration webpack:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
Cette option active la division intelligente du code. Avec cette option, webpack effectue automatiquement rechercher le code commun et l'extraire dans des fichiers distincts.
Vous pouvez également utiliser dans webpack 3 la commande CommonsChunkPlugin
.
Elle déplacera les dépendances courantes dans un nouveau fichier spécifié:
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2 // 2 is the default value
})
]
};
N'hésitez pas à jouer avec la valeur minChunks
pour trouver la meilleure. Généralement,
vous devez la garder petite, mais augmenter
si le nombre de morceaux augmente. Pour
Par exemple, pour 3 segments, minChunks
peut être égal à 2, mais pour 30 segments, il peut être égal à 8
car si vous conservez la valeur 2, un trop grand nombre de modules arriveront dans le fichier commun.
de la gonfler trop.
Documentation complémentaire
- Documentation Webpack sur le concept d'entrée points d'accès
- Documentation Webpack à propos de la CommonsChunkPlugin
- "Exploiter pleinement les CommonsChunkPlugin"
- Fonctionnement de
optimization.splitChunks
etoptimization.runtimeChunk
Renforcer la stabilité des ID de module
Lors de la création du code, webpack attribue un ID à chaque module. Par la suite, ces identifiants seront
utilisée dans les éléments require()
du bundle. Vous voyez généralement des ID dans la sortie de compilation
juste avant les chemins d'accès au module:
$ 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
↓ Ici
[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
Par défaut, les identifiants sont calculés à l'aide d'un compteur (c'est-à-dire que le premier module a l'identifiant 0, le second porte l'identifiant 1, et ainsi de suite). Le problème avec cela est que lorsque vous ajoutez un nouveau module, il peut apparaître au milieu de la liste des modules et modifier tous les modules suivants 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]
↓ Nous avons ajouté module...
[4] ./webPlayer.js 24 kB {1} [built]
↓ Et voilà le résultat ! comments.js
a désormais l'ID 5 au lieu de 4
[5] ./comments.js 58 kB {0} [built]
↓ ads.js
a désormais l'ID 6 au lieu de 5
[6] ./ads.js 74 kB {1} [built]
+ 1 hidden module
Cela invalide tous les fragments qui incluent ou dépendent de modules dont les ID ont été modifiés.
même si leur code réel n'a pas changé. Dans notre cas, le fragment 0
(le fragment
avec comments.js
) et le bloc main
(le bloc contenant l'autre code d'application) obtient
invalidée, alors que seule la main
aurait dû l'être.
Pour résoudre ce problème, modifiez le mode de calcul des identifiants de module à l'aide de la méthode
HashedModuleIdsPlugin
Il remplace les ID basés sur un compteur par des hachages de chemins d'accès aux modules:
$ 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
↓ Ici
[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
Ainsi, l'ID d'un module ne change que si vous le renommez ou le déplacez de ce module. Les nouveaux modules n'affectent pas le niveau ID.
Pour activer le plug-in, ajoutez-le à la section plugins
de la configuration:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};
Documentation complémentaire
- Documentation Webpack à propos de la HashedModuleIdsPlugin
Récapitulatif
- Mettre en cache le bundle et différencier les versions en modifiant son nom
- Diviser le bundle en code d'application, code de fournisseur et environnement d'exécution
- Intégrer l'environnement d'exécution pour enregistrer une requête HTTP
- Effectuer un chargement différé du code non critique avec
import
- Divisez le code par route/page pour éviter de charger des éléments inutiles