看这篇前,如果对浏览器缓存原理不太清楚的,先补习浏览器缓存这部分基础知识。如果对文件摘要算法不太清楚的,先补习文件摘要算法这部分基础知识。

用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就不会变。


梦梦她爹
1.8k 声望122 粉丝