最近讀了一下webpack的文檔,讀到CommonsChunkPlugin這個插件,深深折服與webpack的強大,同時也產生了一些自己的疑問。
首先,CommonsChunkPlugin這個插件是用來提取公共代碼的,通過將公共模塊提取出來,只在頁面加載的時候引入一次,提升應用的加載效率。
順便提一下,chunk其實就是代碼塊的意思,可能是一個或多個模塊,一般就是一個js文件。
CommonsChunkPlugin有中文翻譯的文檔,但是感覺並不是很通順,英文文檔看完也有一些疑惑,比如minChunks到底是做什么用的,怎么用?chunks是什么?首先貼一下文檔。
1 { 2 name: string, // or 3 names: string[], 4 // The chunk name of the commons chunk. An existing chunk can be selected by passing a name of an existing chunk. 5 // If an array of strings is passed this is equal to invoking the plugin multiple times for each chunk name. 6 // If omitted and `options.async` or `options.children` is set all chunks are used, otherwise `options.filename` 7 // is used as chunk name. 8 // When using `options.async` to create common chunks from other async chunks you must specify an entry-point 9 // chunk name here instead of omitting the `option.name`. 10 11 filename: string, 12 // The filename template for the commons chunk. Can contain the same placeholders as `output.filename`. 13 // If omitted the original filename is not modified (usually `output.filename` or `output.chunkFilename`). 14 // This option is not permitted if you're using `options.async` as well, see below for more details. 15 16 minChunks: number|Infinity|function(module, count) -> boolean, 17 // The minimum number of chunks which need to contain a module before it's moved into the commons chunk. 18 // The number must be greater than or equal 2 and lower than or equal to the number of chunks. 19 // Passing `Infinity` just creates the commons chunk, but moves no modules into it. 20 // By providing a `function` you can add custom logic. (Defaults to the number of chunks) 21 22 chunks: string[], 23 // Select the source chunks by chunk names. The chunk must be a child of the commons chunk. 24 // If omitted all entry chunks are selected. 25 26 children: boolean, 27 // If `true` all children of the commons chunk are selected 28 29 async: boolean|string, 30 // If `true` a new async commons chunk is created as child of `options.name` and sibling of `options.chunks`. 31 // It is loaded in parallel with `options.chunks`. 32 // Instead of using `option.filename`, it is possible to change the name of the output file by providing 33 // the desired string here instead of `true`. 34 35 minSize: number, 36 // Minimum size of all common module before a commons chunk is created. 37 }
- name和names:chunk的名稱,如果這個chunk已經在entry中定義,該chunk會被直接提取;如果沒有定義,則生成一個空的chunk來提取其他所有chunk的公共代碼。
- filename:可以指定提取出的公共代碼的文件名稱,可以使用output配置項中文件名的占位符。未定義時使用name作為文件名。
- chunks:可以指定要提取公共模塊的源chunks,指定的chunk必須是公共chunk的子模塊,如果沒有指定則使用所有entry中定義的入口chunk。
-
minChunks:在一個模塊被提取到公共chunk之前,它必須被最少minChunks個chunk所包含。(通俗的說就是一個模塊至少要被minChunks個模塊所引用,才能被提取到公共模塊。)
該數字必須不小於2或者不大於chunks的個數。默認值等於chunks的個數。
如果指定了Infinity,則創建一個公共chunk,但是不包含任何模塊,內部是一些webpack生成的runtime代碼和chunk自身包含的模塊(如果chunk存在的話)。
用戶也可以定制自己的邏輯去生成代碼。
我們看一個簡單的例子。
1 module.exports = { 2 entry: { 3 app: './src/index.js', 4 vender: [ 5 'lodash', 6 'otherlib' 7 ] 8 }, 9 plugins: [ 10 new webpack.optimize.CommonsChunkPlugin({ 11 name: 'vender' 12 }) 13 ], 14 output: { 15 filename: '[name].[chunkhash].js', // 使用Hash來命名文件,實現文件緩存的功能。當文件內容發生變化,文件名會隨之改變。 16 path: path.resolve(__dirname, 'dist') 17 } 18 };
上面的代碼中定義了兩個入口,app和vender(公共庫),plugins中使用CommonsChunkPlugin提取vender。
vender是我們提取出來的公共chunk,通常不會被修改,所以理應在每次編譯后文件名保持一致。然而,我們嘗試修改入口文件index.js會發現,vender的文件名會發生變化。
原因呢上面提到過,由於每次編譯會導致vender的module.id發生變化,內部的runtime代碼隨之發生改變。
解決方案有以下幾種:
1. 使用NamedModulesPlugin插件,用文件路徑而非默認的數字ID來作為模塊標識。
2. 使用HashedModuleIdsPlugin插件,用相對路徑的Hash值來作為模塊標識。推薦在生產環境中使用。
3. 將runtime部分的代碼提取到一個單獨的文件中,代碼如下。
1 module.exports = { 2 entry: { 3 app: './src/index.js', 4 vender: [ 5 'lodash' 6 ] 7 }, 8 plugins: [ 9 new webpack.optimize.CommonsChunkPlugin({ 10 name: 'vender', 11 minChunks: Infinity 12 }), 13 new webpack.optimize.CommonsChunkPlugin({ 14 name: 'manifest', 15 chunks: ['vender'] 16 }) 17 ], 18 output: { 19 filename: '[name].[chunkhash].js', 20 path: path.resolve(__dirname, 'dist') 21 } 22 };
代碼中再次使用了CommonsChunkPlugin,從vender中提取出了名為manifest的運行時代碼。
未完待續,歡迎指正。