看这篇前,如果对浏览器缓存原理不太清楚的,先补习浏览器缓存这部分基础知识。如果对文件摘要算法不太清楚的,先补习文件摘要算法这部分基础知识。
用vue-cli搭建的项目,打包的时候会把node_modules目录下所有文件都分离出来单独生成vendor.js。
// 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中的外部插件更新不频繁,所以单独生成文件可以利用浏览器缓存提升页面性能,这是个非常好的实践。
实际工作中,如果只是修改已有组件中的代码,打包后的vendor.js的hash码是不会变的。But!只要我新写一个组件然后引入项目,打包出来的所有文件的hash码全部都会变更,包括vendor.js。
参考https://webpack.js.org/guides...后,大概搞清楚了webpack缓存优化问题。
假设现在项目就三个组件,只用到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里的数组也会被重排,所以导致前面所说的问题:
// 新增组件后,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不受其外部包的影响的?
发现代码异常短小精悍
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就不会变。