聽說你用webpack處理檔名的hash?那麼建議你看看你生成的ha
童鞋,你看到這篇文章的時候很可能你只是在找一篇webpack的配置文章教學,但是聽老哥說一句,別去搜什麼startkit或者best practice文章,特別是中文的,如果你找到了,也記得看一下文章啥時候寫的,超過半年的文章就別看了,百分之92.6裡面的內容已經過期了。你想學webpack相關的姿勢,最好的辦法就是:看文件
言歸正傳,這篇文章並不教你怎麼配置webpack,內容全部都是關於webpack生成檔案的hash
的。在打包出來的檔名上加上檔案內容的hash
是目前最常見的有效使用瀏覽器長快取的方法,js檔案如果有內容更新,hash
就會更新,瀏覽器請求路徑變化所以更新快取,如果js內容不變,hash
不變,直接用快取,PERFECT!所以所有的問題就留給如何更好得控制檔案的hash
了。
首先我們弄一個最簡單的webpack配置:
const path = require('path')
module.exports = {
entry: {
app: path.join(__dirname, 'src/foo.js')
},
output: {
filename: '[name].[chunkhash].js',
path: path.join(__dirname, 'dist')
}
}
而我們foo.js如下:
import React from 'react'
console.log(React.toString())
注意這裡的output.filename
你也可以用[hash]
而不是[chunkhash]
,但是這兩種生成的hash碼是不一樣的
使用hash如下:
app.03700a98484e0f02c914.js 70.4 kB 0 [emitted] app
[6] ./src/foo.js 55 bytes {0} [built]
+ 11 hidden modules
使用chunkhash如下:
app.f2f78b37e74027320ebf.js 70.4 kB 0 [emitted] app
[6] ./src/foo.js 55 bytes {0} [built]
+ 11 hidden modules
對於單個entry來說用哪個都沒有問題,做例子期間使用的是webpack@3.8.1
版本,這個版本webpack對於原始碼沒有改動的情況,已經修復了hash串會變的問題。但是在之前的版本有可能會出現對於同一份沒有修改的程式碼進行修改,hash不一致的問題,所以不管你使用的版本會不會有問題,都建議使用接下去的配置。之後的配置都是用chunkhash作為hash生成
因為webpack要處理不同模組的依賴關係,所以他內建了一個js模板用來處理依賴關係(後面稱為runtime),這段js因此也會被打包的我們最後bundle裡面。在實際專案中我們常常需要將這部分程式碼分離出來,比如我們要把類庫分開打包的情況,如果不單獨給runtime單獨生成一個js,那麼他會和類庫一起打包,而這部分程式碼會隨著業務程式碼改變而改變,導致類庫的hash也每次都改變,那麼我們分離出類庫就沒有意義了。所以這裡我們需要給runtime單獨提供一個js。
修改配置如下:
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的文件中說明,如果給webpack.optimize.CommonsChunkPlugin
的name指定一個在entry中沒有宣告的名字,那麼他會把runtime程式碼打包到這個檔案中,所以你這裡可以任意指定你喜歡的name (ゝ∀・)b
那麼現在打包出來會是神馬樣的呢?
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
我們可以看到,app和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
從這裡就可以看出hash和chunkhash的區別了,chunkhash會包含每個chunk的區別(chunk可以理解為每個entry),而hash則是所有打包出來的檔案都是一樣的,所以一旦你的打包輸出有多個檔案,你勢必需要使用chunkhash。
類庫檔案單獨打包在一般的專案中,我們的類庫檔案都不會經常更新,比如react,更多的時候我們更新的是業務程式碼。那麼我們肯定希望類庫程式碼能夠儘可能長的在瀏覽器進行快取,這就需要我們單獨給類庫檔案打包了,怎麼做呢?
修改配置檔案:
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'
})
]
}
然後我們來執行以下打包:
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和app分開了,而且hash都不一樣,看上去很美好是不是?高興太早了年輕人。我們再新建一個檔案,叫bar.js
,程式碼如下:
import React from 'react'
export default function() {
console.log(React.toString())
}
然後修改foo.js
如下:
import bar from './bar.js'
console.log(bar())
從這個修改中可以看出,我們並沒有修改類庫相關的內容,我們的vendor中應該依然只有react
,那麼vendor的hash應該是不會變的,那麼結果如我們所願嗎?
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
很遺憾,webpack狠狠打了我們的臉╮(╯_╰)╭
這是什麼原因呢?這是因為我們多加入了一個檔案,對於webpack來說就是多了一個模組,預設情況下webpack的模組都是以一個有序數列命名的,也就是[0,1,2....]
,我們中途加了一個模組導致每個模組的順序變了,vendor裡面的模組的模組id變了,所以hash也就變了。總結一下:
- app變化是因為內容發生了變化
- vendor變化時因為他的module.id發生了變化
- runtime變化時因為它本身就是維護模組依賴關係的
那麼怎麼解決呢?
NamedModulePlugin和HashedModuleIdsPlugin這兩個plugin讓webpack不再使用數字給我們的模組進行命名,這樣每個模組都會有一個獨有的名字,也就不會出現增刪模組導致模組id變化引起最終的hash變化了。如何使用?
{
plugins: [
new webpack.NamedModulesPlugin(),
// new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime'
})
]
}
NamedModulePlugin一般用在開發時,能讓我們看到模組的名字,可讀性更高,但是效能相對較差。HashedModuleIdsPlugin更建議在正式環境中使用。
我們來看一下使用這個外掛後,兩次打包的結果,修改前:
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
修改後:
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
可以看到vendor的hash沒有變化,HashedModuleIdsPlugin也是一樣的效果。貌似世界變得更和諧了d(`・∀・)b,是嗎?哈哈,並不是!
async module隨著我們的系統變得越來越大,模組變得很多,如果所有模組一次性打包到一起,那麼首次載入就會變得很慢。這時候我們會考慮做非同步載入,webpack原生支援非同步載入,用起來很方便。
我們再建立一個js叫做async-bar.js
,在foo.js
中:
import('./async-bar').then(a => console.log(a))
打包:
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
恩,這時候我們已經看到,我們的vendor變了,但是更可怕的還在後頭,我們再建了一個模組叫async-baz.js
,一樣的在foo.js
引用:
import('./async-baz').then(a => console.log(a))
然後再打包:
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都變了啊?!!為啥模組又變成數字ID了啊?!!
好吧,言歸正傳,決絕辦法還是有的,那就是NamedChunksPlugin,之前是用來處理每個chunk名字的,似乎在最新的版本中不需要這個也能正常打包普通模組的名字。但是這裡我們可以用來處理非同步模組的名字,在webpack的plugins中加入如下程式碼:
new webpack.NamedChunksPlugin((chunk) => {
if (chunk.name) {
return chunk.name;
}
return chunk.mapModules(m => path.relative(m.context, m.request)).join("_");
}),
再執行打包,兩次結果如下:
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
可以看到結果都是用名字而不是id了,而且不改改變的地方也都沒有改變
注意生成chunk名字的邏輯程式碼你可以根據自己的需求去改
使用上面的方式會有一些問題,比如使用.vue檔案開發模式,m.request是一大串vue-loader生成的程式碼,所以打包會報錯。當然大家可以自己找對應的命名方式,在這裡我推薦一個webpack原生支援的方式,在使用import的時候,寫如下注釋:
import(/* webpackChunkName: "views-home" */ '../views/Home')
然後配置檔案只要使用new NamedChunksPlugin()
就可以了,不需要自己再拼寫名字,因為這個時候我們的非同步chunk已經有名字了。
所以到這就結束了是嗎?真的,求你快結束吧,我想去吃我兩小時前買的烤鴨了。
好~~~的吧,我們還得搞點事情
修改webpack.config.js
:
{
...
entry: {
app: path.join(__dirname, 'src/foo.js'),
vendor: ['react'],
two: path.join(__dirname, 'src/foo-two.js')
},
...
}
增加的enrty如下:
import bar from './bar.js'
console.log(bar)
import('./async-bar').then(a => console.log(a))
// import('./async-baz').then(a => console.log(a))
是的跟foo.js
一模一樣,當然你可以改邏輯,只需要記得引用bar.js
就可以。
然後我們打包,結果會讓你想再次(╯‵′)╯︵┻━┻
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
為毛所有檔案的hash都變化了啊?!!!逗我玩呢?
好吧,原因是vendor作為common chunk並不只是包含我們在entry中宣告的部分,他還會包含每個entry中引用的公共程式碼,有些時候你可能希望這樣的結果,但在我們這裡,這就是我要解決的一個問題啊ლ(゚д゚ლ)
所以這裡怎麼做呢,在CommonsChunkPlugin裡面有一個引數,可以用來告訴webpack我們的vendor真的只想包含我們宣告的內容:
{
plugins: [
...
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity
}),
]
}
這個引數的意思是儘可能少的把公用程式碼包含到vendor裡面。於是我們又打包:
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
恩,熟悉的味道。
到這裡我們跟webpack的hash變化之戰算是告一段落,大部分webpack打包出現問題的原因是模組命名的問題,所以解決辦法其實也就是給每個模組一個固定的名字。
最後我們的配置如下:
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'
})
]
}
如果你遇到了其他問題,你可以給我留言,我會去嘗試解決,希望大家看完能有一些收穫( σ՞ਊ ՞)σ
參考文章:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2480/viewspace-2808250/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 騰訊面試官:兄弟,你說你會Webpack,那說說他的原理?面試Web
- 聽說你用JavaScript寫程式碼?本文是你的機器學習指南JavaScript機器學習
- 面試官:你剛說你喜歡研究新技術,那麼請說說你對 Blazor 的瞭解面試Blazor
- 聽說Android9.0要禁用@HideApi的呼叫,你怎麼看?AndroidIdeaAPI
- 你說你學不動啦,看 Redux 作者怎麼說?Redux
- 你不知道的前端異常處理(萬字長文,建議收藏)前端
- 音訊 3A 處理實踐,讓你的應用更「動聽」音訊
- 聽說你的爬蟲被封了?爬蟲
- 你說你精通Redis,你看過持久化的配置嗎?Redis持久化
- 讓你的 webpack sass 和 css 處理效能 10 倍提升WebCSS
- 函式節流你應該知道的那點事,建議收藏!函式
- 聽說你的物件有個”環“?怎麼發現的呢?物件
- 聽說你會 Python ?Python
- 你從未聽說過的 JavaScript 早期特性JavaScript
- webpack(5)webpack處理css檔案WebCSS
- 入行IT,為什麼建議你學Java?Java
- 你信得過AI嗎?聽聽IBM科學家怎麼看AIIBM
- 用專案強化你的webpackWeb
- 幽默:如果你聽人說:它昨天還挺好啊,那麼可以斷定你們是在一個軟體專案。
- 產品經理該聽聽你的團隊的呼聲
- ROSE HA,想說愛你不容易——為ROSE HA配置IP資源的LocalFailoverROSAI
- async/await 你是會用,但是你知道怎麼處理錯誤嗎?AI
- 你知道Golang的模板怎麼用嗎?帶你瞭解動態文字的生成!Golang
- 說說你對vue的mixin的理解,有什麼應用場景?Vue
- 用異常處理來精簡你的程式碼
- Dubbo 自定義異常,你是怎麼處理的?
- 面試官:網站的SEO你怎麼處理啊?面試網站
- 你說你懂計算機網路,那這些你都知道嗎計算機網路
- 面試題:你工作中碰到的印象比較深的 bug,你怎麼處理的?面試題
- 聽說同學你搞不懂Java的LinkedHashMap,可笑JavaHashMap
- 聽說你還不理解JavaScript裡的閉包JavaScript
- 聽說你的 fetch 還要相容 IE9IE9
- 你的 APP 為何啟動那麼慢?APP
- 你是怎麼處理vue專案中的錯誤的?Vue
- 留下你最想說的話,我來用ai回覆你AI
- 為什麼建議你常閱讀原始碼?原始碼
- 你看那程式碼,好像一條鏈哎
- 聽說你還在手寫懶載入?