浏览器在多次访问静态资源文件的时候,如果该文件已经在本地缓存,并且它的名称没有改变,那么浏览器会认为它没有被更新,便不会去请求服务器,而是使用该静态资源的缓存版本。如果某个文件被更改,我们希望浏览器应该重新请求它,而不是使用缓存版本,所以文件被修改后,它的名称也应该被改变。
在 webpack 中,最后生成的 chunk 文件都是由一个个的 module 编译后组合而成的,浏览器请求的 JS 文件实际上就是一个个的 chunk 。当我们修改某个 module 后,其所属 chunk 打包后的 JS 文件名称应该也要发生变化,否则浏览器是不会请求最新版本的 JS 文件的!
输出文件名
在 webpack 中,有 3 种哈希占位符,通过它们修改文件名来控制缓存。
1. hash:只要改动某个模块,所有 chunk 的 hash 都会被修改(不常用);
2. chunkhash:只要改动某个模块,依赖它的 chunk 的 hash 就会被修改(常用);
3. contenthash:只要改动某个模块,这个模块的 hash 就会被更改 ,通常用于被抽离的代码,如抽离 css 。如果不抽离该模块,contenthash 和 chunkhash 的产生的效果一致 (常用,webpack3 不支持 contenthash);
当 hash 变化后,文件名就会变化(如果输出文件名使用了上面的 3 中哈希占位符),浏览器就会去重新请求对应的资源。
[为了方便测试,我们打开 webpack 配置的 watch 选项。]
hash
准备测试的文件。
index1.js
console.log('index1.js');
import('./async').then((res) => {
console.log(res);
})
index2.js
console.log('index2.js');
import('./async').then((res) => {
console.log(res);
})
async.js
console.log('async.js');
export default {
desc: 'async.js'
}
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
watch: true,
entry: {
index1: './index1.js',
index2: './index2.js'
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].[hash:16].js'
},
module: {
rules: []
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
]
}
编译后控制台打印如下。
Asset Size Chunks Chunk Names
0.a535734c8f8ddf89.js 515 bytes 0 [emitted] [immutable]
index.html 374 bytes [emitted]
index1.a535734c8f8ddf89.js 8.54 KiB index1 [emitted] [immutable] index1
index2.a535734c8f8ddf89.js 8.53 KiB index2 [emitted] [immutable] index2
可以发现,所有 chunk 的 hash 都是 a535734c8f8ddf89 。
修改 async.js、index1.js 或 index2.js 其中的某个文件都将导致所有 chunk 重新构建,这里修改 async.js 后,控制台打印如下。
Asset Size Chunks Chunk Names
0.160f75be0b2a9d7a.js 519 bytes 0 [emitted] [immutable]
index.html 374 bytes [emitted]
index1.160f75be0b2a9d7a.js 8.54 KiB index1 [emitted] [immutable] index1
index2.160f75be0b2a9d7a.js 8.53 KiB index2 [emitted] [immutable] index2
可以发现,每次的改动都将导致整个项目重新构建。
chunkhash
把 webpack.config.js 中输出配置名称中的 hash 改为 chunkhash 。
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
watch: true,
entry: {
index1: './index1.js',
index2: './index2.js'
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].[chunkhash:16].js'
},
module: {
rules: []
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
]
}
编译构建后,控制台打印如下。
Asset Size Chunks Chunk Names
0.ef0a90ca4be41633.js 519 bytes 0 [emitted] [immutable]
index.html 374 bytes [emitted]
index1.e04fb78630b9ed06.js 8.55 KiB index1 [emitted] [immutable] index1
index2.a82fa8cf316672d3.js 8.54 KiB index2 [emitted] [immutable] index2
可以发现每个 chunk 都有自己的专属 hash,在修改 index1.js 或 index2.js 的时候,也只会导致被修改的文件所属的 chunk 重新构建。
修改 index1.js 后,控制台打印如下。
Asset Size Chunks Chunk Names
index.html 374 bytes [emitted]
index1.219644b9fa78ecf2.js 8.53 KiB index1 [emitted] [immutable] index1
修改 index2.js 后,控制台打印如下。
Asset Size Chunks Chunk Names
index.html 374 bytes [emitted]
index2.2fb396992e95ea30.js 8.53 KiB index2 [emitted] [immutable] index2
修改 async.js 后,控制台打印如下。
Asset Size Chunks Chunk Names
0.ef0a90ca4be41633.js 519 bytes 0 [emitted] [immutable]
index.html 374 bytes [emitted]
index1.b19fb895cd7844da.js 8.53 KiB index1 [emitted] [immutable] index1
index2.89c07defcd285597.js 8.53 KiB index2 [emitted] [immutable] index2
index1.js 和 index2.js 所属的 chunk 重新构建了,这是因为 index1 chunk 和 index2 chunk 需要懒加载 async.js ,而懒加载 async.js 是根据 async.js 所属的 chunk 打包后的文件名来加载的,当 async.js 发生变化后,它所属的 chunk 打包后的文件名会因为 hash 变化而变化,index1 chunk 和 index2 chunk 中懒加载 相关的的路径名称也跟着发生了变化,从而导致 index1 chunk 和 index2 chunk 的 hash 变化而重新构建。
因为懒加载流程是属于 runtime (安装、加载和连接模块的逻辑)部分的代码,所以可以将 runtime 部分的代码抽离出来,然后再次修改 async.js ,发现 index1 chunk 和 index2 chunk 的代码就不会重新构建了。
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
watch: true,
entry: {
index1: './index1.js',
index2: './index2.js'
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].[chunkhash:16].js'
},
module: {
rules: []
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
],
optimization: {
// 'single' - 表示抽离所有 chunk 公用的 runtime 代码
runtimeChunk: 'single'
}
}
编译打包后,控制台打印如下。
Asset Size Chunks Chunk Names
0.ef0a90ca4be41633.js 519 bytes 0 [emitted] [immutable]
index.html 425 bytes [emitted]
index1.ce2de40546b020f7.js 545 bytes index1 [emitted] [immutable] index1
index2.cf1286e262093666.js 553 bytes index2 [emitted] [immutable] index2
runtime.a0f05e8cde7456ae.js 8.97 KiB runtime [emitted] [immutable] runtime
修改 async.js 后,控制台打印如下。
Asset Size Chunks Chunk Names
0.0f79481d25aeed80.js 523 bytes 0 [emitted] [immutable]
index.html 425 bytes [emitted]
runtime.fa3fbcea2005af1c.js 8.97 KiB runtime [emitted] [immutable] runtime
这下 index1.js 和 index2.js 对应的 chunk 便不会重新构建了,因为我们把连接模块的逻辑都抽离出来了,改变 async.js 会导致 async.js 和 runtime 代码对应的 chunk 重新构建。
contenthash
contenthash 只有在抽离某个 module 的时候才会显示它的作用,否则它和 chunkhash 的表现一致,这里以抽离 css 为例。先安装 css-loader 和 mini-css-extract-plugin 。
yarn add css-loader mini-css-extract-plugin -d
index1.css
body , html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
index1.js
console.log('index1.js');
import('./async').then((res) => {
console.log(res);
})
import './index1.css';
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ProgressPlugin = require('webpack').ProgressPlugin;
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin;
module.exports = {
mode: 'development',
watch: true,
entry: {
index1: './index1.js',
index2: './index2.js'
},
output: {
publicPath: '',
path: __dirname + '/dist',
filename: '[name].[chunkhash].js'
},
module: {
rules: [{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // 抽取css文件的loader,不再插入head里
'css-loader'
]
}]
},
plugins: [
new ProgressPlugin(),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
new HtmlWebpackPlugin({
template: './index.html',
})
],
optimization: {
// 'single' - 表示抽离所有 chunk 公用的 runtime 代码
runtimeChunk: 'single'
}
}
编译打包后,控制台打印如下。
Asset Size Chunks Chunk Names
0.a84498019577afde7588.js 1.07 KiB 0 [emitted] [immutable]
index.html 499 bytes [emitted]
index1.470f6e18ca06585dd954.js 1.07 KiB index1 [emitted] [immutable] index1
index1.8db6dca1b242dbbabe70.css 87 bytes index1 [emitted] [immutable] index1
index2.82d191b5754b0e2e2ca8.js 537 bytes index2 [emitted] [immutable] index2
runtime.fca50e4fde877e19576a.js 8.98 KiB runtime [emitted] [immutable] runtime
修改 index1.js 后,控制台打印如下。
Asset Size Chunks Chunk Names
index.html 499 bytes [emitted]
index1.2748748868607facc4a8.js 1.08 KiB index1 [emitted] [immutable] index1
index1.css 和 index1.js 都属于 index1 这个 chunk 。当修改 index1.js 后,并不会引起 indx1.css 重新构建,因为 index1.css 不依赖 index1.js 。contenthash 的作用就是:当模块自身发生变化的时候,才会去重新构建这个模块。
其他
在开发环境中,因为公用模块代码不常变,所以可以缓存它,在生产环境中也是一样。但是如果公用模块代码确实需要更新,则需要重新构建公用模块代码来更新它的文件名,让浏览器重新请求它,而我们发布的新版本的业务代码一定是不可以缓存的!