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

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

1. hash与chunkhash

首先我们先看一下官方文档对于两者的定义:

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

hash代表的是compilation的hash值。

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

chunkhash代表的是chunk的hash值。

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

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

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

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

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作为构建工具时需要谨慎使用hash和 chunkhash,并且还需要注意webpack将一切视为js模块这种理念带来的一些不便。

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

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

转自:http://www.cnblogs.com/ihardcoder/p/5623411.html

Webpack中hash与chunkhash的区别,以及js与css的hash指纹解耦方案的更多相关文章

  1. 配置webpack中externals来减少打包后vendor.js的体积

    在日常的项目开发中,我们会用到各种第三方库来提高效率,但随之带来的问题就是打包后的vendor.js体积过大,导致加载时空白页时间过长,给用户的体验太差.为此我们需要减少vendor.js的体积,从本 ...

  2. [转]webpack中require和import的区别

    webpack中可以写commonjs格式的require同步语法,可以写AMD格式的require回调语法,还有一个require.ensure,以及webpack自己定义的require.incl ...

  3. webpack中imports-loader,exports-loader,expose-loader的区别

    Webpack有几个和模块化相关的loader,imports-loader,exports-loader,expose-loader,比较容易混淆.今天,我们来理一理. imports-loader ...

  4. 【转】在ASP.NET MVC中,使用Bundle来打包压缩js和css

    在ASP.NET MVC4中(在WebForm中应该也有),有一个叫做Bundle的东西,它用来将js和css进行压缩(多个文件可以打包成一个文件),并且可以区分调试和非调试,在调试时不进行压缩,以原 ...

  5. 在ASP.NET MVC中,使用Bundle来打包压缩js和css(转)

    转自:http://www.cnblogs.com/xwgli/p/3296809.html 在ASP.NET MVC4中(在WebForm中应该也有),有一个叫做Bundle的东西,它用来将js和c ...

  6. webpack中require和import的区别

    commonjs同步语法 经典的commonjs同步语法如下: var a = require('./a'); a.show(); 此时webpack会将a.js打包进引用它的文件中.这是最普遍的情形 ...

  7. Webpack 中 file-loader 和 url-loader 的区别

    如果我们希望在页面引入图片(包括img的src和background的url).当我们基于webpack进行开发时,引入图片会遇到一些问题. 其中一个就是引用路径的问题.拿background样式用u ...

  8. webpack中使用html-webpack-plugin生成HTML文件并主动插入css和js引入标签

    html-webpack-plugin clean-webpack-plugin 一.html-webpack-plugin 由于打包时生成的css样式文件和js脚本文件会采用hash值作为文件命名的 ...

  9. webpack的最简单应用,只使用js与css的打包

    首先进行全局安装webpack npm install -g webpack cmd跳转到项目的文件夹,安装webpack npm install --save-dev webpack 接着需要pac ...

随机推荐

  1. Lambda表达式的诞生过程

    这是一篇很经典的文章,解决了工作中一些使用过但是又不太明白的知识点,今天终于弄明白了.花了一晚上重新整的,坚决要分享出来!!! 那得从很久很久以前说起了,记得那个时候... 懵懂的记得从前有个叫委托的 ...

  2. 开启基本数据结构和算法之路--初识Graphviz

    在我的Linux刀耕开荒阶段,就想开始重拾C,利用C实现常用的基本数据结构和算法,而数据结构和算法的掌握的熟练程度正是程序的初学者与职业程序员的分水岭. 那么怎么开启这一段历程呢? 按照软件工程的思想 ...

  3. [Erlang 0122] Erlang Resources 2014年1月~6月资讯合集

    虽然忙,有些事还是要抽时间做; Erlang Resources 小站 2014年1月~6月资讯合集,方便检索.      小站地址: http://site.douban.com/204209/   ...

  4. .Net开源Excel、Word操作组件-NPOI、EPPlus、DocX

    一.NPOI 简介: NPOI is the .NET version of POI Java project. With NPOI, you can read/write Office 2003/2 ...

  5. Linux学习笔记(17) Shell编程之基础

    1. 正则表达式 (1) 正则表达式用来在文件中匹配符合条件的字符串,正则是包含匹配.grep.awk.sed等命令可以支持正则表达式:通配符用来匹配符合条件的文件名,通配符是完全匹配.ls.find ...

  6. Wiki安装(PHP +Sqlite+Cache)

    前期准备 PHP http://windows.php.net/download   WinCache Extension for PHP URL:http://sourceforge.net/pro ...

  7. Ioc和Ao使用扩展

    一.Bean作用域 spring容器创建的时候,会将所有配置的bean对象创建出来,默认bean都是单例的.代码通过getBean()方法从容器获取指定的bean实例,容器首先会调用Bean类的无参构 ...

  8. mysql max_allowed_packet 设置过小导致记录写入失败

    mysql根据配置文件会限制server接受的数据包大小. 有时候大的插入和更新会受max_allowed_packet 参数限制,导致写入或者更新失败. 查看目前配置 show VARIABLES ...

  9. [LeetCode] Duplicate Emails 重复的邮箱

    Write a SQL query to find all duplicate emails in a table named Person. +----+---------+ | Id | Emai ...

  10. [LeetCode] Divide Two Integers 两数相除

    Divide two integers without using multiplication, division and mod operator. If it is overflow, retu ...