系列:
webpack入門——了解及使用
webpack基礎——常用配置解析
先來看看最基礎的webpack配置:
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
在index.js中引入了lodash庫:
src/index.js:
import _ from 'lodash';
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
打包之后鳄炉,只會生成一個bundle.js丑婿,這樣的話,每次若要加載資源文件,瀏覽器都會加載根本不會改動的lodash庫诉位,這樣很低效。
由于如果每次去訪問瀏覽器菜枷,瀏覽器都重新下載資源苍糠,由于網(wǎng)絡獲取資源可能很慢,可能頁面久久加載不出來啤誊,低效且不友好岳瞭,故瀏覽器會緩存資源,以避免每次訪問都通過網(wǎng)絡去獲取資源蚊锹。
但是瞳筏,由于瀏覽器緩存,又會出現(xiàn)新的問題牡昆,如果我們部署版本時不更改資源的文件名姚炕,瀏覽器可能認為它沒有更新,就會使用它的緩存版本。
這樣我們就需要解決兩個問題:第一柱宦,分離打包文件些椒。第二,解決緩存問題掸刊。
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
common: ['lodash'],
app: './src/index.js'
},
output: {
filename: '[name].[hash].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指代index.js引入的lodash庫
})
]
}
主要變動:
- 添加插件:CommonsChunkPlugin免糕,提取引入的庫,并且更名忧侧,實現(xiàn)代碼分離石窑。
- 輸出上在名字上加了hash,每次打包后蚓炬,hash值都不一樣解決了瀏覽器緩存的問題尼斧。
結果:index.js打包為app.[hash].js,index.js引入的lodash打包為common.[hash].js试吁。這樣解決了瀏覽器緩存問題和實現(xiàn)了靜態(tài)資源代碼和源代碼的分離棺棵,但是新的問題又出現(xiàn)了。
第一次打包后(注意Asset列下的名字):
每次我們修改源代碼時熄捍,再次打包烛恤,不僅僅index生成app.[hash].js的hash值發(fā)生了變化,
而且common.[hash].js的hash值與app的hash值相同也發(fā)生了變化(可以自行測試一下余耽,先webpack打包一次缚柏,修改index.js后再次打包一次)。
這并不是我們想要的結果碟贾,雖然源代碼hash改變解決了瀏覽器使用緩存版本的問題币喧,但是,如果common.js的hash值也一同發(fā)生了變化的話袱耽,那么瀏覽器也還需要每次都請求不會發(fā)生改變的靜態(tài)代碼common杀餐,這樣還是浪費了網(wǎng)絡資源,很低效朱巨。
注:本案例會多次打包史翘,dist目錄中會生成過多垃圾文件,在實際使用中都使用了CleanWebpackPlugin插件冀续。
new CleanWebpackPlugin(['dist']) // 加入在插件數(shù)組中琼讽,用于在每次打包前,都清空打包文件夾下之前打包的文件洪唐。
如果修改了index钻蹬,僅僅只是生成的app的hash值發(fā)生變化,而common的hash值不發(fā)生變化凭需,那就能夠達到我們的目的问欠,既能緩存庫又能識別源文件的更改肝匆。
我們進行如下配置: output中將 [name].[hash].js 改為[name].[chunkhash].js ,讓每個文件生成唯一的hash值:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
common: ['lodash'],
app: './src/index.js'
},
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new CleanWebpackPlugin(['dist']),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指代index.js引入的lodash庫
})
]
}
(注意:不要在開發(fā)環(huán)境下使用 [chunkhash]溅潜,因為這會增加編譯時間术唬。將開發(fā)和生產(chǎn)模式的配置分開,并在開發(fā)模式中使用 [name].js 的文件名滚澜,在生產(chǎn)模式中使用 [name].[chunkhash].js 文件名粗仓,所以如果這個時候使用了熱替換插HotModuleReplacementPlugin,將會導致編譯不成功I杈琛)
我們配置好之后借浊,進行webpack打包:
chunkhash是根據(jù)文件內(nèi)容生成的hash,可見app與common生成的hash值不相同了(對比使用 [name].[hash].js打包)萝招。
我們在index.js中隨便進行修改蚂斤,再次打包:
奇怪的是,雖然common與app生成了單獨的hash值槐沼,但是修改了index.js曙蒸,common的hash值還是發(fā)生了變化。
原因是:為了最小化生成的文件大小岗钩,webpack使用標識符而不是模塊名稱纽窟,在編譯期間生成標識符,并映射到塊文件名兼吓,然后放入一個
名為chunk manifest的JS對象中臂港。重點就在于!视搏!當我們使用CommonsChunkPlugin分離代碼時审孽,被分離出來的代碼(本文中的lodash庫,
被打包為common浑娜。)佑力,會默認被移動到entry中最后一個入口進行打包(第一個入口是index.js)。重要的是棚愤,chunk manifest將隨著這些被分離出來的代碼共同打包4晗簟!宛畦!
由于我們更改源代碼后,不但會更新app的hash值揍移,還會生成新的映射次和,然后新的映射又會和資源代碼一同打包,又由于chunkhash是根據(jù)內(nèi)容生成hash的那伐,那么加入了新的映射對象chunk manifest的資源代碼被打包后踏施,hash自然也會發(fā)生改變石蔗。這反過來,產(chǎn)生的新hash將使長效緩存失效畅形。
那么接下來我們需要做的就是講 manifest分離出來养距。這里我們利用一個CommonsChunkPlugin一個較少有人知道的功能,能夠在每次修改后的構建中將manifest提取出來日熬,通過指定entry中未用到的名稱棍厌,此插件會自動將我們需要的內(nèi)容提取到單獨的包中。
故再額外配置一個CommonsChunkPlugin:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
common: ['lodash'],
app: './src/index.js'
},
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new CleanWebpackPlugin(['dist']),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指代index.js引入的lodash庫
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' // 用于提取manifest
})
]
}
webpack打包后:
從這里可以證明之前所說的manifest被打包進了commonJT派础!仔細看之前的圖:common的Size都是547kb毕荐,到這里common大小是541kb 而manifest大小正好為5.85kb束析,加起來正好為547kb。
然后我們修改index.js再次打包:
從這里可以發(fā)現(xiàn)T餮恰员寇!我們修改了源代碼,common的hash值已經(jīng)不再發(fā)生改變了第美!到這里可以達到我們不緩存源代碼緩存資源文件的目的了蝶锋。
但是可別高興得太早!斋日!我們做了一個很小的修改牲览,交換了entry中 app 和 common的順序(對比上一個代碼段):
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
app: './src/index.js',
common: ['lodash']
},
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new CleanWebpackPlugin(['dist']),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指代index.js引入的lodash庫
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' // 用于提取manifest
})
]
}
打包后:
這里發(fā)現(xiàn)對比上一張圖片發(fā)現(xiàn),common的hash值又發(fā)生改變了6袷亍第献!而且根本沒有更改index.js的內(nèi)容app的hash也變了,只是換了一下順序而已兔港!
大家注意看本張圖與上一張圖的模塊解析順序([1]庸毫,[2],[3]...之后所對應的模塊)衫樊。發(fā)現(xiàn)上一張圖飒赃,lodash第一個解析,而現(xiàn)在lodash最后一個解析科侈。
這就是hash更變的原因:這是因為每個 module.id 會基于默認的解析順序(resolve order)進行增量载佳。也就是說,當解析順序發(fā)生變化臀栈,ID 也會隨之改變蔫慧,所以hash值也會發(fā)生變化。
有人可能會決定权薯,一般我們都不會更換webpack.config.js中entry的入口順序姑躲,那么是否我就不會遇見這個問題了睡扬。答案是否定的,除否你能保證資源文件都寫在entry的頂部黍析。否則會出現(xiàn)這樣的情況:
假如entry的順序為: app -> common卖怜, 那么解析順序為 index.js → lodash。 如果之后index.js引入了 print.js阐枣,那么解析順序變?yōu)?index.js → print.js -> lodash马靠。
以上,我們并沒有在entry中更改入口順序侮繁,解析的順序還是會發(fā)生改變虑粥,common的hash還是會發(fā)生,不能緩存宪哩。
這里我們就引入一個新的組件:HashedModuleIdsPlugin:根據(jù)hash生成ID(NamedModulesPlugin也具有同樣的效果娩贷,但是是根據(jù)路徑名生成ID,可讀性更高锁孟,也由此編譯時間會相對長一些)彬祖。 這樣module.id就不會使用數(shù)字標識符,而使用hash:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
common: ['lodash'],
app: './src/index.js'
},
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new CleanWebpackPlugin(['dist']),
new webpack.HashedModuleIdsPlugin(), // 引入該插件
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // 指代index.js引入的lodash庫
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' // 用于提取manifest
})
]
}
打包發(fā)現(xiàn)品抽,之前[ ]里都是數(shù)字储笑,現(xiàn)在都是一些字符,
接下來圆恤,我們再把app和common的順序調(diào)換一下突倍,并且隨意修改index.js,再次打包:
現(xiàn)在大功告成盆昙,common的hash沒有改變羽历,而因為更變了內(nèi)容app的hash改變了,這正是我們想要的結果淡喜。