Webpack的hash策略

2,705 阅读4分钟

前言

今日群里小伙伴分享一题如下:

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-pluginJavascriptModulesPlugin 插件生成的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