[Front-end must learn] Accurately control the problem of webpack processing file name hash

background knowledge

1. After the static resource is loaded for the first time, the browser will cache it. Generally, the same resource will not be requested again if the cache has not expired. So how to notify the browser that the resource has changed when the resource is updated? Resource file hashnaming is born to solve this problem;

2. For the browser, on the one hand, it expects to obtain the latest resources every time a page resource is requested; on the other hand, it expects to be able to reuse the cache object when the resource has not changed.

At this time, using the method of [file name + file hash value], you can distinguish whether the resource has been updated as long as you pass the file name.

written in front

1. webpack has a built-in hashcalculation method, and hashthe field can be added to the output file for the generated file.

2. There are three built-in webpack :hash

  • hash: Generated one per build hash. It is related to the entire project, as long as there are file changes in the project, it will change hash.
  • contenthash: related to the content of a single file. Changes are made when the contents of the specified file change hash.
  • chunkhash: Related to generated by webpack packaging. chunkEvery one entry, there will be useless hash.

text

1. A basic configuration

const path = require('path')

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js')
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  }
}

src/foo.jsThe content is as follows:

import React from 'react'
console.log(React.toString())

Note that output.filenameyou can also use [hash]instead of [chunkhash], but the two generated hashcodes are different.

Use hashas follows :

app.03700a98484e0f02c914.js  70.4 kB       0  [emitted]  app
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

Use chunkhashas follows :

app.f2f78b37e74027320ebf.js  70.4 kB       0  [emitted]  app
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

For a entrysingle , it doesn't matter which one is used. During the example [email protected], the version was used. This webpackversion has fixed hashthe problem that the string will change if the source code has not been changed. However, in the previous version, there may be inconsistencies in modifying the same unmodified codehash , so no matter whether there is a problem with the version you use , it is recommended to use the next configuration. Subsequent configurations chunkhashare hashgenerated using as .

二、hash vs chunkhash

Because webpackit needs to deal with the dependencies of different modules, it has a built-in jstemplate for handling dependencies (hereinafter referred to as runtime), jsso will also be packaged into our final bundle. In actual projects, we often need to separate this part of the code. For example, if we want to package the class library separately, if we do not runtimegenerate a separate one jsfor The code changes and changes, causing the class hashlibrary to change every time, so it doesn't make sense for us to separate the class library. So here we need to provide a runtimeseparate one js.

Modify the configuration as follows:

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js')
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

webpackIt is stated in the documentation that if webpack.optimize.CommonsChunkPluginyou namespecify a entryname that is not declared in , then he will package runtimethe code into this file, so you can specify whatever you like here.name

So what will it look like when it is packaged now?

app.aed80e077eb0a6c42e65.js    68 kB       0  [emitted]  app
runtime.ead626e4060b3a0ecb1f.js  5.82 kB       1  [emitted]  runtime
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

We can see that itapp is not the same as the . So what if we use instead of ?runtimehashhashchunkhash

app.357eff03ae011d688ac3.js    68 kB       0  [emitted]  app
runtime.357eff03ae011d688ac3.js  5.81 kB       1  [emitted]  runtime
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

From here you can hashsee chunkhashthe difference between and , chunkhashwhich will contain the difference chunkof ( chunkit can be understood as each entry), hashand all the packaged files are the same, so once your package output has multiple files, you It is bound to need to be used chunkhash.

Class library files are packaged separately

In general projects, our class library files are not updated frequently. For example react, more often we update business codes. Then we definitely hope that the class library code can be cached in the browser as long as possible, which requires us to package the class library file separately. How to do it?

Modify the configuration file:

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js'),
    vendor: ['react']  // 所有类库都可以在这里声明
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  },
  plugins: [
    // 单独打包,app中就不会出现类库代码
    // 必须放在runtime之前
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
    }),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

Then, let's perform the following packaging:

vendor.72d208b8e74b753cf09c.js    67.7 kB       0  [emitted]  vendor
    app.fdc2c0fe8694c1690cb3.js  494 bytes       1  [emitted]  app
runtime.035d95805255d39272ba.js    5.85 kB       2  [emitted]  runtime
   [7] ./src/foo.js 55 bytes {
    
    1} [built]
  [12] multi react 28 bytes {
    
    0} [built]
    + 11 hidden modules

vendorIt's appseparated from me , and hashmy body is different. It looks beautiful, doesn't it? It's too early to rejoice young people. Let's create a new file called, bar.jsthe code is as follows:

import React from 'react'

export default function() {
    
    
  console.log(React.toString())
}

Then modify foo.jsas follows :

import bar from './bar.js'
console.log(bar())

It can be seen from this modification that we have not modified the content related to the class library. There should still be only vendorin react, so vendorthe hashshould not change, so is the result as we wish?

vendor.424ef301d6c78a447180.js    67.7 kB       0  [emitted]  vendor
    app.0dfe0411d4a47ce89c61.js  845 bytes       1  [emitted]  app
runtime.e90ad557ba577934a75f.js    5.85 kB       2  [emitted]  runtime
   [7] ./src/foo.js 45 bytes {
    
    1} [built]
   [8] ./src/bar.js 88 bytes {
    
    1} [built]
  [13] multi react 28 bytes {
    
    0} [built]
    + 11 hidden modules

It's a pity that webpackit hit us hard in the face ╮(╯_╰)╭

What is the reason? This is because we have added one more file, which is one more module webpackfor default webpackthe modules of are named after an ordered sequence, that is [0,1,2....], we added a module halfway and the order of each module changes , vendorthe module of the module inside idhas changed , so hashalso changed. in conclusion:

1. appThe change is because the content has changed.
2. vendorThe change is because his content module.idhas changed
. 3. runtimeThe change is because it itself maintains the module dependency

So how to solve it?

NamedModulesPlugin 和 HashedModuleIdsPlugin

These two pluginlet us webpackno longer use numbers to name our modules, so that each module will have a unique name, and there will be no additions or deletions of modules that will cause idchanges in final hashchanges. how to use?

{
    
    
  plugins: [
    new webpack.NamedModulesPlugin(),
    // new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
    }),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

NamedModulePluginGenerally used in development, it allows us to see the name of the module, which is more readable, but the performance is relatively poor. HashedModuleIdsPluginIt is more recommended to use it in a formal environment.

Let's take a look at the results of two packages after using this plugin
. Before code modification:

vendor.91148d0e2f4041ef2280.js      69 kB       0  [emitted]  vendor
    app.0228a43edf0a32a59426.js  551 bytes       1  [emitted]  app
runtime.8ed369e8c4ff541ad301.js    5.85 kB       2  [emitted]  runtime
[./src/foo.js] ./src/foo.js 56 bytes {
    
    1} [built]
   [0] multi react 28 bytes {
    
    0} [built]
    + 11 hidden modules

After code modification:

vendor.91148d0e2f4041ef2280.js      69 kB       0  [emitted]  vendor
    app.f64e232e4b6d6a59e617.js  917 bytes       1  [emitted]  app
runtime.c12d50e9a1902f12a9f4.js    5.85 kB       2  [emitted]  runtime
[./src/bar.js] ./src/bar.js 88 bytes {
    
    1} [built]
   [0] multi react 28 bytes {
    
    0} [built]
[./src/foo.js] ./src/foo.js 43 bytes {
    
    1} [built]
    + 11 hidden modules

It can be seen vendorthat hashthere is no change, HashedModuleIdsPluginand the effect is the same.

async module

As our systems got bigger and there were many modules, if all the modules were bundled together at once, the first load would be very slow. At this time, we will consider doing asynchronous loading, which webpacknatively supports asynchronous loading and is very convenient to use.

Let's create another jscalled async-bar.js, foo.jsin :

import('./async-bar').then(a => console.log(a))

Pack:

      0.1415eebc42d74a3dc01d.js  131 bytes       0  [emitted]
 vendor.19a637337ab59d16fb34.js      69 kB       1  [emitted]  vendor
    app.f7e5ecde27458097680e.js    1.04 kB       2  [emitted]  app
runtime.c4caa7f9859faa94b02e.js    5.88 kB       3  [emitted]  runtime
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    0} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    2} [built]
   [0] multi react 28 bytes {
    
    1} [built]
[./src/foo.js] ./src/foo.js 92 bytes {
    
    2} [built]
    + 11 hidden modules

Well, at this time we have seen that our has vendorchanged , but the more frightening thing is yet to come. We have built another module called async-baz.js, which is also foo.jsquoted :

import('./async-baz').then(a => console.log(a))

Then pack it again:

      0.eb2218a5fc67e9cc73e4.js  131 bytes       0  [emitted]
      1.61c2f5620a41b50b31eb.js  131 bytes       1  [emitted]
 vendor.1eada47dd979599cc3e5.js      69 kB       2  [emitted]  vendor
    app.1f82033832b8a5dd6e3b.js    1.17 kB       3  [emitted]  app
runtime.615d429d080c11c1979f.js     5.9 kB       4  [emitted]  runtime
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    1} [built]
[./src/async-baz.js] ./src/async-baz.js 32 bytes {
    
    0} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    3} [built]
   [0] multi react 28 bytes {
    
    2} [built]
[./src/foo.js] ./src/foo.js 140 bytes {
    
    3} [built]
    + 11 hidden modules

hashchanged for each module . . .

Why did the module IDbecome ? ! !

Well, let’s get down to business, there is still a solution, that is NamedChunksPlugin, it was used to process each chunkname , it seems that in the latest version, this name can be packaged normally without this name. But here we can use it to handle the name of the asynchronous module webpack, pluginsadd the following code in the of:

new webpack.NamedChunksPlugin((chunk) => {
    
     
  if (chunk.name) {
    
     
    return chunk.name; 
  } 
  return chunk.mapModules(m => path.relative(m.context, m.request)).join("_"); 
})

Then execute the packaging, the results of the two times are as follows:

         app.5faeebb6da84bedaac0a.js    1.11 kB           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
     runtime.f263e4cd58ad7b17a4bf.js     5.9 kB       runtime  [emitted]  runtime
      vendor.05493d3691191b049e65.js      69 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    app} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo.js] ./src/foo.js 143 bytes {
    
    app} [built]
    + 11 hidden modules
         app.55e3f40adacf95864a96.js     1.2 kB           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
async-baz.js.a85440cf862a8ad3a984.js  144 bytes  async-baz.js  [emitted]
     runtime.deeb657e46f5f7c0da42.js    5.94 kB       runtime  [emitted]  runtime
      vendor.05493d3691191b049e65.js      69 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/async-baz.js] ./src/async-baz.js 32 bytes {
    
    async-baz.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    app} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo.js] ./src/foo.js 140 bytes {
    
    app} [built]
    + 11 hidden modules

It can be seen that the results are all using the name instead idof , and the places that have not been changed have not changed.

Pay attention to the logic code that generates chunkthe name you can change it according to your own needs

There will be some problems with the above method, such as using .vuethe file development mode, m.requestwhich is a large series of vue-loadergenerated codes, so an error will be reported when packaging. Of course, you can find the corresponding naming method by yourself. Here I recommend a webpacknatively supported method. When using import, write the following notes:

import(/* webpackChunkName: "views-home" */ '../views/Home')

Then you only need new NamedChunksPlugin()to , and you don’t need to spell the name yourself, because our asynchronous chunkalready has a name at this time.

Add more entries

Modify webpack.config.js:

{
    
    
  ...
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js'),
    vendor: ['react'],
    two: path.join(__dirname, 'src/foo-two.js')
  },
  ...
}

The additions enrtyare as follows :

// foo-two.js
import bar from './bar.js'
console.log(bar)

import('./async-bar').then(a => console.log(a))
// import('./async-baz').then(a => console.log(a))

Yes, it is exactly foo.jsthe same as , of course you can change the logic, just remember to quote bar.js.

Then we pack, the result. . .

         app.77b13a56bbc0579ca35c.js  612 bytes           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
     runtime.bbe8e813f5e886e7134a.js    5.93 kB       runtime  [emitted]  runtime
         two.9e4ce5a54b4f73b2ed60.js  620 bytes           two  [emitted]  two
      vendor.8ad1e07bfa18dd78ad0f.js    69.5 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    vendor} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo-two.js] ./src/foo-two.js 143 bytes {
    
    two} [built]
[./src/foo.js] ./src/foo.js 143 bytes {
    
    app} [built]
    + 11 hidden modules

How hashall have changed ah? ! ! !

Well, the reason is vendorthat as common chunkdoesn't just contain the parts we declare entryin , he also contains entrythe common code referenced in each , sometimes you may want this result, but in our case, this is the one I want to solve Question ლ(゚д゚ლ)

So what to do CommonsChunkPluginhere which can be used to tell webpackus that we vendorreally only want to include the content we declared:

{
    
    
  plugins: [
    ...
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
      minChunks: Infinity
    }),
  ]
}

This parameter means to include as little common code as possible in vendorit . So we pack again:

         app.5faeebb6da84bedaac0a.js    1.13 kB           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
     runtime.b0406822caa4d1898cb8.js    5.93 kB       runtime  [emitted]  runtime
         two.9be2d4a28265bfc9d947.js    1.13 kB           two  [emitted]  two
      vendor.05493d3691191b049e65.js      69 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    app} {
    
    two} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo-two.js] ./src/foo-two.js 143 bytes {
    
    two} [built]
[./src/foo.js] ./src/foo.js 143 bytes {
    
    app} [built]
    + 11 hidden modules

Well, the familiar taste.

At this point, the battle between webpackour changeshash with .webpack

Finally our configuration is as follows:

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

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js'),
    vendor: ['react'],
    two: path.join(__dirname, 'src/foo-two.js')
  },
  externals: {
    
    
    jquery: 'jQuery'
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  },
  plugins: [
    new webpack.NamedChunksPlugin((chunk) => {
    
     
      if (chunk.name) {
    
     
        return chunk.name; 
      } 
      return chunk.mapModules(m => path.relative(m.context, m.request)).join("_"); 
    }),
    new webpack.NamedModulesPlugin(),
    // new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
      minChunks: Infinity
    }),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

Supplementary knowledge points

The hash principle of webpack

webpackThe hashis cryptoimplemented through encryption and hashing algorithms, webpackproviding hashDigest(the encoding used hashwhen , the default is 'hex'), hashDigestLength(the prefix length of the hash digest, the default is 20), hashFunction(the hashing algorithm, the default is 'md5'), hashSalt(an optional Selected salt value) and other parameters to achieve custom hash;

CommonsChunkPlugin

I believe that if you have heard webpack4of the update of , the biggest feeling should be that it has been removed CommonsChunkPlugin. After all, this is the one that the change logofficial .

CommonsChunkPluginAfter deletion, change to use optimization.splitChunksfor module division. If you are interested, you can go to the following official detailed documents.

The official statement is that the default settings are already very good for most users, but there is one problem that needs to be paid attention to. The default configuration will only extract and split the modules of asynchronous requests. If you want entryto optimization.splitChunks.chunks = 'all'. You can study the rest by yourself.

Corresponding to the situation where we runtimesplit , there is now a configuration optimization.runtimeChunk, which truewill automatically split runtimethe file if it is set to .

UglifyJsPlugin

Now there is no need to use pluginthis , just use asoptimization.minimize , which will be done automatically below .trueproduction modetrue

optimization.minimizerIt is possible to configure your own compressor.

—————————— [End of text] ——————————

Front-end learning exchange group, if you want to come in face-to-face, you can join the group: 832485817 , 685486827 ;
Front-end top learning exchange group (1) Front-end top learning exchange group (2)

Written at the end: convention is better than configuration - the principle of simplicity in software development

—————————— 【End】 ——————————

My:
Personal website: https://neveryu.github.io/neveryu/
Github: https://github.com/Neveryu
Sina Weibo: https://weibo.com/Neveryu
WeChat: miracle421354532

For more learning resources, please pay attention to my Sina Weibo... ok

Guess you like

Origin blog.csdn.net/csdn_yudong/article/details/126094623