一.hash是什么?
hash 是一種散列算法.
它可以將[任意長度的二進制數(shù)]據(jù)映射成[較短的固定長度的]二進制值.
它的原理很簡單,所有的數(shù)據(jù),不管是圖片,文字,視頻,文件等亂七八糟的東西.
丟給一個固定的hash算法,只要文件的內(nèi)容不發(fā)生改變,那么計算出來的hash永遠都是一樣的.
大家最熟悉的hash算法莫過于 MD5 了.
二.hash在前端開發(fā)的過程中,起到了哪些特別的作用?
前端開發(fā),不管是用什么框架,什么打包工具,最終運行的環(huán)境是瀏覽器.
瀏覽器是有緩存機制的.如果一個文件的文件名不發(fā)生變化,那么瀏覽器就會有很大的可能去緩存這個文件(cache-control:no-cache除外.)
有時候,瀏覽器的緩存也是一把雙刃劍.比如我們的某個css文件的內(nèi)容實際上是改了,但是由于文件名沒改.
瀏覽器就認為這個文件沒有發(fā)生變動,不去再次發(fā)生請求從服務器獲取,而是從本地緩存里讀取.會導致用戶看到的文件并不是最新的.
所以,經(jīng)常會看到這樣的css代碼.
<link rel="stylesheet" href="style.css?v=1.0">
<link rel="stylesheet" href="style.css?v=1.2">
<link rel="stylesheet" href="style.css?v=1.3">
通過給一個基本沒啥作用的查詢字符串結尾,讓瀏覽器認為這是一個動態(tài)的請求,從而獲得最新的css文件的目的.
webpack
提供的類似的功能,就叫做hash功能.
可以給打包出來的文件名,加上一串hash值,每次都可以生成一個新的文件名.
webpack 給文件名插入之 hash
webpack.config.js
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
entry: {
indexName: path.join(__dirname, 'index.js'),
appFileName: path.join(__dirname, 'app.js')
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[hash].js'
},
mode: 'development',
plugins: [
new CleanWebpackPlugin()
]
}
打包查看結果:
查看打包的輸出結果
- 整個項目的hash值是:
e4b33a6c56df04dda9be
-
appFileName
的hash值是:e4b33a6c56df04dda9be
-
indexName
的hash值是:e4b33a6c56df04dda9be
現(xiàn)在僅僅修改 index.js
文件.
// index.js
let i = 0
重新打包
- 整個項目的hash值:
414410450aa6ab446bc3
-
appFileName
的hash值是:414410450aa6ab446bc3
-
indexName
的hash值是:414410450aa6ab446bc3
現(xiàn)在問題就來了, 我明明只改了 index.js 里的代碼,app.js代碼壓根沒動.但是它的hash還是變了.
hash是項目級別的.只要項目內(nèi)部改動了任意一個位置,所有設置了 hash
的文件的hash
值都會跟著一起發(fā)生改變.
(那些從來壓根沒改過的文件的hash也會跟著改變).這樣的話,完全就不合理了.沒變內(nèi)容的文件如果hash也變了,文件
名就變了.文件變了,瀏覽器就需要重新下載這個文件,達不到緩存的作用了.
webpack 給文件插入之 chunkhash
chunkhash是針對entry的每一個入口文件,獨立的hash顽耳。如果entry里面的其中一個文件內(nèi)容改變,只會改變這個入口
文件build之后的文件名膝迎,而不會影響到其他文件。
webpack.config.js
const path = require('path')
// import { CleanWebpackPlugin } from 'clean-webpack-plugin' // node.js 還不支持ES6的import模塊導入語法.
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin')
module.exports = {
entry: {
indexName: path.join(__dirname, 'index.js'),
appFileName: path.join(__dirname, 'app.js')
},
output: {
path: path.join(__dirname, 'dist'),
// filename: '[name]_[hash].js'
filename: '[name]_[chunkhash].js'
},
mode: 'development',
module: {
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name]_[contenthash].[ext]'
}
}]
}]
},
plugins: [
new CleanWebpackPlugin()
]
}
查看結果
- 整個項目的 hash 值是:
9ec8f05fbffa71101b9c
- appFileName 的 hash 值是:
420fbddeda08d6b1d3c0.
- indexName 的 hash 值是:
bdd32b0728173915f845
他們?nèi)齻€的hash值都不相同.
現(xiàn)在修改 app.js
let i = 0
查看結果:
- 由于對于整個項目而言,的確有文件的內(nèi)容被修改了,所以 hash 值從原來的
9ec8f05fbffa71101b9c
變成了2bd51d157cc397ec1523
. 在情理和意料之中. - appFileName 的 hash 值 :
49d983a60c15f9a92cb4
而原來的是420fbddeda08d6b1d3c0
. 也在情理和意料之中,因為我們修改了app.js
的代碼let i = 0
- indexName 的 hash 仍然是原來的
bdd32b0728173915f845
, 沒有改動過代碼,所以還是原來的值.
webpack 給文件名插入之 contenthash
contenthash 顧名思義, 就是根據(jù)當前文件的內(nèi)容,來計算hash值.
將非output的文件名設置hash或者chunkhash,比如(css,image等)都將無效,而且默認使用的是contenthash
比如:
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]' // 按道理是當前項目的hash值.
}
}]
}]
結果:
按道理來說,設置了hash,圖片文件名應該和項目的hash一致.
但結果,它倆完全不一樣,肉眼都可以一眼看出,起碼長度都不一樣.
webpack.config.js
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
// name: '[name]_[hash].[ext]' // 按道理是當前項目的hash值.
name: '[name]_[chunkhash].[ext]' //Path variable [chunkhash] not implemented in this context: 282-8_[chunkhash].jpg
}
}]
}]
配置費output文件成為chunkhash,直接報錯.
//Path variable [chunkhash] not implemented in this context: 282-8_[chunkhash].jpg
壓根就沒實現(xiàn)這個hash的功能.
配置contenthash
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
// name: '[name]_[hash].[ext]' // 按道理是當前項目的hash值.
// name: '[name]_[chunkhash].[ext]' //Path variable [chunkhash] not implemented in this context: 282-8_[chunkhash].jpg
name: '[name]_[contenthash].[ext]' // 根據(jù)文件的內(nèi)容,生成的hash
}
}]
}]
結果:
發(fā)現(xiàn)和配置hash
的情況一致.也就是說,給jpg等靜態(tài)資源配置hash
默認就會轉成contenthash
.
總結:
hash [范圍最大] 是針對整個項目的,如果把整個項目當做是一個文件(為什么非要是單個的1.txt就這么好理解成是文件了?),那么這個項目文件的內(nèi)容發(fā)生改變(文件刪除添加,文件內(nèi)容修改),都會導致整個項目的hash值發(fā)生改變.
chunkhash [范圍其次] 是根據(jù)當前入口文件最終打包出來的js文件.output. 當前依賴鏈中,有任意文件變動,都會改變這個hash值.
而contenthash [范圍最小] 就僅僅只是針對當前文件的內(nèi)容.
這里說了一堆東西.到底想表達什么?
hash,chunkhash,contenthash,說白了,就是根據(jù)不同的范圍,給最終生成的文件名里加一串字符串.(hash把整個項目當成一個文件;chunkhash把一個獨立的entry個output當前一個文件(多個模塊之間的依賴當成一個chunk);contenthash則是把單個文件當成是一個文件)
它們有一個共同的特點就是,最終它們都會是生成一個獨立的文件,且在瀏覽器中,會有一個鏈接指向它們.
有鏈接指向它們,它們就能利用所謂的各種hash,來做緩存了.
一張圖解: