设为首页 收藏本站
查看: 2045|回复: 0

[经验分享] Webpack中hash与chunkhash的区别,以及js与css的hash指纹解耦方案

[复制链接]

尚未签到

发表于 2017-2-24 08:41:15 | 显示全部楼层 |阅读模式
  文件的hash指纹通常作为前端静态资源实现增量更新的方案之一,Webpack是目前最流行的开源编译工具之一,其强大的功能也带来很多坑(当然,大部分麻烦其实都可以在官方文档中找到答案)。
  比如,在Webpack编译输出文件的配置过程中,如果需要为文件加入hash指纹,Webpack提供了两个配置项可供使用:hash和chunkhash。那么两者有何区别呢?其各自典型的应用场景又是什么?本文结合笔者工作中遇到的问题,简单记录一下以上问题的解决方案。

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指纹。如下:
  
DSC0000.png
  这样带来的问题是,三个js文件任何一个改动都会影响另外两个文件的最终文件名。上线后,另外两个文件的浏览器缓存也全部失效。这肯定不是我们想要的结果。
  那么如何避免这个问题呢?
  答案就是chunkhash!
  根据chunkhash的定义知道,chunkhash是根据具体模块文件的内容计算所得的hash值,所以某个文件的改动只会影响它本身的hash指纹,不会影响其他文件。配置webpack的output如下:
  

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

  编译输出的文件为:
  
DSC0001.png
  每个文件的hash指纹都不相同,上线后无改动的文件不会失去缓存。
  说来说去,好像chunkhash可以完全取代hash,那么hash就毫无用处吗?

1.2 hash应用场景
  接上文所述,webpack的hash字段是根据每次编译compilation的内容计算所得,也可以理解为项目总体文件的hash值,而不是针对每个具体文件的。
  webpack针对compilation提供了两个hash相关的生命周期钩子:before-hash和after-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;
  var chunkHash = require(&quot;crypto&quot;).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(&quot;chunk-hash&quot;, chunk, chunkHash);
  chunk.hash = chunkHash.digest(hashDigest);
  hash.update(chunk.hash);
  chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
  
}
  webpack使用NodeJS内置的crypto模块计算chunkhash,具体使用哪种算法与我们讨论的内容无关,我们只需要关注上述代码中this.applyPlugins(&quot;chunk-hash&quot;, chunk, chunkHash);的执行时机。
  chunk-hash是在chunhash计算完毕之后执行的,这就意味着如果我们在chunk-hash钩子中可以用新的chunkhash替换已存在的值。如下伪代码:
  

compilation.plugin(&quot;chunk-hash&quot;, 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: &quot;[chunkhash].chunk.js&quot;
  },
  plugins: [
  new WebpackMd5Hash()
  ]
  
};
  

3. 结语
  静态资源的版本管理是前端工程化中非常重要的一环,使用webpack作为构建工具时需要谨慎使用hash和 chunkhash,并且还需要注意webpack将一切视为js模块这种理念带来的一些不便。
  webpack可以说是目前最流行的构建工具了,但是其官方文档太过笼统,许多细节并未列出,需要研究源码才会了解。好在我们并非独立战斗,庞大的社区资源也是促进webpack流行的重要因素之一。
  行文至此,常规的前端项目中关于静态资源hash指纹的问题基本得到了解决,但是前端的环境是复杂的,各种新技术新框架层出不穷。最后留一点悬念给大家:像vue这种将template/js/style统统写在一个js文件中,如何保证在只修改了style时不影响编译输出的js文件hash指纹?
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-346382-1-1.html 上篇帖子: 持续部署单页应用的7大技巧 下篇帖子: 那些年我们写过的爬虫
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表