The hash, chunkhash, contenthash differences in webpack4!

Posted by Bac on Sun, 16 Jan 2022 19:39:34 +0100

The responsiveness of the website is the first element of the user experience, and its importance is self-evident. Response speed is influenced by many factors, such as different business scenarios, different user terminals, different technology stacks.

For faster response, on the one hand, it is expected that each time a page resource is requested, it will be the most up-to-date resource. On the other hand, it is expected that the cache can be reused to improve page loading speed when the resources do not change.

By using the file name + file hash value, you can distinguish whether a resource has been updated as long as the file name is passed.

webpack has a built-in hash calculation method that adds hash fields to the output file for the generated file

Let's first look at the differences between hash, chunkhash, and contenthash in Web packs

Differences between hash, chunkhash, contenthash in webpack

hash

A hash is generated for each build. Regarding the entire project, hash is changed whenever there is a change to the project file.

Generally speaking, there is no opportunity to use hash directly. Hash is calculated based on the content of each project and can easily result in unnecessary hash changes that are detrimental to version management

chunkhash

Related to the chunk generated by the webpack package. Each entry has a different hash.

However, the hash value of the same module is the same even if js and CSS are separated. If you modify one, the hash values of js and CSS will change, which is the same as hash and does not make caching sense. For example, if you only change the css, not the js content, chunkhash will also change.

contenthash

Related to the contents of a single file. The hash changes when the contents of the specified file change.

For css files, MiniCssExtractPlugin is generally used to extract them into a single css file. You can use contenthash to mark to ensure hash can be updated when the contents of a css file change without affecting the hash of a js

  • file-splitting

Next, we will introduce the optimal file-splitting method to improve page responsiveness. The Webpack glossary describes two different ways of splitting files:

Bundle splitting: For better caching, you can split a large file into more, smaller files
Code splitting: On-demand loading, such as lazy page loading for SPA projects. Code splitting looks more attractive. In fact, many articles refer to Code splitting as the best way to reduce the size of js files and improve page responsiveness.
However, Bundle splitting is more worthwhile than Code splitting.

  • Bundle splitting

The principle behind Bundle splitting is very simple. Suppose you package the entire project into a larger file main.[contenthash].js, the value of contenthash changes when there are code changes, and the user needs to reload the latest main again. [new contenthash]. Js.

However, if you split the file into two files, the content of the changed file contenthash changes and the user needs to reload it, but the content of the other file does not update, the content of the dependent file does not change, and the browser loads it from the cache.

To better describe the problem, we create a scene, collect performance data, and compare:

  • Xiao Ming visited our website once a week for 10 weeks
  • We add a new feature to the website every week
  • Update Product List Page once a week
  • Product Detail Page has not been requested and will not be changed
  • In the fifth week, we added a new npm package
  • Week 9, we upgraded an existing npm package

First

The size of the packaged JavaScript file is 400 KB and the entire content is packaged in one dist/js/main.ab586865.js file.

The webpack configuration is as follows (unrelated content is not shown):

const path = require('path');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js'
  }
};

When the content of the code changes, different contenthash values will be generated, and users will need to load the latest main when they access it. JS file.

Contentnthash changes once a week when the site is updated, so every week users download 400 KB of files again.

After the tenth week, the file size has changed to 4.12MB.

The splitChunk feature of webpack4 splits the package into two files, main.js and vendor.js

Extract vendor package

The configuration is as follows:

const path = require('path');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js'
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    }
  }
};

Add optimization. SplitChunks. Chunks ='all', which packages all referenced third-party modules into vendor.js.

In this way, every time you modify your business code (without adding or updating npms), only main is available. The contenthash of JS changes. As a result, every time a user visits, he or she needs to reload the latest main.js file.

In the absence of new or updated node_ Vendor in the case of npm packages for modules. Js'contenthash does not change. The browser is loaded from the cache.

As you can see from the diagram, each user only needs to load 200 KB of main.js. Fifth week ago, vendor.js are unchanged and the browser loads from the cache.

  • Split npm package

vendors.js will also encounter main. The JS file has the same problem, and changing part of it means downloading the entire vendor again. Js.

So why not prepare a separate file for each npm package?

Therefore, splitting vue, vuex, vue-route, core-js into different files is a good choice.

The configuration is as follows:

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  plugins: [
    new webpack.HashedModuleIdsPlugin(), // so that file hashes don't change unexpectedly
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            //Get the name of each npm package
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

           //Prefix the package name of npm and remove @
            return `npm.${packageName.replace('@', '')}`;
          }
        }
      }
    }
  }
}

`A vue-cli initialized project package result:

  dist/npm.vue.44c71c1a.js           
  dist/npm.vue-router.0290a1da.js   
  dist/npm.core-js.028dc51e.js       
  dist/npm.vuex.6946f3d5.js          
  dist/app.e76cff0a.js               
  dist/runtime.4e174d8a.js          
  dist/npm.vue-loader.611518c6.js    
  dist/about.16c4e81c.js             
  dist/npm.webpack.034f3d3d.js      
  dist/css/app.ab586865.css

If you don't know splitChunks for Webpck, take a look at "Step by Step Understanding the splitChunks plug-in for webpack4", which is easy to understand. However, the improved default configuration of splitChunks in this article may not be appropriate for real business scenarios.

The following highlights cacheGroups

CacheGroups is the core configuration in splitChunks. SplitChunks splits modules based on cacheGroups, and chunks and other properties previously mentioned are configured for the cache group. SpitChunks has two cache groups by default, vendor-load content source node_modules, the other is default.

The name:string:Function value represents a separated chunk name. In the above configuration, the value of name is a Function, which is called by each parsed file and exported separately. For example, the vue-router export file is dist/npm.vue-router.0290a1da.js

The figure above shows the simulation results of configuring the output, where each npm package is output separately, in which case if one of the npm packages is updated, the caching of the other npm packages will not be affected.

Here, there may be three questions:

  • Question 1: With more files, will network requests slow down?
  • The answer is: NO!, It won't slow down. If you don't have hundreds of files, you don't have to worry about this at all with HTTP/2. Don't believe you can look at the results of the data analysis in two articles:
The Right Way to Bundle Your Assets for Faster Sites over HTTP/2
Forgo JS packaging? Not so fast
  • Question 2: Does each output file have an overhead/boilerplate code for the Webpack?
  • Answer: Yes
  • Question 3: Does this affect file compression
  • Answer: No.

summary

The smaller the split, the more files, there may be more Webpack helper code, and there will be less merge compression. However, through data analysis, the more files are split, the better the performance (it may be hard to convince you, but it is true)

  • Code splitting

On-demand loading, through the import() syntax of Webpack4, makes it easy to implement it.

It is also important to configure Babel, which is not detailed here. A new series will follow detailing how to configure Babel

How a project created by Vue-cli does Bundle splitting
By running npx vue inspect, you can see the default Webpack configuration for the project, where we intercept the output and optimization sections:

output: {
    path: path.resolve(__dirname, '/dist'),
    filename: 'js/[name].[contenthash:8].js',
    publicPath: '/',
    chunkFilename: 'js/[name].[contenthash:8].js'
},
optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    },
    minimizer: [
      {
        options: {
          test: /\.m?js(\?.*)?$/i,
          chunkFilter: () => true,
          warningsFilter: () => true,
          extractComments: false,
          sourceMap: true,
          cache: true,
          cacheKeys: defaultCacheKeys => defaultCacheKeys,
          parallel: true,
          include: undefined,
          exclude: undefined,
          minify: undefined,
          terserOptions: {
            output: {
              comments: /^\**!|@preserve|@license|@cc_on/i
            },
            compress: {
              arrows: false,
              collapse_vars: false,
              comparisons: false,
              computed_props: false,
              hoist_funs: false,
              hoist_props: false,
              hoist_vars: false,
              inline: false,
              loops: false,
              negate_iife: false,
              properties: false,
              reduce_funcs: false,
              reduce_vars: false,
              switches: false,
              toplevel: false,
              typeofs: false,
              booleans: true,
              if_return: true,
              sequences: true,
              unused: true,
              conditionals: true,
              dead_code: true,
              evaluate: true
            },
            mangle: {
              safari10: true
            }
          }
        }
      }
    ]
  }

A vue-cli project has two cache groupings (cacheGroups) by default.

Next we create the Vue under the project root directory. Config. JS file. Add the following configuration to override the default configuration:

module.exports = {
  configureWebpack: {
    optimization: {
      runtimeChunk: 'single',
      splitChunks: {
        chunks: 'all',
        maxInitialRequests: Infinity,
        minSize: 0,
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name (module) {
              // get the name. E.g. node_modules/packageName/not/this/part.js
              // or node_modules/packageName
              const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
              // https://docs.npmjs.com/cli/v7/configuring-npm/package-json
              // npm package name satisfies URL-safe
              return `npm.${packageName.replace('@', '')}`
            }
          }
        }
      }
    }
  }
}

Then run npm run build to see the output

$ vue-cli-service build

Building for production...


  File                                 Size   

  dist/js/chunk-vendors.bbe8cb82.js    132.82 KiB        
  dist/js/app.7cebea8f.js              4.18 KiB       
  dist/js/runtime.9ab490a2.js          2.31 KiB    
  dist/js/about.8c7b0bba.js            0.44 KiB        
  dist/css/app.ab586865.css            0.42 KiB

Last

If in doubt, you can follow the public number below for advice!
There are all kinds of new front-end technology blog updates, students who want to learn to pay attention to it! 😌 😌 😌

Topics: Webpack