Maison  >  Article  >  interface Web  >  Une brève discussion sur la pratique de la mise en cache persistante Webpack

Une brève discussion sur la pratique de la mise en cache persistante Webpack

亚连
亚连original
2018-05-26 16:04:331546parcourir

Cet article présente principalement la pratique de la mise en cache persistante Webpack. Je vais maintenant le partager avec vous et vous donner une référence.

Avant-propos

Récemment, j'ai examiné comment Webpack effectuait la mise en cache persistante et j'ai découvert qu'il y avait encore quelques pièges. J'ai juste le temps de les trier et de les résumer après. en lisant cet article, vous pouvez à peu près comprendre :

  1. Qu'est-ce que la mise en cache persistante et pourquoi faisons-nous de la mise en cache persistante ?

  2. Comment Webpack est-il persistant mise en cache ?

  3. Quelques notes sur la mise en cache du webpack.

Mise en cache persistante

Nous devons d'abord expliquer ce qu'est la mise en cache persistante, dans le contexte de la popularité actuelle des applications où le front-end et la séparation back-end est Dans ces circonstances, le HTML, le CSS et le js front-end existent souvent sur le serveur sous la forme d'un fichier de ressources statiques, et les données sont obtenues via des interfaces pour afficher du contenu dynamique. Cela implique la question de la manière dont l'entreprise déploie le code front-end, cela implique donc une question de déploiement des mises à jour. La page doit-elle être déployée en premier, ou les ressources doivent être déployées en premier ?

Déployez d'abord la page, puis déployez les ressources : Durant l'intervalle de temps entre les deux déploiements, si un utilisateur accède à la page, l'ancienne ressource sera chargée dans la nouvelle structure de page, et l'ancienne version de la La ressource sera considérée comme la nouvelle. Lorsque la version est mise en cache, le résultat est que l'utilisateur accède à une page avec un style désordonné. À moins d'être actualisée manuellement, la page restera dans un état désordonné jusqu'à l'expiration du cache de ressources.

Déployez d'abord les ressources, puis déployez les pages : pendant l'intervalle de temps de déploiement, les utilisateurs disposant de ressources mises en cache localement d'anciennes versions visitent le site Web. Étant donné que la page demandée est une version plus ancienne, la référence de la ressource n'a pas changé et le navigateur. L'utilisera directement. La mise en cache locale est normale, mais lorsque les utilisateurs qui n'ont pas de mise en cache locale ou dont le cache a expiré visitent le site Web, la page de l'ancienne version chargera la nouvelle version de la ressource, ce qui entraînera une erreur d'exécution de la page.

Nous avons donc besoin d'une stratégie de déploiement pour garantir que lorsque nous mettons à jour notre code en ligne, les utilisateurs en ligne puissent effectuer une transition en douceur et ouvrir notre site Web correctement.

Il est recommandé de lire d'abord cette réponse : Comment développer et déployer du code front-end dans une grande entreprise ?

Après avoir lu la réponse ci-dessus, vous comprendrez à peu près que la solution de mise en cache persistante la plus mature consiste désormais à ajouter une valeur de hachage après le nom de la ressource statique, car la valeur de hachage est générée à chaque fois que le fichier est modifié. est différent. L'avantage est de publier les fichiers de manière incrémentielle pour éviter d'écraser les fichiers précédents et de provoquer l'échec de l'accès des utilisateurs en ligne.

Car tant que les noms des ressources statiques (css, js, img) publiées à chaque fois sont uniques, alors je peux :

  1. Pour les fichiers html : Faire n'activez pas la mise en cache, mettez le HTML sur votre propre serveur, désactivez le cache du serveur, votre serveur ne fournit que des fichiers HTML et des interfaces de données

  2. Pour les js statiques, les CSS, les images, etc. Fichier : Activez le CDN et la mise en cache, et téléchargez les ressources statiques vers le fournisseur de services CDN. Nous pouvons activer la mise en cache à long terme des ressources. Le chemin de chaque ressource étant unique, cela n'entraînera pas l'écrasement de la ressource, garantissant ainsi la stabilité de la connexion en ligne. accès des utilisateurs.

  3. Chaque fois qu'une mise à jour est publiée, les ressources statiques (js, css, img) sont d'abord transférées vers le service cdn, puis le fichier html est téléchargé. Cela garantit que les anciens utilisateurs. L'accès normal permet aux nouveaux utilisateurs de voir de nouvelles pages.

Ce qui précède présente brièvement la solution de mise en cache persistante frontale la plus courante, alors pourquoi devons-nous faire une mise en cache persistante ?

Lorsqu'un utilisateur visite notre site pour la première fois à l'aide d'un navigateur, la page introduit une variété de ressources statiques. Si nous pouvons réaliser une mise en cache persistante, nous pouvons ajouter Cache- dans l'en-tête de réponse http ou Expire. champ pour définir le cache, le navigateur peut mettre en cache ces ressources localement une par une.

Si l'utilisateur doit demander à nouveau la même ressource statique lors de visites ultérieures et que la ressource statique n'a pas expiré, le navigateur peut utiliser directement le cache local au lieu de demander la ressource via le réseau.

Comment Webpack effectue la mise en cache persistante

Après avoir brièvement introduit la mise en cache persistante, ce qui suit est le point clé, alors comment devrions-nous faire la mise en cache persistante dans Webpack Eh bien, nous devons faites les deux choses suivantes :

  1. Assurez l'unicité de la valeur de hachage, c'est-à-dire générez une valeur de hachage unique pour chaque ressource empaquetée. Tant que le contenu empaqueté est incohérent, alors Le. les valeurs de hachage sont incohérentes.

  2. Pour garantir la stabilité de la valeur de hachage, nous devons nous assurer que lorsqu'un module est modifié, seule la valeur de hachage du fichier empaqueté affecté change, et la valeur de hachage du fichier empaqueté fichier sans rapport avec les modifications du module.

Le nom du fichier de hachage est la première étape pour implémenter la mise en cache persistante. Actuellement, webpack dispose de deux façons de calculer le hachage ([hash] et [chunkhash])

  1. hash signifie que chaque fois que webpack génère une valeur de hachage unique pendant le processus de compilation, elle sera recréée après la modification d'un fichier du projet, puis webpack calcule la nouvelle valeur de hachage.

  2. Chunkhash est une valeur de hachage calculée en fonction du module, donc les modifications apportées à un certain fichier n'affecteront que sa propre valeur de hachage et n'affecteront pas les autres fichiers.

Donc, si vous regroupez simplement tout dans le même fichier, alors le hachage peut vous satisfaire. Si votre projet implique le déballage, le chargement de modules, etc., alors votre Chunkhash est nécessaire pour garantir que seul le contenu pertinent. les valeurs de hachage des fichiers changent après chaque mise à jour.

Donc, notre configuration webpack avec cache persistant devrait ressembler à ceci :

module.exports = {
 entry: __dirname + '/src/index.js',
 output: {
 path: __dirname + '/dist',
 filename: '[name].[chunkhash:8].js',
 }
}

La signification du code ci-dessus est : utilisez index.js comme point d'entrée pour empaqueter tout le code Créer un fichier nommé index.xxxx.js et placez-le dans le répertoire dist. Nous pouvons désormais générer un fichier nouvellement nommé à chaque fois que nous mettons à jour le projet.

Si vous avez affaire à des scénarios simples, cela suffit, mais dans les grandes applications multipages, nous avons souvent besoin d'optimiser les performances de la page :

  1. Séparer Code métier et code tiers : la raison pour laquelle nous séparons le code métier et le code tiers est que le code métier est mis à jour fréquemment et que les mises à jour et les itérations du code tiers sont lentes, nous séparons donc le code tiers (bibliothèques, frameworks), qui peut utiliser pleinement le cache du navigateur pour charger des bibliothèques tierces.

  2. Chargement à la demande : Par exemple, lors de l'utilisation de React-Router, lorsque l'utilisateur a besoin d'accéder à un certain itinéraire, le composant correspondant sera alors chargé. au début, tous les composants de routage seront téléchargés localement.

  3. Dans les applications multipages, nous pouvons souvent extraire des modules communs, tels que l'en-tête, le pied de page, etc., de sorte que lorsque la page saute, ces modules communs existent dans le cache, vous peut le charger directement au lieu de faire une requête réseau.

Alors, comment décompresser et charger dans des modules, cela nécessite le plug-in intégré de webpack : CommonsChunkPlugin Ci-dessous, j'utiliserai un exemple pour expliquer comment configurer webpack.

Le code de cet article est sur mon Github Si vous êtes intéressé, vous pouvez le télécharger et y jeter un œil :

git clone https://github.com/happylindz/blog.git
cd blog/code/multiple-page-webpack-demo
npm install

Avant de lire le contenu suivant, je vous recommande fortement. lisez mes articles précédents : Une compréhension approfondie du mécanisme d'empaquetage de fichiers Webpack vous aidera à mieux implémenter la mise en cache persistante.

L'exemple est grossièrement décrit comme ceci : il se compose de deux pages pageA et pageB

// src/pageA.js
import componentA from './common/componentA';
// 使用到 jquery 第三方库,需要抽离,避免业务打包文件过大
import $ from 'jquery';
// 加载 css 文件,一部分为公共样式,一部分为独有样式,需要抽离
import './css/common.css'
import './css/pageA.css';
console.log(componentA);
console.log($.trim(' do something '));

// src/pageB.js
// 页面 A 和 B 都用到了公共模块 componentA,需要抽离,避免重复加载
import componentA from './common/componentA';
import componentB from './common/componentB';
import './css/common.css'
import './css/pageB.css';
console.log(componentA);
console.log(componentB);
// 用到异步加载模块 asyncComponent,需要抽离,加载首屏速度
document.getElementById('xxxxx').addEventListener('click', () => {
 import( /* webpackChunkName: "async" */
 './common/asyncComponent.js').then((async) => {
  async();
 })
})
// 公共模块基本长这样
export default "component X";

Le contenu de la page ci-dessus implique essentiellement les trois modes de fractionnement de nos modules : Spliter les bibliothèques communes, charger et divisez les modules communs à la demande. Ensuite, l'étape suivante consiste à configurer le webpack :

const path = require('path');

const webpack = require('webpack');

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {

 entry: {

 pageA: [path.resolve(__dirname, './src/pageA.js')],

 pageB: path.resolve(__dirname, './src/pageB.js'),

 },

 output: {

 path: path.resolve(__dirname, './dist'),

 filename: 'js/[name].[chunkhash:8].js',

 chunkFilename: 'js/[name].[chunkhash:8].js'

 },

 module: {

 rules: [

  {

  // 用正则去匹配要用该 loader 转换的 CSS 文件

  test: /.css$/,

  use: ExtractTextPlugin.extract({

   fallback: "style-loader",

   use: ["css-loader"]

  }) 

  }

 ]

 },

 plugins: [

 new webpack.optimize.CommonsChunkPlugin({

  name: 'common',

  minChunks: 2,

 }),

 new webpack.optimize.CommonsChunkPlugin({

  name: 'vendor',

  minChunks: ({ resource }) => (

  resource && resource.indexOf('node_modules') >= 0 && resource.match(/.js$/)

  )
 }),
 new ExtractTextPlugin({
  filename: `css/[name].[chunkhash:8].css`,
 }),
 ]
}

Le premier CommonsChunkPlugin est utilisé pour extraire les modules publics, ce qui équivaut à parler du patron du webpack. Si vous voyez un module chargé deux fois ou plus, alors. S'il vous plaît, aidez-moi à le déplacer vers le morceau commun. Le minChunks ici est de 2, et la granularité est la plus petite. Vous pouvez choisir combien de fois utiliser les modules avant de les extraire en fonction de votre situation réelle.

Le deuxième CommonsChunkPlugin est utilisé pour extraire des codes tiers, les extraire et déterminer si les ressources proviennent de node_modules. Si tel est le cas, cela signifie qu'il s'agit de modules tiers, puis extrayez-les. Cela équivaut à dire au responsable du webpack que si vous voyez certains modules provenant du répertoire node_modules et que leurs noms se terminent par .js, veuillez les déplacer vers le bloc du fournisseur. Si le bloc du fournisseur n'existe pas, créez-en un nouveau.

Quels sont les avantages de cette configuration ? À mesure que notre activité se développe, nous sommes susceptibles de nous appuyer de plus en plus sur des codes de bibliothèques tierces. Si nous configurons spécialement une entrée pour stocker du code tiers, alors notre webpack. .config.js deviendra :

// 不利于拓展
module.exports = {
 entry: {
 app: './src/main.js',
 vendor: [
  'vue',
  'axio',
  'vue-router',
  'vuex',
  // more
 ],
 },
}

Le troisième plug-in ExtractTextPlugin est utilisé pour extraire le CSS du fichier js emballé et générer un fichier CSS indépendant. Imaginez que lorsque vous modifiez simplement le style, la logique fonctionnelle de la page n'est pas modifiée. Vous ne voulez certainement pas que la valeur de hachage de votre fichier js change. Vous voulez absolument que CSS et js soient séparés les uns des autres et ne s'affectent pas.

Après avoir exécuté webpack, vous pouvez voir l'effet d'empaquetage :

├── css

│ ├── common.2beb7387.css

│ ├── pageA.d178426d.css

│ └── pageB.33931188.css

└── js

 ├── async.03f28faf.js

 ├── common.2beb7387.js

 ├── pageA.d178426d.js

 ├── pageB.33931188.js

 └── vendor.22a1d956.js

Vous pouvez voir que css et js ont été séparés, et nous avons divisé le module pour nous assurer que le morceau de module est Nature unique, chaque fois que vous mettez à jour le code, une valeur de hachage différente sera générée.

Avec l'unicité, nous devons assurer la stabilité de la valeur de hachage. Imaginez ce scénario. Vous ne voulez certainement pas que votre modification d'une certaine partie du code (module, css) provoque la valeur de hachage de. le fichier doit être complètement modifié, alors ce n'est évidemment pas judicieux, alors que devrions-nous faire pour minimiser le changement de valeur de hachage ?

En d'autres termes, nous devons découvrir les facteurs qui provoquent l'échec du cache lors de la compilation du webpack et trouver des moyens de les résoudre ou de les optimiser ?

Le changement de la valeur chunkhash est principalement causé par les quatre parties suivantes :

  1. Le code source contenant le module

  2. webpack utilisé Le code d'exécution qui commence à s'exécuter

  3. L'identifiant du module du module généré par webpack (y compris l'identifiant du module et l'identifiant du module dépendant référencé)

  4. chunkID

Tant qu'une partie de ces quatre parties change, le fichier fragmenté généré sera différent et le cache deviendra invalide. Les quatre parties seront introduites une par une ci-dessous :

1. Modifications du code source :

显然不用多说,缓存必须要刷新,不然就有问题了

二、webpack 启动运行的 runtime 代码:

看过我之前的文章:深入理解 webpack 文件打包机制 就会知道,在 webpack 启动的时候需要执行一些启动代码。

(function(modules) {

 window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {

 // ...

 };

 function __webpack_require__(moduleId) {

 // ...

 }

 __webpack_require__.e = function requireEnsure(chunkId, callback) {

 // ...

 script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"pageA","1":"pageB","3":"vendor"}[chunkId]||chunkId) + "." + {"0":"e72ce7d4","1":"69f6bbe3","2":"9adbbaa0","3":"53fa02a7"}[chunkId] + ".js";

 };

})([]);

大致内容像上面这样,它们是 webpack 的一些启动代码,它们是一些函数,告诉浏览器如何加载 webpack 定义的模块。

其中有一行代码每次更新都会改变的,因为启动代码需要清楚地知道 chunkid 和 chunkhash 值得对应关系,这样在异步加载的时候才能正确地拼接出异步 js 文件的路径。

那么这部分代码最终放在哪个文件呢?因为我们刚才配置的时候最后生成的 common chunk 模块,那么这部分运行时代码会被直接内置在里面,这就导致了,我们每次更新我们业务代码(pageA, pageB, 模块)的时候, common chunkhash 会一直变化,但是这显然不符合我们的设想,因为我们只是要用 common chunk 用来存放公共模块(这里指的是 componentA),那么我 componentA 都没去修改,凭啥 chunkhash 需要变了。

所以我们需要将这部分 runtime 代码抽离成单独文件。

module.exports = {

 // ...

 plugins: [

 // ...

 // 放到其他的 CommonsChunkPlugin 后面

 new webpack.optimize.CommonsChunkPlugin({

  name: 'runtime',

  minChunks: Infinity,
 }),
 ]
}

这相当于是告诉 webpack 帮我把运行时代码抽离,放到单独的文件中。

├── css

│ ├── common.4cc08e4d.css

│ ├── pageA.d178426d.css

│ └── pageB.33931188.css

└── js

 ├── async.03f28faf.js

 ├── common.4cc08e4d.js

 ├── pageA.d178426d.js

 ├── pageB.33931188.js

 ├── runtime.8c79fdcd.js

 └── vendor.cef44292.js

多生成了一个 runtime.xxxx.js,以后你在改动业务代码的时候,common chunk 的 hash 值就不会变了,取而代之的是 runtime chunk hash 值会变,既然这部分代码是动态的,可以通过 chunk-manifest-webpack-plugin 将他们 inline 到 html 中,减少一次网络请求。

三、webpack 生成的模块 moduleid

在 webpack2 中默认加载 OccurrenceOrderPlugin 这个插件,OccurrenceOrderPlugin 插件会按引入次数最多的模块进行排序,引入次数的模块的 moduleId 越小,但是这仍然是不稳定的,随着你代码量的增加,虽然代码引用次数的模块 moduleId 越小,越不容易变化,但是难免还是不确定的。

默认情况下,模块的 id 是这个模块在模块数组中的索引。OccurenceOrderPlugin 会将引用次数多的模块放在前面,在每次编译时模块的顺序都是一致的,如果你修改代码时新增或删除了一些模块,这将可能会影响到所有模块的 id。

最佳实践方案是通过 HashedModuleIdsPlugin 这个插件,这个插件会根据模块的相对路径生成一个长度只有四位的字符串作为模块的 id,既隐藏了模块的路径信息,又减少了模块 id 的长度。

这样一来,改变 moduleId 的方式就只有文件路径的改变了,只要你的文件路径值不变,生成四位的字符串就不变,hash 值也不变。增加或删除业务代码模块不会对 moduleid 产生任何影响。

module.exports = {

 plugins: [

 new webpack.HashedModuleIdsPlugin(),

 // 放在最前面

 // ...

 ]

}

四、chunkID

实际情况中分块的个数的顺序在多次编译之间大多都是固定的, 不太容易发生变化。

这里涉及的只是比较基础的模块拆分,还有一些其它情况没有考虑到,比如异步加载组件中包含公共模块,可以再次将公共模块进行抽离。形成异步公共 chunk 模块。有想深入学习的可以看这篇文章:Webpack 大法之 Code Splitting

webpack 做缓存的一些注意点

  1. CSS 文件 hash 值失效的问题

  2. 不建议线上发布使用 DllPlugin 插件

CSS 文件 hash 值失效的问题:

ExtractTextPlugin 有个比较严重的问题,那就是它生成文件名所用的[chunkhash]是直接取自于引用该 css 代码段的 js chunk ;换句话说,如果我只是修改 css 代码段,而不动 js 代码,那么最后生成出来的 css 文件名依然没有变化。

所以我们需要将 ExtractTextPlugin 中的 chunkhash 改为 contenthash,顾名思义,contenthash 代表的是文本文件内容的 hash 值,也就是只有 style 文件的 hash 值。这样编译出来的 js 和 css 文件就有独立的 hash 值了。

module.exports = {

 plugins: [

 // ...

 new ExtractTextPlugin({

  filename: `css/[name].[contenthash:8].css`,

 }),

 ]

}

如果你使用的是 webpack2,webpack3,那么恭喜你,这样就足够了,js 文件和 css 文件修改都不会影响到相互的 hash 值。那如果你使用的是 webpack1,那么就会出现问题。

具体来讲就是 webpack1 和 webpack 在计算 chunkhash 值得不同:

webpack1 在涉及的时候并没有考虑像 ExtractTextPlugin 会将模块内容抽离的问题,所以它在计算 chunkhash 的时候是通过打包之前模块内容去计算的,也就是说在计算的时候 css 内容也包含在内,之后才将 css 内容抽离成单独的文件,

那么就会出现:如果只修改了 css 文件,未修改引用的 js 文件,那么编译输出的 js 文件的 hash 值也会改变。

À cet égard, webpack2 a apporté des améliorations. Il calcule la valeur de hachage en fonction du contenu du fichier packagé, c'est donc après que ExtractTextPlugin ait extrait le code CSS, il n'y a donc pas de problème comme ci-dessus. Si malheureusement vous utilisez toujours webpack1, il est recommandé d'utiliser le plug-in md5-hash-webpack-plugin pour modifier la stratégie de calcul du hachage du webpack.

Il n'est pas recommandé d'utiliser le plug-in DllPlugin pour la publication en ligne

Pourquoi dites-vous cela ? Parce que récemment, un ami est venu me voir et m'a demandé pourquoi leur leader n'autorisait pas l'utilisation du plug-in DllPlugin en ligne. Pourquoi ?

DllPlugin lui-même présente plusieurs défauts :

  1. Tout d'abord, vous devez configurer une configuration Webpack supplémentaire, ce qui augmente la charge de travail.

  2. L'une des pages utilise une grande bibliothèque de dépendances tierce et les autres pages n'ont pas du tout besoin de l'utiliser, mais cela ne vaut pas la peine de la conditionner directement dans dll.js . Ce code inutile doit être chargé à chaque ouverture de la page et la fonction Code Splitting de webpack2 ne peut pas être utilisée.

  3. Vous devez télécharger le fichier dll lorsque vous l'ouvrez pour la première fois. Parce que vous avez regroupé de nombreuses bibliothèques, le fichier dll est très volumineux et la vitesse de chargement des pages est très lente. pour la première fois.

Bien que vous puissiez le regrouper dans un fichier dll et laisser le navigateur lire le cache, afin que vous n'ayez pas à le demander la prochaine fois. Par exemple, si vous en utilisez un. les fonctions lodash, et vous utilisez dll L'intégralité du fichier lodash sera importé, ce qui vous fera charger trop de code inutile, ce qui n'est pas propice au temps de rendu du premier écran.

Je pense que la bonne approche est :

  1. Les bibliothèques avec une forte intégrité comme React et Vue peuvent générer des bibliothèques tierces de fournisseurs Pour effectuer la mise en cache, parce que votre système technique général est fixe et qu'un système technique unifié est essentiellement utilisé dans un site, une bibliothèque de fournisseur est donc générée pour la mise en cache.

  2. Les bibliothèques de composants fonctionnels telles que antd et lodash peuvent être éliminées en secouant l'arborescence, ne laissant que du code utile. Ne le mettez jamais directement dans la bibliothèque tierce du fournisseur, sinon vous l'exécuterez. beaucoup de code inutile.

Conclusion

D'accord, j'ai l'impression d'avoir encore beaucoup divagué, j'ai vraiment beaucoup gagné en lisant Webpack récemment. J'espère que vous pourrez apprendre de l'article. Vous pouvez également gagner quelque chose. De plus, je recommande à nouveau l'article que j'ai écrit précédemment, qui peut mieux vous aider à comprendre le mécanisme de mise en cache des fichiers : Compréhension approfondie du mécanisme d'empaquetage de fichiers Webpack

Ce qui précède est ce que j'ai compilé pour tout le monde, j'espère. cela sera utile à tout le monde à l’avenir.

Articles associés :

Un module d'enregistrement des utilisateurs ajax très pratique

Cliquez Ajax pour charger en continu la liste des données (tutoriel graphique)

Ajax+Struts2 implémente la fonction de vérification du code de vérification (tutoriel graphique)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn