理解 Webpack 中的 Chunk
本篇文章我們來(lái)聊聊 Webpack 中的 Chunk。
- Chunk 定義
- Chunk 產(chǎn)生途徑
Chunk 定義
Chunk 不同于 entry、 output、module 這樣的概念,它們對(duì)應(yīng)著 Webpack 配置對(duì)象中的一個(gè)字段,Chunk 沒(méi)有單獨(dú)的配置字段,但卻出現(xiàn)在 CommonsChunkPlugin(Webpack3 以前)、optimization.splitChunks(Webpack4 以后)這樣的名稱之中。
Chunk 是我們理解 Webpack 的一個(gè)重要概念,它指的是 Webpack 里的一個(gè)代碼塊。具體是什么樣的代碼塊呢?
我們先來(lái)看一下 Module。
Module
Webpack 可以看做是模塊打包器,我們編寫的任何文件,對(duì)于 Webpack 來(lái)說(shuō),都是一個(gè)個(gè)模塊。所以 Webpack 的配置文件,有一個(gè) module 字段,module 下有一個(gè) rules 字段,rules 下有就是處理模塊的規(guī)則,配置哪類的模塊,交由哪類 loader 來(lái)處理。
module:?{
????rules:?[
??????{
????????test:?/\.css$/,
????????use:?[
??????????{
????????????loader:?"style-loader"
??????????},?{
????????????loader:?"css-loader"
??????????}
????????]
??????},
??????...
????]
??}
Chunk
Chunk 是 Webpack 打包過(guò)程中,一堆 Module 的集合。我們知道 Webpack 的打包是從一個(gè)入口文件開(kāi)始,也可以說(shuō)是入口模塊,入口模塊引用這其他模塊,模塊再引用模塊。Webpack 通過(guò)引用關(guān)系逐個(gè)打包模塊,這些 Module 就形成了一個(gè) Chunk。
如果我們有多個(gè)入口文件,可能會(huì)產(chǎn)出多條打包路徑,一條路徑就會(huì)形成一個(gè) Chunk。
除了入口 entry 會(huì)產(chǎn)生 Chunk 之外,還有其他途徑也會(huì)產(chǎn)生 Chunk,下文中會(huì)介紹。
Chunk VS Bundle
通常我們會(huì)弄混這兩個(gè)概念,以為 Chunk 就是 Bundle,Bundle 就是我們最終輸出的一個(gè)或多個(gè)打包文件。確實(shí),大多數(shù)情況下,一個(gè) Chunk 會(huì)生產(chǎn)一個(gè) Bundle。但也不完全是一對(duì)一的關(guān)系,比如我們把 devtool 配置成 source-map,然后只配置一個(gè)入口文件,不配置代碼分割:
?//?webpack配置
?entry:?{
????main:?__dirname?+?"/app/main.js",
?},
?output:?{
????path:?__dirname?+?"/public",//打包后的文件存放的地方
????filename:?"[name].js",?//打包后輸出文件的文件名
??},
?devtool:?'source-map'
這樣的配置,會(huì)產(chǎn)生一個(gè) Chunk,但是會(huì)產(chǎn)生兩個(gè) bundle,如下圖:

圖中注意 Chunk Names 一列,只有 main 這一個(gè) Chunk,再看 Asset 這一列,產(chǎn)生了兩個(gè) bundle(main.js 和 main.js.map)。
這就是 Chunk 和 Bundle 的區(qū)別,Chunk 是過(guò)程中的代碼塊,而 Bundle 是結(jié)果的代碼塊。
接下來(lái)我們查看一下 Webpack 源碼,發(fā)現(xiàn)有一個(gè) Chunk.js:
/**
?*?A?Chunk?is?a?unit?of?encapsulation?for?Modules.
?*?一個(gè) Chunk 是一些模塊的封裝單元。
?*?Chunks?are?"rendered"?into?bundles?that?get?emitted?when?the?build?completes.
?* Chunk 在構(gòu)建完成就呈現(xiàn)為 bundle。
?*/
class?Chunk?{
}
根據(jù)翻譯,可以得出 Webpack 在運(yùn)行中,會(huì)生成 Chunk 對(duì)象,而一旦構(gòu)建完成 Chunk 就會(huì)變成 Bundle。
Chunk 產(chǎn)生途徑
- entry 產(chǎn)生 Chunk
- 異步模塊產(chǎn)生 Chunk
- 代碼分割產(chǎn)生 Chunk
entry 產(chǎn)生 Chunk
Webpack 入口文件 entry 的配置有三種方式:
1、傳遞一個(gè)字符串,會(huì)產(chǎn)生一個(gè) Chunk。
entry:?'./src/js/main.js'
2、傳遞一個(gè)數(shù)組,也只會(huì)產(chǎn)生一個(gè) Chunk。
entry:?['./src/js/main.js','./src/js/other.js']
Webpack 會(huì)將數(shù)組里的源代碼,最終都打包到一個(gè) Bundle 里,原因就是只生成了一個(gè) Chunk。
3、傳遞一個(gè)對(duì)象,可能產(chǎn)生多個(gè) Chunk。
entry:?{
??main:?'./src/js/main.js',
??other:?'./src/js/other.js'
},
output:?{
??//?path:?__dirname?+?"/public",
??//?filename:'bundle.js'
??//?以上2行會(huì)報(bào)錯(cuò)
??path:?__dirname?+?"/public",//打包后的文件存放的地方
??filename:?"[name].js",?//打包后輸出文件的文件名
}
對(duì)象中一個(gè)字段就會(huì)產(chǎn)生一個(gè) Chunk,所以在 output 中 filename 直接寫死名稱,會(huì)報(bào)錯(cuò)。因?yàn)樯厦娴呐渲茫a(chǎn)生了兩個(gè) Chunk,最終會(huì)生成兩個(gè) Bundle,一個(gè)名稱肯定不夠用了。需要用 [name] 變量來(lái)把 entry 下的字段名稱,設(shè)置為對(duì)應(yīng)的 Bundle 名稱。
這里面 entry 的 key,也被用來(lái)當(dāng)作它對(duì)應(yīng)的 Chunk 的名稱,上面?zhèn)鬟f字符串和數(shù)組的方式?jīng)]有 key,Webpack 會(huì)默認(rèn)給生成的 Chunk 命名為 main。
異步模塊產(chǎn)生 Chunk
除了入口文件影響 Chunk 之外,異步加載的模塊,也會(huì)產(chǎn)生 Chunk。
{
????entry:?{
????????"index":?"pages/index.jsx"
????},
????output:?{
????????filename:?"[name].min.js",
????????chunkFilename:?"[name].min.js"
????}
}
const?myModel?=?r?=>?require.ensure([],?()?=>?r(require('./myVue.vue')),?'myModel')
這個(gè)時(shí)候 chunkFilename 字段就派上用場(chǎng)了,為異步加載的 Chunk 命名。
代碼分割產(chǎn)生 Chunk
最后一種方法是代碼分割產(chǎn)生 Chunk。
我們來(lái)分析一下,下面代碼會(huì)產(chǎn)生幾個(gè) Chunk。
// main.js 和 other.js 都引用了同一個(gè) greeter.js 文件。main.js 中使用了 react。
module.exports?=?{
??entry:?{
????main:?__dirname?+?"/app/main.js",
????other:?__dirname?+?"/app/other.js",
??},
??output:?{
????path:?__dirname?+?"/public",//打包后的文件存放的地方
????filename:?"[name].js",?//打包后輸出文件的文件名
????chunkFilename:?'[name].js',
??},
??optimization:?{
????runtimeChunk:?"single",
????splitChunks:?{
??????cacheGroups:?{
????????commons:?{
??????????chunks:?"initial",
??????????minChunks:?2,
??????????maxInitialRequests:?5,?//?The?default?limit?is?too?small?to?showcase?the?effect
??????????minSize:?0?//?This?is?example?is?too?small?to?create?commons?chunks
????????},
????????vendor:?{
??????????test:?/node_modules/,
??????????chunks:?"initial",
??????????name:?"vendor",
??????????priority:?10,
??????????enforce:?true
????????}
??????},
????}
??}
}
答案是 5 個(gè)。
- 兩個(gè)入口文件分別產(chǎn)生一個(gè)。
runtimeChunk: "single"會(huì)將 Webpack 在瀏覽器端運(yùn)行時(shí),單獨(dú)抽離到一個(gè)文件,生成一個(gè) Chunk。- commons 下的配置會(huì)產(chǎn)生一個(gè) Chunk。
- vendor 下的配置會(huì)產(chǎn)生一個(gè) Chunk。
最終構(gòu)建圖如下:

結(jié)束語(yǔ)
今天的研究就到這里。
如果你能看懂最后一個(gè) Webpack 配置案例,并能清楚地分辨出能生成幾個(gè) Chunk,那么恭喜你,你已經(jīng)掌握 Webpack Chunk 了,這對(duì)于后續(xù) Webpack 的理解非常有幫助,你學(xué)到了嗎?
