看这篇前,若是对浏览器缓存原理不太清楚的,先补习浏览器缓存这部分基础知识。若是对文件摘要算法不太清楚的,先补习文件摘要算法这部分基础知识。vue
用vue-cli搭建的项目,打包的时候会把node_modules目录下全部文件都分离出来单独生成vendor.js。node
// split vendor js into its own file new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: function (module, count) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, "../../node_modules") ) === 0 ); } }),
node_modules中的外部插件更新不频繁,因此单独生成文件能够利用浏览器缓存提高页面性能,这是个很是好的实践。jquery
实际工做中,若是只是修改已有组件中的代码,打包后的vendor.js的hash码是不会变的。But!只要我新写一个组件而后引入项目,打包出来的全部文件的hash码所有都会变动,包括vendor.js。webpack
参考https://webpack.js.org/guides...后,大概搞清楚了webpack缓存优化问题。web
假设如今项目就三个组件,只用到jquery。通常状况下,全部的组件在webpack打包后,都会以数组项的形式保存下来:算法
// main.js里包含三个组件,其对应的id为1,2,3 webpackJsonp([0],[ /* 0 */, /* 1 */ (function(module, __webpack_exports__, __webpack_require__){...}), /* 2 */ (function(module, __webpack_exports__, __webpack_require__){...}), /* 3 */ (function(module, __webpack_exports__, __webpack_require__){...}) ],[1]); //jquery.js分离出来的vendor.js包含了两个组件 webpackJsonp([1],[ /* 0 */ (function(module, exports, __webpack_require__) {...}), /* 1 */, /* 2 */, /* 3 */, /* 4 */ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(0); }) ],[4]);
因此在webpack内部,组件的调用,靠的是他们在数组中的index,项目中全部的组件都拥有一个惟一的index。当我新加一个组件进去的时候,main.js里的数组会被扩充,而vendor.js里的数组也会被重排,因此致使前面所说的问题:vue-cli
// 新增组件后,main.js里包含四个组件,其对应的id为1,2,3,4 webpackJsonp([0],[ /* 0 */, /* 1 */ (function(module, __webpack_exports__, __webpack_require__){...}), /* 2 */ (function(module, __webpack_exports__, __webpack_require__){...}), /* 3 */ (function(module, __webpack_exports__, __webpack_require__){...}), /* 4 */ (function(module, __webpack_exports__, __webpack_require__){...}) ],[1]); //jquery.js分离出来的vendor.js会被重排 webpackJsonp([1],[ /* 0 */ (function(module, exports, __webpack_require__) {...}), /* 1 */, /* 2 */, /* 3 */, /* 4 */, /* 5 */ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(0); }) ],[5]);
解决这一问题的方法官网推荐使用的是new webpack.HashedModuleIdsPlugin()这个插件,只要引入这个插件,问题就不存在了。好奇心让我点进去看了下这个插件究竟干了什么,怎么作到让vendor.js不受其外部包的影响的?segmentfault
发现代码异常短小精悍数组
const createHash = require("crypto").createHash; class HashedModuleIdsPlugin { constructor(options) { this.options = Object.assign({ hashFunction: "md5", hashDigest: "base64", hashDigestLength: 4 }, options); } apply(compiler) { const options = this.options; compiler.plugin("compilation", (compilation) => { const usedIds = new Set(); compilation.plugin("before-module-ids", (modules) => { modules.forEach((module) => { if(module.id === null && module.libIdent) { const id = module.libIdent({ context: this.options.context || compiler.options.context }); // 获取数据摘要方法,默认md5 const hash = createHash(options.hashFunction); // 将模块id进行摘要算法 hash.update(id); // 再进行base64转码 const hashId = hash.digest(options.hashDigest); let len = options.hashDigestLength; // 判断一下生成出来的base64前4位编码,是否和前面的组件有冲突,有的话就再加一位,直到没冲突了为止,保证每一个组件拥有惟一的编码。 while(usedIds.has(hashId.substr(0, len))) len++; // 将最终生成的编码赋值给module.id module.id = hashId.substr(0, len); // 将module.id保存到usedIds列表里,供后面执行的组件判断是否编码冲突 usedIds.add(module.id); } }); }); }); } } module.exports = HashedModuleIdsPlugin;
因此,就是用这样简单的方式,把原来的数据结构,换成了对象结构。用HashedModuleIdsPlugin插件打包后的vendor.js代码变成了这样:浏览器
//jquery.js分离出来的vendor.js会被重排 webpackJsonp([1],{ /***/ 0: /***/ (function(module, exports, __webpack_require__){...}), /***/ "tra3": /***/ (function(module, exports, __webpack_require__) {...}) },[0]);
这样就摆脱了数组引发的序号重排问题,从而保证只要jquery内部代码不变,这个vendor.js的hash就不会变。