前言
今日群里小伙伴分享一题如下:
Webpack如果使用了hash命名,是否每次都会重新生成hash?简单说一下Webpack的几种hash策略?
Webpack hash分类
1.1 输出全部使用hash的情况
- 每个文件都具有相同的哈希值,因为它hash是基于我们使用的所有源文件生成的。
- 如果我们重新运行该构建而不更改任何内容,则生成的hash将保持不变。
- 如果我们仅编辑一个文件,则hash值将发生变化,并且所有生成捆绑的名称中都会包含此新hash。
图解:
1.2 输出的结果全部使用chunkhash的情况
- chunkhash是根绝不同的入口进行依赖文件解析,构建对应的chunk(模块),生成对应的hash值。
- 在使用上来说:我们可以把一些公共库和程序入口文件区分开来,单独打包构建,接着可以采用chunkhash方式来生成hash值,那么只要我们不改动公共库的代码,就可以保证其hash值不受影响,这样也能起到缓存的作用。
图解:
1.3 输出的结果全部使用contenthash的情况
- 每个生成的文件都名称都有一个唯一的hash值,该哈希值是根据该文件的内容计算得出的。
- 当要构建的文件内容发生改变时,就会生成新的hash值,且该文件的改变并不会影响和它同一个模块下的其他文件。
图解:
1.4 总结
所以如果使用了hash,并不是每次都会重新生成新的hash,需要看具体使用的哪种hash策略。
hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值。(粒度整个项目)chunkhash是根据不同的入口进行依赖文件解析,构建对应的chunk(模块),生成对应的hash值。只有被修改的chunk(模块)在重新构建之后才会生成新的hash值,不会影响其他的chunk。(粒度entry的每个入口文件)。contenthash是跟每个生成的文件有关,每个文件都有一个唯一的hash值。当要构建的文件内容发生改变时,就会生成新的hash值,且该文件的改变并不会影响和它同一个模块下的其他文件。(粒度每个文件的内容)。
综上所以,这道题如何回答呢~那就是 Webpack 中如果使用了hash命名,并不是每次都会重新生成新的hash,需要看具体使用的是哪种hash策略了。
hash(整个项目)、chunkhash(模块)、contenthash(每个文件)
扩展
Webpack的hash原理。
webpack的hash是通过crypto加密和哈希算法实现的,webpack提供了hashDigest(在生成 hash 时使用的编码方式,默认为 'hex')、hashDigestLength(散列摘要的前缀长度,默认为 20)、hashFunction(散列算法,默认为 'md5')、hashSalt(一个可选的加盐值)等参数来实现自定义hash;下面依次讲述三种hash生成策略
webpack的三种hash生成策略都是根据源码内容来生成,只是该源码已经被webpack封装成能在webpack环境中运行的代码了,包含每一个源文件的绝对路径;webpack会在build阶段根据源码给对应的模块(module)生成一个_buildHash(后续根据该值生成模块的hash).
如下图所示可以看到源码中包含绝对路径。
webpack 在seal阶段生成三种hash,最后根据output的配置决定使用哪种hash,webpack通过执行
hash 和chunkhash的生成过程
下面主要讲一下 hash的生成过程, 其中 chunkhash 的生成过程包含在其中。
webpack生成hash的步骤:
-
第一步:是获取 Compilation下面的所有modules,把所有的module在build阶段生成的_buildHash作为内容生产一个新的hash值;
-
第二步:获取到所有的代码块(chunks),分别把代码块(chunk)中包含的module的hash作为内容生成 代码块(chunk)的hash,该hash就是配置 chunkhash 时需要使用的hash值;
-
第三步(最后):把所有代码块(chunks)的hash作为内容生产一个hash就是最终的hash
如下源码所示。
// 非源码,代码有删减
createHash(){
//把所有的module根据在build阶段生成_buildHash来生成一个新的hash值
const modules = this.modules;
for(let i = 0; i < modules.length; i++){
cosnt module = modules[i];
const moduleHash = createHash(hashFuntion);
module.updateHash(moduleHash);
}
// clone needed as sort below is inplace mutation
const chunks = this.chunks.slice();
//给所有的chunks分别生成一个hash
for(let i=0; i < chunks.length; i++){
const chunk = chunks[i];
const chunkHash = createHash(hashFunction);
try{
chunk.updateHash(chunkHash);
// chunk中包含的所有module的hash作为内容生产一个hash值
template.updateHashForChunk(
chunkHash,
chunk,
this.moduleTemplates.javascript,
this.dependencyTemplates
);
chunk.hash = chunkHash.digest(hashDigest);
//把所有的chunks的hash作为内容
hash.update(chunk.hash);
//生成contetnHash
this.hooks.contentHash.call(chunk);
}catch(err){}
}
// 生成hash
this.fullHash = hash.digest(hashDigest);
this.hash = this.fullHash.substr(0,hashDigestLength);
}
contenthash生成过程
contenthash 生成与前两种hash生成方式不一样,它是通过mini-css-extract-plugin 和 JavascriptModulesPlugin 插件生成的hash;
mini-css-extract-plugin是webpack打包构建时把css类型的module单独分类出来的插件,使用该插件时会为css类型的文件单独生成hash;它会把代码块(chunk)中所有类型为css/mini-extract的module的hash作为内容生成chunkhash。
// mini-css-extract-plugin插件的css文件hash生成的钩子函数
compilation.hooks.contentHash.tap(pluginName, chunk =>{
const { outputOptions } = compilation;
const { hashFunction, hashDigest, hashDigestLength } = outputOptions;
const hash = createHash(hashFuntion);
//把chunk中所有类型为 css/mini-extract的module的hash作为内容生成hash
for(const m of chunk.modulesIterable){
if(m.type === MODULE_TYPE){
m.updateHash(hash);
}
}
const { contentHash } = chunk;
// 把生成的内容放入chunk对象的contentHash中
contentHash[MODULE_TYPE] = hash.digest(hashDigest).substring(0,hashDigestLength);
});
- contentHash钩子触发时会调用
JavascriptModulesPlugin插件注册的contentHash事件,把代码块(chunk)中所有类型为函数的module的hash作为内容生成hash。
// JavascriptModulesPlugin插件为js生成contentHash的钩子函数
compilation.hooks.contentHash.tap("JavascriptModulesPlugin",chunk =>{
//...此处有删减
for( const m of chunk.modulesIterable){
if(typeof m.source ==="function"){
hash.update(m.hash);
}
}
chunk.contentHash.javascript = hash.digest(hashDigest).substr(0.hashDigestLength);
});
参考:从源码看webpack的hash策略 https://juejin.cn/post/6844903942384517127