Webpack中hash与chunkhash的区别,以及js与css的hash指纹解耦方案

文件的hash指纹一般做为前端静态资源实现增量更新的方案之一,Webpack是目前最流行的开源编译工具之一,其强大的功能也带来不少坑(固然,大部分麻烦其实均可以在官方文档中找到答案)。javascript

好比,在Webpack编译输出文件的配置过程当中,若是须要为文件加入hash指纹,Webpack提供了两个配置项可供使用:hashchunkhash。那么二者有何区别呢?其各自典型的应用场景又是什么?本文结合笔者工做中遇到的问题,简单记录一下以上问题的解决方案。css

1. hash与chunkhash

首先咱们先看一下官方文档对于二者的定义:html

[hash] is replaced by the hash of the compilation.前端

hash表明的是compilation的hash值。vue

[chunkhash] is replaced by the hash of the chunk.java

chunkhash表明的是chunk的hash值。node

chunkhash很好理解,chunk在Webpack中的含义咱们都清楚,简单讲,chunk就是模块chunkhash也就是根据模块内容计算出的hash值。webpack

那么该如何理解hash是compilation的hash值这句话呢?git

首先先讲解一下Webpack中compilation的含义。github

1.1 compilation

Webpack官方文档中How to write a plugin章节有对compilation的详解。

A compilation object represents a single build of versioned assets. While running Webpack development middleware, a new compilation will be created each time a file change is detected, thus generating a new set of compiled assets. A compilation surfaces information about the present state of module resources, compiled assets, changed files, and watched dependencies.

compilation对象表明某个版本的资源对应的编译进程。当使用Webpack的development中间件时,每次检测到项目文件有改动就会建立一个compilation,进而可以针对改动生产全新的编译文件。compilation对象包含当前模块资源、待编译文件、有改动的文件和监听依赖的全部信息。

与compilation对应的有个compiler对象,经过对比,能够帮助你们对compilation有更深刻的理解。

The compiler object represents the fully configured Webpack environment. This object is built once upon starting Webpack, and is configured with all operational settings including options, loaders, and plugins.

compiler对象表明的是配置完备的Webpack环境。 compiler对象只在Webpack启动时构建一次,由Webpack组合全部的配置项构建生成。

简单的讲,compiler对象表明的是不变的webpack环境,是针对webpack的;而compilation对象针对的是随时可变的项目文件,只要文件有改动,compilation就会被从新建立。

理解了compilation以后,再回头看hash的定义:

[hash] is replaced by the hash of the compilation.

compilation在项目中任何一个文件改动后就会被从新建立,而后webpack计算新的compilation的hash值,这个hash值即是hash

若是使用hash做为编译输出文件的hash指纹的话,以下:

output: {
    filename: '[name].[hash:8].js',
    path: __dirname + '/built'
}

hash是compilation对象计算所得,而不是具体的项目文件计算所得。因此以上配置的编译输出文件,全部的文件名都会使用相同的hash指纹。以下:

这样带来的问题是,三个js文件任何一个改动都会影响另外两个文件的最终文件名。上线后,另外两个文件的浏览器缓存也所有失效。这确定不是咱们想要的结果。

那么如何避免这个问题呢?

答案就是chunkhash

根据chunkhash的定义知道,chunkhash是根据具体模块文件的内容计算所得的hash值,因此某个文件的改动只会影响它自己的hash指纹,不会影响其余文件。配置webpack的output以下:

output: {
    filename: '[name].[chunkhash:8].js',
    path: __dirname + '/built'
}

编译输出的文件为:

每一个文件的hash指纹都不相同,上线后无改动的文件不会失去缓存。

说来讲去,好像chunkhash能够彻底取代hash,那么hash就毫无用处吗?

1.2 hash应用场景

接上文所述,webpack的hash字段是根据每次编译compilation的内容计算所得,也能够理解为项目整体文件的hash值,而不是针对每一个具体文件的。

webpack针对compilation提供了两个hash相关的生命周期钩子:before-hashafter-hash。源码以下:

this.applyPlugins("before-hash");
this.createHash();
this.applyPlugins("after-hash");

hash能够做为版本控制的一环,将其做为编译输出文件夹的名称统一管理,以下:

output: {
    filename: '/dest/[hash]/[name].js'
}

咱们不讨论这种方式的合理性和效率,这只是hash的一种应用场景。固然,hash还有其余的应用场景,不过笔者目前未接触过,欢迎你们补充。

2. js与css共用相同chunkhash的解决方案

webpack的理念是把全部类型的文件都以js为汇聚点,不支持js文件之外的文件为编译入口。因此若是咱们要编译style文件,惟一的办法是在js文件中引入style文件。以下:

import 'style/style.scss';

webpack默认将js/style文件通通编译到一个js文件中,能够借助extract-text-webpack-plugin将style文件单独编译输出。从这点能够看出,webpack将style文件视为js的一部分。

这样的模式下有个很严重的问题,当咱们但愿将css单独编译输出而且打上hash指纹,按照前文所述的使用chunkhash配置输出文件名时,编译的结果是js和css文件的hash指纹彻底相同。不管是单独修改了js代码仍是style代码,编译输出的js/css文件都会打上全新的相同的hash指纹。这种情况下咱们没法有效的进行版本管理和部署上线。

为何会产生这种问题呢?

2.1 chunkhash的计算模式

前文提到了webpack的编译理念,webpack将style视为js的一部分,因此在计算chunkhash时,会把全部的js代码和style代码混合在一块儿计算。好比main.js引用了main.scss:

import 'main.scss';
alert('I am main.js');

main.scss的内容以下:

body{
    color: #000;
}

webpack计算chunkhash时,以main.js文件为编译入口,整个chunk的内容会将main.scss的内容也计算在内:

body{
    color: #000;
}
alert('I am main.js');

因此,不管是修改了js代码仍是scss代码,整个chunk的内容都改变了,计算所得的chunkhash天然就不一样了。

那么如何解决这种问题呢?

2.2 contenthash

前文提到了使用extract-text-webpack-plugin单独编译输出css文件,形成上一节js/css共用hash指纹的配置为:

new ExtractTextPlugin('[name].[chunkhash].css');

extract-text-webpack-plugin提供了另一种hash值:contenthash。顾名思义,contenthash表明的是文本文件内容的hash值,也就是只有style文件的hash值。这个hash值就是解决上述问题的银弹。修改配置以下:

new ExtractTextPlugin('[name].[contenthash].css');

编译输出的js和css文件将会有其独立的hash指纹。

到这里是否是就找到完美的解决方案了呢?

远远没有!

结合上文提到的种种,考虑一下这个问题:若是只修改了main.scss文件,未修改main.js文件,那么编译输出的js文件的hash指纹会改变吗?

答案是确定的。

修改了main.scss编译输出的css文件hash指纹理所固然要更新,可是咱们并未修改main.js,但是js文件的hash指纹也更新了。这是由于上文提到的:

webpack计算chunkhash时,以main.js文件为编译入口,整个chunk的内容会将main.scss的内容也计算在内。

那么怎么解决这个问题呢?

很简单,既然咱们知道了webpack计算chunkhash的方式,那咱们就从这一点出发,尝试修改chunkhash的计算方式。

2.3 chunk-hash

此小节内容只适用于webpack1,webpack2已经修复了hash相关的计算规则。

chunk-hash并非webpack中另外一种hash值,而是compilation执行生命周期中的一个钩子。chunk-hash钩子表明的是哪一个阶段呢?请看webpack的Compilation.js源码中如下部分:

for(i = 0; i < chunks.length; i++) {
        chunk = chunks[i];
        var chunkHash = require("crypto").createHash(hashFunction);
        if(outputOptions.hashSalt)
            hash.update(outputOptions.hashSalt);
        chunk.updateHash(chunkHash);
        if(chunk.entry) {
            this.mainTemplate.updateHashForChunk(chunkHash, chunk);
        } else {
            this.chunkTemplate.updateHashForChunk(chunkHash);
        }
        this.applyPlugins("chunk-hash", chunk, chunkHash);
        chunk.hash = chunkHash.digest(hashDigest);
        hash.update(chunk.hash);
        chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
}

webpack使用NodeJS内置的crypto模块计算chunkhash,具体使用哪一种算法与咱们讨论的内容无关,咱们只须要关注上述代码中this.applyPlugins("chunk-hash", chunk, chunkHash);的执行时机。

chunk-hash是在chunhash计算完毕以后执行的,这就意味着若是咱们在chunk-hash钩子中能够用新的chunkhash替换已存在的值。以下伪代码:

compilation.plugin("chunk-hash", function(chunk, chunkHash) {
        var new_hash = md5(chunk);
        chunkHash.digest = function () {
        return new_hash;
    };
});

webpack之因此若是流行的缘由之一就是拥有庞大的社区和不可胜数的开发者们,实际上,咱们遇到的问题已经有先驱者帮咱们解决了。插件webpack-md5-hash即是上述伪代码的具体实现,咱们须要作的只是将这个插件加入到webpack的配置中:

var WebpackMd5Hash = require('webpack-md5-hash');

module.exports = {
    output: {
        //...
        chunkFilename: "[chunkhash].chunk.js"
    },
    plugins: [
        new WebpackMd5Hash()
    ]
};

3. 结语

静态资源的版本管理是前端工程化中很是重要的一环,使用webpack做为构建工具时须要谨慎使用hashchunkhash,而且还须要注意webpack将一切视为js模块这种理念带来的一些不便。

webpack能够说是目前最流行的构建工具了,可是其官方文档太过笼统,许多细节并未列出,须要研究源码才会了解。好在咱们并不是独立战斗,庞大的社区资源也是促进webpack流行的重要因素之一。

行文至此,常规的前端项目中关于静态资源hash指纹的问题基本获得了解决,可是前端的环境是复杂的,各类新技术新框架层出不穷。最后留一点悬念给你们:像vue这种将template/js/style通通写在一个js文件中,如何保证在只修改了style时不影响编译输出的js文件hash指纹?

相关文章
相关标签/搜索