Webpack 日常使用与优化之 webpack.optimize.CommonsChunkPlugin 比你想的更强大/配置更复杂
请注意,本文基于 webpack >= 3.0,记录下日常使用 webpack 的一些注意点/优化点,记忆和自用更多一点,同学们谨慎阅读。
实际项目:
- 入口文件:
['babel-polyfill', './src/client.js']
- chunks:入口文件及其依赖文件里通过
import()
(split point) 分离出 chunk 文件。
webpack 配置:
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: module => { // 所有 node_modules 下的文件 return module.resource && /node_modules/.test(module.resource) } }),
build 情况:
可以看到,我们生成了 vendor.hash.js
,里面包括了入口文件使用到的所有 node_modules
下的文件,但是,我们同时看到,同样位于 node_modules
下的 bluebird
并没有被打包进 vendor.hash.js
,并且在多个 chunk 文件中重复。
可见,CommonsChunkPlugin
比我们想象的要复杂,我们的配置并没有达到预期。
阅读完文档和资料,算是重新认识下 CommonsChunkPlugin
的配置项。
首先我们理解下 webpack 的 chunk:
- 入口文件(entry)也是 chunk,即 entry chunk;
- 入口文件以及它的依赖文件里通过 code split 分离出来/创建的也是 chunk,可以理解这些 chunk 为 children chunk 。
CommonsChunkPlugin
创建的文件也是 chunk,即 commons chunk。
其次我们要明确:CommonsChunkPlugin
是把共用模块的代码从 bundles 分离出来合并到单独的文件(commons chunk)。
有了这些概念后我们可以重新理解下 CommonsChunkPlugin
的各个选项:
name
可以是已经存在的 chunk 的 name (一般是入口文件),那么共用模块代码会合并到这个已存在的 chunk;否则,创建名字为name
的 commons chunk 来合并。filenames
,即这个 commons chunk 的文件名(最终保存到本地的文件)。chunks
指定 source chunks,即从哪些 chunk 去查找共用模块。省略chunks
选项时,默认为所有 entry chunks。minChunks
可以- 设定为数字(大于等于2),指定共用模块被多少个 chunk 使用才能被合并。
- 也可以设为函数,接受
(module, count)
两个参数,用法如上。 - 特别地,还可以设置为
Infinity
,即创建 commons chunk 但不合并任何共用模块。这时一般搭配entry
的配置一起用:
entry: { vendor: ["jquery", "other-lib"], app: "./entry" } new webpack.optimize.CommonsChunkPlugin({ name: "vendor", // filename: "vendor.js" // (Give the chunk a different name) minChunks: Infinity, // (with more entries, this ensures that no other module // goes into the vendor chunk) })
children
设为true
时,指定 source chunks 为 children of commons chunk。这里的 children of commons chunk 比较难理解,可以认为是 entry chunks 通过 code split 创建的 children chunks。children
与chunks
不可同时设置(它们都是指定 source chunks 的)。children
可以用来把 entry chunk 创建的 children chunks 的共用模块合并到自身,但这会导致初始加载时间较长:new webpack.optimize.CommonsChunkPlugin({ // names: ["app", "subPageA"] // (choose the chunks, or omit for all chunks) children: true, // (select all children of chosen chunks) // minChunks: 3, // (3 children must share the module before it's moved) })
async
即解决children: true
时合并到 entry chunks 自身时初始加载时间过长的问题。async
设为true
时,commons chunk 将不会合并到自身,而是使用一个新的异步的 commons chunk。当这个 commons chunk 被下载时,自动并行下载相应的共用模块。
好了,解读了这么多选项的各种用法,是时候见证实际效果了:
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: module => { return module.resource && /node_modules/.test(module.resource) } }), new webpack.optimize.CommonsChunkPlugin({ name: 'client', async: 'chunk-vendor', children: true, minChunks: (module, count) => { // 被 3 个及以上 chunk 使用的共用模块提取出来 return count >= 3 } }),
可以看到,我们把 client 创建的多个 chunk 的共用模块分离到了 chunk-vendor.hash.chunk.js
,大大减少了这些 chunk 里的重复代码,总 size 从 1.36MB
减少到 872.3KB
。
参考
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 学习与理解 React Fiber
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(21)
@creeperyang 关于webpack.optimize.CommonsChunkPlugin 与优化浏览器端缓存
在项目中静态导入本地模块时(import),vendor(公共模块)的chunkhash不会发生变化,但当我在项目中动态导入时(dynamic import),vendor的chunkhash会发生变化,不知原因是为何?
假如项目较大,就需要按需加载(dynamic import),vendor的chunkhash每次都发生变化,如何做到缓存呢?
@nlffeng 可以给个repo或者其它例子实际查看下吗?
@creeperyang 首先感谢回答,以下是我的webapck配置
1、当我在js文件动态导入2个(模块/js文件),构建打包后如下:
2、当我在js文件动态导入1个时,构建打包后,这时vendor的chunkhash发生了变化,如下:
vendor是公共库,是没有发生变化的,此处由于动态导入模块,引起了vendor的chunkId发生了变化,即vendor内容中chunkId值发生了变化,导致chunkhash变化,这样,在一个项目中,每次添加一个动态导入时,它的公共库的chunkhash都会发生变化,如何做到缓存呢?
@nlffeng 考虑下搭配
NamedModulesPlugin/HashedModuleIdsPlugin
使用。https://webpack.js.org/guides/caching/
@creeperyang 我的配置中是有HashedModuleIdsPlugin这个,这个插件是可以保持node_modules当中的模块的id不变,但是生成vendor后这个模块的id会和受其它动态导入的模块的影响,才会产生这种chunkhash变化的情况,楼主可以试一试,目前还没找到如何保持这个vendor的moduleId保持不变的方法!
@creeperyang
我是使用了vue-router的懒加载,这样改完后,异步加载组件里的类库仍没被抽离出来,chunk-vendor文件没有生成,求指点..
webpack版本是3.3.0
@cqq626 可以设置
stats.chunks/stats.children
等,查看webpack实际是怎么为你处理的。https://segmentfault.com/a/1190000015919928
@kunsun 对。4.0 去除 了
CommonsChunkPlugin
,使用optimization.splitChunks/optimization.runtimeChunk
代替。可以参考阅读:
总结得很好,children部分的例子对我很有帮助
请问这个配置里的client是一个单独的entry的name吗,我试着直接用似乎没有效果
不过webpack4.0.1 好像取消了children chunks的配置,不知道是不是理解有误
关于chunks的分类,讲的特别好,赞
3. 命令行里输出详细错误原因
合格的程序员碰到错误的第一反应一定是自己先找原因,而不是去问别人。
0配置打包工具的火爆说明webpack某种程度上比较繁琐,尤其当你碰到错误时。排除错误的前提是可以找到原因,查找原因的前提是错误信息详细输出——善用
stats
配置。具体的配置官方文档都有,不再细述,这里主要强调怎么防止配置不起作用:
使用 webpack-dev-server 时需要把
stats
放devServer
里。使用 Node.js API 时
stats
不起作用,但可以使用webpack-dev-middleware的配置:@lz-lee
minChunks
指定 1 相当于命中所有chunks(每个chunk必然出现 >= 1 次),即所有 chunk 打包为一个文件。建议提问前可以先阅读官方文档
多页配置webpack,需要将每个页面对应的css/less单独打包成对应的css,为什么设置minChunks >=2 就可以,而设置为 1 就只打包成一个css文件。
主要是因为需求太多样化了,前端也越来越复杂了。
而插件越来越多,说明生态繁荣,同时能满足的场景也各式各样。
感觉号称0配置的比如 parcel 这些只能满足一部分需求,但如果没有特殊需求的可以一试。
现在webpack的配置越来越复杂,插件越来越多,越来越累赘了
@hanxiansen https://github.com/webpack-contrib/webpack-bundle-analyzer
问下博主,上面这种图片是通过什么生成的?
2.
webpack.optimize.CommonsChunkPlugin
与优化浏览器端缓存浏览器下载静态文件是非常耗时的,所以为了提高性能,浏览器尽一切可能来使用缓存。通常情况下,
所以生产中,我们一般在文件名中加入 hash,即文件内容变化,则文件名变化。这样自动利用浏览器缓存且不会妨碍文件更新。
hash vs chunkhash
在 webpack 里,我们可以通过 hash/chunkhash 来自动更新文件名:
其中:
怎么最大化利用浏览器缓存?(怎么保证 vendor 的有效缓存?)
有了 chunkhash,看起来我们已经可以正常利用浏览器缓存了。但 webpack 有个已知问题:
webpack 自身的 boilerplate 和 manifest 代码可能在每次编译时都会变化。
这导致我们只是在 入口文件 改了一行代码,但编译出的 vendor 和 entry chunk 都变了,因为它们自身都包含这部分代码。
这是不合理的,因为我们的依赖(node_modules)没变,vendor 不应该在我们业务代码变化时发生变化!而 vendor 通常 size 很大,每次发布业务代码导致 vendor 变化无法利用缓存是不可接受的。
很直接的解决方案:为什么不把 webpack 自身的这部分代码分离出来呢?
配置很简单,如上即可。(name 只要不在 entry 里出现过即可,但通常使用
"runtime"/"manifest"
)。下面是我改业务代码后的两次编译:
可以看到 vendor 的 chunkhash 没有变化。很好,终于可以愉快的利用浏览器缓存了!