Webpack CommonsChunkPlugin 理解
最近读了一下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的运行时代码。
未完待续,欢迎指正。