About precise control 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 hash
naming 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 hash
calculation method, and hash
the field can be added to the output file for the generated file.
2. There are three built-in webpack :hash
hash
: Generated one per buildhash
. It is related to the entire project, as long as there are file changes in the project, it will changehash
.contenthash
: related to the content of a single file. Changes are made when the contents of the specified file changehash
.chunkhash
: Related to generated by webpack packaging.chunk
Every oneentry
, there will be uselesshash
.
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.js
The content is as follows:
import React from 'react'
console.log(React.toString())
Note that output.filename
you can also use [hash]
instead of [chunkhash]
, but the two generated hash
codes are different.
Use hash
as follows :
app.03700a98484e0f02c914.js 70.4 kB 0 [emitted] app
[6] ./src/foo.js 55 bytes {
0} [built]
+ 11 hidden modules
Use chunkhash
as follows :
app.f2f78b37e74027320ebf.js 70.4 kB 0 [emitted] app
[6] ./src/foo.js 55 bytes {
0} [built]
+ 11 hidden modules
For a entry
single , it doesn't matter which one is used. During the example [email protected]
, the version was used. This webpack
version has fixed hash
the 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 chunkhash
are hash
generated using as .
二、hash vs chunkhash
Because webpack
it needs to deal with the dependencies of different modules, it has a built-in js
template for handling dependencies (hereinafter referred to as runtime
), js
so 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 runtime
generate a separate one js
for The code changes and changes, causing the class hash
library to change every time, so it doesn't make sense for us to separate the class library. So here we need to provide a runtime
separate 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'
})
]
}
webpack
It is stated in the documentation that if webpack.optimize.CommonsChunkPlugin
you name
specify a entry
name that is not declared in , then he will package runtime
the 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 ?runtime
hash
hash
chunkhash
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 hash
see chunkhash
the difference between and , chunkhash
which will contain the difference chunk
of ( chunk
it can be understood as each entry
), hash
and 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
vendor
It's app
separated from me , and hash
my 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.js
the code is as follows:
import React from 'react'
export default function() {
console.log(React.toString())
}
Then modify foo.js
as 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 vendor
in react
, so vendor
the hash
should 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 webpack
it hit us hard in the face ╮(╯_╰)╭
What is the reason? This is because we have added one more file, which is one more module webpack
for default webpack
the 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 , vendor
the module of the module inside id
has changed , so hash
also changed. in conclusion:
1. app
The change is because the content has changed.
2. vendor
The change is because his content module.id
has changed
. 3. runtime
The change is because it itself maintains the module dependency
So how to solve it?
NamedModulesPlugin 和 HashedModuleIdsPlugin
These two plugin
let us webpack
no 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 id
changes in final hash
changes. how to use?
{
plugins: [
new webpack.NamedModulesPlugin(),
// new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime'
})
]
}
NamedModulePlugin
Generally used in development, it allows us to see the name of the module, which is more readable, but the performance is relatively poor. HashedModuleIdsPlugin
It 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 vendor
that hash
there is no change, HashedModuleIdsPlugin
and 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 webpack
natively supports asynchronous loading and is very convenient to use.
Let's create another js
called async-bar.js
, foo.js
in :
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 vendor
changed , but the more frightening thing is yet to come. We have built another module called async-baz.js
, which is also foo.js
quoted :
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
hash
changed for each module . . .
Why did the module ID
become ? ! !
Well, let’s get down to business, there is still a solution, that is NamedChunksPlugin
, it was used to process each chunk
name , 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
, plugins
add 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 id
of , and the places that have not been changed have not changed.
Pay attention to the logic code that generates
chunk
the name you can change it according to your own needs
There will be some problems with the above method, such as using .vue
the file development mode, m.request
which is a large series of vue-loader
generated codes, so an error will be reported when packaging. Of course, you can find the corresponding naming method by yourself. Here I recommend a webpack
natively 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 chunk
already 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 enrty
are 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.js
the 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 hash
all have changed ah? ! ! !
Well, the reason is vendor
that as common chunk
doesn't just contain the parts we declare entry
in , he also contains entry
the 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 CommonsChunkPlugin
here which can be used to tell webpack
us that we vendor
really 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 vendor
it . 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 webpack
our 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
webpack
The hash
is crypto
implemented through encryption and hashing algorithms, webpack
providing hashDigest
(the encoding used hash
when , 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 webpack4
of the update of , the biggest feeling should be that it has been removed CommonsChunkPlugin
. After all, this is the one that the change log
official .
CommonsChunkPlugin
After deletion, change to use optimization.splitChunks
for 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 entry
to optimization.splitChunks.chunks = 'all'
. You can study the rest by yourself.
Corresponding to the situation where we runtime
split , there is now a configuration optimization.runtimeChunk
, which true
will automatically split runtime
the file if it is set to .
UglifyJsPlugin
Now there is no need to use plugin
this , just use asoptimization.minimize
, which will be done automatically below .true
production mode
true
optimization.minimizer
It 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 ;
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