共计 7026 个字符,预计需要花费 18 分钟才能阅读完成。
为什么须要 splitChunks?
先来举个简略的栗子,wepack 设置中有 3 个入口文件:a.js
、b.js
和 c.js
,每个入口文件都同步 import 了 m1.js
,不设置 splitChunks,配置下 webpack-bundle-analyzer 插件用来查看输入文件的内容,打包输入是这样的:
从剖析图中能够比拟直观的看出,三个输入 bundle 文件中都蕴含了 m1.js
文件,这阐明有反复的模块代码。splitChunks 的目标就是用来把反复的模块代码拆散到独自的文件,以异步加载的形式来节俭输入文件的体积。splitChunks 的配置项很多而且感觉官网文档的一些形容不是很分明,上面通过一些重点配置属性和场景解释来帮忙大家了解和弄懂如何配置 splitChunks。为不便了解和简略演示,webpack 和 splitChunks 的初始设置如下:
const path = require('path');
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'development',
entry: {
a: './src/a.js',
b: './src/b.js',
c: './src/c.js',
},
output: {path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true,
},
optimization: {
splitChunks: {
chunks: 'async',
// 生成 chunk 的最小体积(以 bytes 为单位)。// 因为演示的模块比拟小,须要设置这个。minSize: 0,
},
},
plugins: [new BundleAnalyzerPlugin()],
};
chunks
splitChunks.chunks
的作用是批示采纳什么样的形式来优化拆散 chunks,罕用的有三种罕用的取值:async
、initial
和 all
,async
是默认值,接下来别离看下这三种设置的区别。
async
chunks: 'async'
的意思是只抉择通过 import()
异步加载的模块来拆散 chunks。举个例子,还是三个入口文件 a.js
、b.js
和 c.js
,有两个模块文件 m1.js
和 m2.js
,三个入口文件的内容如下:
// a.js
import('./utils/m1');
import './utils/m2';
console.log('some code in a.js');
// b.js
import('./utils/m1');
import './utils/m2';
console.log('some code in a.js');
// c.js
import('./utils/m1');
import './utils/m2';
console.log('some code in c.js');
这三个入口文件对于 m1.js
都是异步导入,m2.js
都是同步导入。打包输入后果如下:
对于异步导入,splitChunks 拆散出 chunks 造成独自文件来重用,而对于同步导入的雷同模块没有解决,这就是 chunks: 'async'
的默认行为。
initial
把 chunks 的这只改为 initial
后,再来看下输入后果:
同步的导入也会分离出来了,成果挺好的。这就是 initial
与 async
的区别:同步导入的模块也会被选中分离出来。
all
咱们退出一个模块文件 m3.js
,并对入口文件作如下更改:
// a.js
import('./utils/m1');
import './utils/m2';
import './utils/m3'; // 新加的。console.log('some code in a.js');
// b.js
import('./utils/m1');
import './utils/m2';
import('./utils/m3'); // 新加的。console.log('some code in a.js');
// c.js
import('./utils/m1');
import './utils/m2';
console.log('some code in c.js');
有点不同的是 a.js
中是同步导入 m3.js
,而 b.js
中是异步导入。放弃 chunks 的设置为 initial
,输入如下:
能够到看 m3.js
独自输入的那个 chunks 是 b 中异步导入的,a 中同步导入的没有被分离出来。也就是在 initial
设置下,就算导入的是同一个模块,然而同步导入和异步导入是不能复用的。
把 chunks 设置为 all
,再导出康康:
不论是同步导入还是异步导入,m3.js
都拆散并重用了。所以 all
在 initial
的根底上,更优化了不同导入形式下的模块复用。
这里有个问题 ,发现 webpack 的 mode 设置为 production
的状况下,下面例子中 a.js
中同步导入的 m3.js
并没有拆散重用,在 mode 设置为 development
时是失常的。不晓得是啥起因,如果有童鞋晓得的话麻烦解释下。
咱们看到 async
、initial
和 all
相似层层递进的模块复用拆散优化,所以如果思考体积最优化的输入,那就设 chunks 为 all
。
cacheGroups
通过 cacheGroups
,能够自定义 chunk 输入分组。设置 test
对模块进行过滤,符合条件的模块调配到雷同的组。splitChunks 默认状况下有如下分组:
module.exports = {
// ...
optimization: {
splitChunks: {
// ...
cacheGroups: {
defaultVendors: {test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
意思就是存在两个默认的自定义分组,defaultVendors
和 default
,defaultVendors
是将 node_modules
上面的模块拆散到这个组。咱们改下配置,设置下将 node_modules
下的模块全副拆散并输入到 vendors.bundle.js
文件中:
const path = require('path');
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'development',
entry: {
a: './src/a.js',
b: './src/b.js',
c: './src/c.js',
},
output: {path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true,
},
optimization: {
splitChunks: {
chunks: 'all',
minSize: 0,
cacheGroups: {
vendors: {test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
name: 'vendors',
},
},
},
},
plugins: [new BundleAnalyzerPlugin()],
};
入口文件内容如下:
// a.js
import React from 'react';
import ReactDOM from 'react-dom';
console.log('some code in a.js');
// b.js
import React from 'react';
console.log('some code in a.js');
// c.js
import ReactDOM from 'react-dom';
console.log('some code in c.js');
输入后果如下:
所以依据理论的需要,咱们能够利用 cacheGroups
把一些通用业务模块分成不同的分组,优化输入的拆分。
举个栗子,咱们当初输入有两个要求:
node_modules
下的模块全副拆散并输入到vendors.bundle.js
文件中。utils/
目录下有一系列的工具模块文件,在打包的时候都打到一个utils.bundle.js
文件中。
调整 webpack 中的设置如下:
const path = require('path');
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'development',
entry: {
a: './src/a.js',
b: './src/b.js',
c: './src/c.js',
},
output: {path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true,
},
optimization: {
splitChunks: {
chunks: 'all',
minSize: 0,
cacheGroups: {
vendors: {test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
name: 'vendors',
},
default: {test: /[\\/]utils[\\/]/,
priority: -20,
reuseExistingChunk: true,
name: 'utils',
},
},
},
},
plugins: [new BundleAnalyzerPlugin()],
};
入口文件调整如下:
// a.js
import React from 'react';
import ReactDOM from 'react-dom';
import('./utils/m1');
import './utils/m2';
console.log('some code in a.js');
// b.js
import React from 'react';
import './utils/m2';
import './utils/m3';
console.log('some code in a.js');
// c.js
import ReactDOM from 'react-dom';
import './utils/m3';
console.log('some code in c.js');
输入如下:
maxInitialRequests 和 maxAsyncRequests
maxInitialRequests
maxInitialRequests
示意入口的最大并行申请数。规定如下:
- 入口文件自身算一个申请。
import()
异步加载不算在内。- 如果同时有多个模块满足拆分规定,然而按
maxInitialRequests
的以后值当初只容许再拆分一个,抉择容量更大的 chunks。
举个栗子,webpack 设置如下:
const path = require('path');
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'development',
entry: {a: './src/a.js',},
output: {path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true,
},
optimization: {
splitChunks: {
chunks: 'all',
minSize: 0,
maxInitialRequests: 2,
cacheGroups: {
vendors: {test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
name: 'vendors',
},
default: {test: /[\\/]utils[\\/]/,
priority: -20,
reuseExistingChunk: true,
name: 'utils',
},
},
},
},
plugins: [new BundleAnalyzerPlugin()],
};
入口文件内容如下:
// a.js
import React from 'react';
import './utils/m1';
console.log('some code in a.js');
打包输入后果如下:
依照 maxInitialRequests = 2
的拆分过程如下:
- a.bundle.js 算一个文件。
- vendors.bundle.js 和 utils.bundle.js 都能够拆分,但当初还剩一个位,所以抉择拆分出 vendors.bundle.js。
把 maxInitialRequests
的值设为 3,后果如下:
再来思考另外一种场景,入口仍然是 a.js
文件,a.js
的内容作一下变动:
// a.js
import './b';
console.log('some code in a.js');
// b.js
import React from 'react';
import './utils/m1';
console.log('some code in b.js');
调整为 a.js
同步导入了 b.js
,b.js
里再导入其余模块。这种状况下 maxInitialRequests
是否有作用呢?能够这样了解,maxInitialRequests
是形容的入口并行申请数,下面这个场景 b.js
会打包进 a.bundle.js
,没有异步申请;b.js
外面的两个导入模块依照 cacheGroups
的设置都会拆分,那就会算进入口处的并行申请数了。
比方 maxInitialRequests
设置为 2 时,打包输入后果如下:
设置为 3 时,打包输入后果如下:
maxAsyncRequests
maxAsyncRequests
的意思是用来限度异步申请中的最大并发申请数。规定如下:
import()
自身算一个申请。- 如果同时有多个模块满足拆分规定,然而按
maxAsyncRequests
的以后值当初只容许再拆分一个,抉择容量更大的 chunks。
还是举个栗子,webpack 配置如下:
const path = require('path');
const BundleAnalyzerPlugin =
require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'development',
entry: {a: './src/a.js',},
output: {path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true,
},
optimization: {
splitChunks: {
chunks: 'all',
minSize: 0,
maxAsyncRequests: 2,
cacheGroups: {
vendors: {test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
name: 'vendors',
},
default: {test: /[\\/]utils[\\/]/,
priority: -20,
reuseExistingChunk: true,
name: 'utils',
},
},
},
},
plugins: [new BundleAnalyzerPlugin()],
};
入口及相干文件内容如下:
// a.js
import ('./b');
console.log('some code in a.js');
// b.js
import React from 'react';
import './utils/m1';
console.log('some code in b.js');
这个时候是异步导入 b.js
的,在 maxAsyncRequests = 2
的设置下,打包输入后果如下:
依照规定:
import('.b')
算一个申请。- 按 chunks 大小再拆分
vendors.bundle.js
。
最初 import './utils/m1'
的内容留在了 b.bundle.js
中。如果将 maxAsyncRequests = 3
则输入如下:
这样 b.js
中导入的 m1.js
也被拆分进去了。理论状况中,咱们能够依据需要来调整 maxInitialRequests
和 maxAsyncRequests
,集体感觉默认设置曾经够用了。
总结
splitChunks 的设置非常复杂。通过以上的规定解说和举例,置信大家曾经明确拆包中几个要害属性的应用,我集体感觉也是官网文档解释比拟迷的几个,残余的其余属性大家能够通过官网文档找到答案。
我的 JS 博客:小声比比 JavaScript