当前位置: 首页 > Web前端 > JavaScript

webpack拆包:splitChunks的几个关键属性分析

时间:2023-03-27 17:52:25 JavaScript

为什么需要splitChunks?我们先举个简单的例子。wepack设置中有3个入口文件:a.js、b.js和c.js。每个入口文件同步导入m1.js。在不设置splitChunks的情况下,配置webpack-bundle-analyzer插件,用于查看输出文件内容,打包输出如下:从分析图中可以直观的看出m1.js文件是包括在三个输出包文件中,这意味着有重复的模块代码。splitChunks的目的是将重复的模块代码分离到单独的文件中,并通过异步加载来节省输出文件的体积。splitChunks中的配置项很多,官方文档中有些描述的不是很清楚。下面是一些关键的配置属性和场景解释,帮助大家理解和了解如何配置splitChunks。为了便于理解和简单演示,webpack和splitChunks的初始设置如下:constpath=require('path');constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports={mode:'development',entry:{a:'./src/a.js',b:'./src/b.js',c:'./src/c.js',},输出:{路径:路径。resolve(__dirname,'dist'),filename:'[name].bundle.js',clean:true,},optimization:{splitChunks:{chunks:'async',//生成的块的最小大小(在字节单位)。//因为demo模块比较小,所以需要设置这个。minSize:0,},},插件:[newBundleAnalyzerPlugin()],};chunkssplitChunks.chunks的作用是指明使用什么方法来优化chunk的分离。常用的值有async、initial和all三个,async是默认值,我们来看看这三个设置的区别。asyncchunks:'async'表示仅选择通过import()异步加载的模块来分隔块。比如仍然有三个入口文件a.js、b.js和c.js,两个模块文件m1.js和m2.js。三个入口文件内容如下://a.jsimport('./utils/m1');import'./utils/m2';console.log('somecodeina.js');//b.jsimport('./utils/m1');import'./utils/m2';console.log('somecodeina.js');//c.jsimport('./utils/m1');import'./utils/m2';console.log('c.js中的一些代码');这三个入口文件对于m1.js是异步导入的,对于m2.js是同步导入的。打包输出结果如下:对于异步导入,splitChunks将chunks分离形成单独的文件以供重用,但不处理同步导入的相同模块,这是chunks:'async'的默认行为。Initial将chunks改成initial,再看输出:同步导入也会分离出来,效果还不错。这就是initial和async的区别:同步导入的模块也会被选中分离。all我们添加一个模块文件m3.js,并对入口文件做如下修改://a.jsimport('./utils/m1');import'./utils/m2';import'./utils/立方米';//新添加的。console.log('somecodeina.js');//b.jsimport('./utils/m1');import'./utils/m2';import('./utils/m3');//新添加的。console.log('a.js中的一些代码');//c.jsimport('./utils/m1');import'./utils/m2';console.log('c.js中的一些代码');不同的是a.js是同步导入m3.js,而b.js是异步导入。保持chunks的设置为initial,输出如下:可以看到b中异步导入m3.js单独输出的chunks,a中同步导入的chunks没有分离。即在初始设置下,即使导入同一个模块,同步导入和异步导入也不能复用。设置chunks为all,然后导出康康:无论是同步导入还是异步导入,m3.js都是分离出来复用的。因此,all在initial的基础上,优化了不同导入方式下模块的复用。这里有一个问题。我发现当webpack的mode设置为production时,上面例子中a.js中同步导入的m3.js并没有被分离出来复用。模式设置为开发时是正常的。不知道是什么原因,有知道的请指教。我们已经看到async,initial,all都是类似progressivemodulereuseseparationoptimization,所以如果考虑volume-optimizedoutput,就把chunks设置成all。cacheGroups通过cacheGroups,可以自定义chunk输出分组。设置test过滤模块,符合条件的模块归入同一组。splitChunks默认有以下组:module.exports={//...optimization:{splitChunks:{//...cacheGroups:{defaultVendors:{test:/[\\/]node_modules[\\/]/,优先级:-10,reuseExistingChunk:true,},默认值:{minChunks:2,优先级:-20,reuseExistingChunk:true,},},},},};意思就是有两个默认的自定义组,defaultVendors和default,defaultVendors就是把node_modules下的modules单独放到这个组里。我们改一下配置,设置成把node_modules下的所有模块分开,输出到vendors.bundle.js文件中:constpath=require('path');constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports={mode:'development',entry:{a:'./src/a.js',b:'./src/b.js',c:'./src/c.js',},输出:{路径:path.resolve(__dirname,'dist'),文件名:'[name].bundle.js',clean:true,},优化:{splitChunks:{chunks:'all',minSize:0,cacheGroups:{vendors:{test:/[\\/]node_modules[\\/]/,priority:-10,reuseExistingChunk:true,name:'vendors',},},},},插件:[新的BundleAnalyzerPlugin()],};入口文件内容如下://a.jsimportReactfrom'react';importReactDOMfrom'react-dom';console.log('somecodeina.js');//b.jsimportReactfrom'react';console.log('somecodeina.js');//c.jsimportReactDOMfrom'react-dom';console.log('somecodeinc.js');输出结果如下:所以根据实际根据实际需要,我们可以使用cacheGroups将一些常用的业务模块分成不同的组,优化输出的拆分。比如我们现在对输出有两个需求:将node_modules下的所有modules分开,输出到vendors.bundle.js文件中。utils/目录下有一系列的工具模块文件,打包时都打成一个utils.bundle.js文件。调整webpack中的设置如下:constpath=require('path');constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports={mode:'development',entry:{a:'./src/a.js',b:'./src/b.js',c:'./src/c.js',},输出:{path:path.resolve(__dirname,'dist'),文件名:'[name].bundle.js',clean:true,},优化:{splitChunks:{chunks:'all',minSize:0,cacheGroups:{vendors:{test:/[\\/]node_modules[\\/]/,优先级:-10,reuseExistingChunk:true,名称:'vendors',},默认值:{test:/[\\/]utils[\\/]/,优先级:-20,reuseExistingChunk:true,name:'utils',},},},},plugins:[newBundleAnalyzerPlugin()],};入口文件调整如下://a.jsimportReactfrom'react';importReactDOMfrom'react-dom';import('./utils/m1');import'./utils/m2';console.log('a.js中的一些代码');//b.jsimportReactfrom'react';import'./utils/m2';import'./utils/m3';console.log('somecodeina.js');//c.jsimportReactDOMfrom'react-dom';import'./utils/m3';console.log('c.js中的一些代码');输出结果如下:maxInitialRequests和maxAsyncRequestsmaxInitialRequestsmaxInitialRequests表示该条目的最大并行请求数。规则如下:入口文件本身算作一次请求。import()异步加载不算数。如果同时有多个模块满足分裂规则,但是根据maxInitialRequests的当前值,只允许再分裂一次,选择容量较大的chunk。例如webpack设置如下:constpath=require('path');constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports={mode:'development',entry:{a:'./src/a.js',},output:{path:path.resolve(__dirname,'dist'),文件名:'[name].bundle.js',clean:true,},optimization:{splitChunks:{chunks:'all',minSize:0,maxInitialRequests:2,cacheGroups:{vendors:{test:/[\\/]node_modules[\\/]/,优先级:-10,reuseExistingChunk:true,名称:'vendors',},默认值:{test:/[\\/]utils[\\/]/,优先级:-20,reuseExistingChunk:true,名称:'utils',},},},},插件:[newBundleAnalyzerPlugin()],};入口文件内容如下://a.jsimportReactfrom'react';import'./utils/m1';console.log('somecodeina.js');打包输出结果如下:按照maxInitialRequests=2的拆分过程如下:a.bundle.js算作一个文件。vendors.bundle.js和utils.bundle.js都可以拆分,但是还剩一位,所以我选择拆分vendors.bundle.js。将maxInitialRequests的值设置为3,结果如下:考虑另一种场景,入口还是a.js文件,a.js的内容要改成://a.jsimport'./b';console.log('somecodeina.js');//b.jsimportReactfrom'react';import'./utils/m1';console.log('somecodeinb.js');调整为a.js同步导入b.js,其他模块在b.js中导入。maxInitialRequests在这种情况下是否有效?可以理解为maxInitialRequests为条目的并行请求数。上述场景中,b.js会在没有异步请求的情况下被打包成a.bundle.js;b.js中引入的两个模块会根据cacheGroups的设置进行拆分。然后它会统计入口处的并行请求数。比如maxInitialRequests设置为2时,打包输出结果如下:设置为3时,打包输出结果如下:maxAsyncRequestsmaxAsyncRequests表示限制异步请求的最大并发数。规则如下:import()本身算作一个请求。如果同时有多个模块满足分裂规则,但根据maxAsyncRequests的当前值,只允许再分裂一次,选择容量较大的chunk。再比如,webpack配置如下:constpath=require('path');constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports={mode:'development',entry:{a:'./src/a.js',},output:{path:path.resolve(__dirname,'dist'),文件名:'[name].bundle.js',clean:true,},optimization:{splitChunks:{chunks:'all',minSize:0,maxAsyncRequests:2,cacheGroups:{vendors:{test:/[\\/]node_modules[\\/]/,优先级:-10,reuseExistingChunk:true,名称:'vendors',},默认值:{test:/[\\/]utils[\\/]/,优先级:-20,reuseExistingChunk:true,名称:'utils',},},},},插件:[newBundleAnalyzerPlugin()],};入口及相关文件如下://a.jsimport('./b');console.log('a.js中的一些代码');//b.jsimportReactfrom'react';import'./utils/m1';console.log('somecodeinb.js');这次异步导入b.js是的,在maxAsyncRequests=2的设置下,打包输出结果如下:根据规则:import('.b')算作一个request,按照chunk的大小拆分vendors.bundle.js。最后import'./utils/m1'的内容保留在b.bundle.js中。如果maxAsyncRequests=3,则输出如下:这样b.js中导入的m1.js也被拆分了。在实践中,我们可以根据需要调整maxInitialRequests和maxAsyncRequests。就个人而言,我认为默认设置就足够了。总结splitChunks的设置非常复杂。通过以上规则的讲解和示例,相信大家已经了解了几个关键属性在解包中的使用。个人认为官方文档的解释比较混乱。其余属性你可以通过官方文档找到答案。我的JS博客:BibiJavaScript