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

In order to get faster response speed, on the one hand, it is expected that every time a page resource is requested, it is the latest resource. On the other hand, it is expected that the cache can be reused to improve page loading speed when the resource does not change.

Using file name + file hash, it can be realized as long as the file name, you can distinguish whether the resource has been updated.

Webpack has a built-in hash method, and for generated files you can add hash fields to the output file

Take a look at the differences between Hash, Chunkhash, and Contenthash in Webpack

Hash, Chunkhash, and Contenthash are different in webpack

hash

Each build generates a hash. With respect to the entire project, the hash changes whenever a project file changes.

In general, there is little opportunity to use hash directly. Hash is calculated based on the content of each project, which is easy to cause unnecessary hash changes, which is not conducive to version management

chunkhash

Related to chunks generated by webpack packaging. Each entry has a different hash.

However, the same module, even if js and CSS are separated, its hash value is the same, modify a place, JS and CSS hash value will change, the same hash, no cache significance. For example, if you only change the CSS without changing the JS content, chunkhash will also change.

contenthash

Is related to the contents of a single file. If the content of the specified file changes, the hash changes.

For CSS files, the MiniCssExtractPlugin is typically used to extract them as a single CSS file. You can use contenthash to make sure that when the CSS file content changes, the HASH can be updated without affecting the JS hash

file-splitting

Next we’ll look at the best file-splitting method to speed up page responses. The Webpack vocabulary describes two different ways to split files:

  • Bundle Splitting: Splitting a large file into more and smaller files for better caching
  • Code Splitting: Loading on demand, such as lazy loading of pages for SPA projects.Code splittingIt looks more attractive. In fact, a lot of the articlesCode splittingThis approach is seen as the best way to reduce the size of JS files and improve the speed of page response.

But Bundle splitting is worth more than Code splitting.

Bundle splitting

The principle behind Bundle Splitting is very simple. If the entire project is packaged into a large file main.[contenthash].js, the contenthash value will change when code changes, and the user will need to reload the latest main.[new Contenthash].js file.

However, if you split two files, the contenthash file with the changed content will change and the user will need to reload it, but the other file, depending on the file’s content, will not change and the browser will load the contenthash from the cache.

To better visualize the problem, we created a scenario, collected performance data, and compared:

  • Xiao Ming visited our website once a week for 10 weeks
  • We add a new feature to the site every week
  • Update the Product List page once a week.
  • The “Product details page” is not in demand and will not change
  • In week 5, we added an NPM package
  • In week 9, we upgraded an existing NPM package

First of all,

The size of our packed JavaScript file is 400KB, and all contents are packed into a file dist/js/main.ab586865.js.

The WebPack configuration is as follows (irrelevant 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'}};Copy the code

When the content of the code changes, different contenthash values are generated, and users need to load the latest main.js file to access it.

Contenthash changes every week when the site is updated, so every week users have to re-download 400KB of files.

By week 10, the file size had grown to 4.12MB.

Use webPack4’s splitChunk feature to split the package into two files -main.js and vendor.js

Extract the 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'}}};Copy the code

Increase of optimization. SplitChunks. Chunks = ‘all’, the reference of third-party modules (node_modules) all package to vendor. The js.

In this way, only the Contenthash in main.js changes every time the business code is modified (without adding or updating the NPM). As a result, users need to reload the latest main.js file each time they access.

Vendor.js contenthash does not change without adding or updating the NPM package for node_modules. The browser will load it from the cache.

As you can see, the user only needs to load 200KB of main.js each time. Vendor.js will remain unchanged until week 5, and the browser will load it from cache.

Break up the NPM package

Vendor.js also suffers from the same problems as the main.js file, and making changes to part of it means redownloading the entire vendor.js.

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

Therefore, it is a good choice to separate Vue, vuex, VUe-route and core-js into different files.

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];

           // Add a prefix to the package name of NPM and remove the @
            return `npm.${packageName.replace(The '@'.' ')}`;
          }
        }
      }
    }
  }
}
Copy the code

‘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  
Copy the code

If you are not familiar with splitChunks in Webpck, take a look at the Step by step webPack4 splitChunk extension, which is easy to understand. However, the default configuration of splitChunks improved in this article may not be suitable for real business scenarios.

Let’s focus on cacheGroups

CacheGroups are the core configuration of splitChunks. SplitChunks split modules according to the cacheGroups, and the chunks and other properties mentioned earlier are configured for cacheGroups. SplitChunks has two cache groups by default, vendor-load content source node_modules and default.

Name :string:Function The value indicates the name of chunk separated. In the above configuration, the value of name is a Function that is called for each parsed file, exporting the corresponding name separately. For example, the vue-router export file is dist/npm.vue-router.0290a1da.js

The figure above shows the simulation results of configuration output. Each NPM package is output separately. In this case, if one of the NPM packages is updated, the cache of the other NPM packages will not be affected.

At this point, some people may have the following three questions:

Q1: Do network requests slow down as there are more files?

The answer is: NO! It will not slow down. If you don’t have hundreds of files, you don’t have to worry about this when using HTTP/2. If you don’t believe me, take a look at the data analysis results of the two articles:

  • The Right Way to Bundle Your Assets for Faster Sites over HTTP/2
  • Forgo JS packaging? Not so fast

Overhead /boilerplate code exists for each output file

Answer: Yes

Fault 3: Whether file compression is affected

Answer: No.

conclusion

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

Code splitting

Load on demand, which has been made easy by Webpack4’s import() syntax.

How do you configure Babel is also very important, and I’m not going to do that in detail, but I’ll do a series on how to configure Babel, okay

How to do Bundle Splitting for vuE-CLI created projects

Run NPX vue inspect to see the default Webpack configuration for your project. Here 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}}}}]}Copy the code

Vue-cli projects have two cacheGroups by default.

Next we create the vue.config.js file under the project root. 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
              // The NPM package name meets the url-safe requirement
              return `npm.${packageName.replace(The '@'.' ')}`
            }
          }
        }
      }
    }
  }
}
Copy the code

Then run NPM run build to see the output

$ vue-cli-service build

Building forproduction... The File Size dist/js/the chunk - vendors. Bbe8cb82. Js 132.82 KiB dist/js/app. 7 cebea8f. Js 4.18 KiB dist/js/runtime. 9 ab490a2. Js 2.31 KiB dist/js/about. 8 c7b0bba. Js 0.44 KiB dist/CSS/app. Ab586865. CSS 0.42 KiBCopy the code

The last

Due to my limited language ability, you may still have a lot of doubts after reading it, I suggest you take a look at the reference links provided below to further deepen the impression.

Refer to the link

  • The 100% correct way to split your chunks with Webpack
  • Learn about webPack4’s splitChunk plug-in step by step
  • SplitChunks for Webpack
  • Hash, Chunkhash, and Contenthash are different in webpack