Skip to main content

分割打包 Bundle Splitting

概述

当项目的多个页面使用相同的基础库,或者引用了相同的模块时,对每个页面都打包一遍是很浪费资源的。所以,需要在打包的时候通过提取公共资源来减少打包后bundle的体积。在webpack 3版本时,一般会使用CommonsChunkPlugin插件进行资源提取,但是在webpack 4版本中,推荐使用SplitChunksPlugin来代替CommonsChunkPlugin,而CommonsChunkPlugin已被删除。

SplitChunksPlugin

SplitChunksPluginwebpack的内置插件,不需要单独安装,其各字段的默认配置如下:

webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'async', // async 分离异步引入的库,initial 分离同步引用的库,all 分离所有类型的库
minSize: 30000, // 进行脚本分离的最少字节
minRemainingSize: 0,
maxSize: 0,
minChunks: 1, // 设置最少引用次数为1次
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: '~',
automaticNameMaxLength: 30,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/, // 引用模块的匹配规则,此处设置意味着将node_modules下的所有模块都打包到defaultVendors中。
priority: -10 // 一个模块可能属于多个cache group,当优先级高的group可以优先选择模块。优先级一样的话,size大的优先被选择。默认值为0
},
default: {
minChunks: 2, // 覆盖splitChunks.*中的配置项
priority: -20,
reuseExistingChunk: true // 当module未变时,是否可以使用之前的chunk
}
}
}
}
};

cacheGroups对象能够继承或者覆盖splitChunks.*中的配置项,但是testpriorityreuseExistingChunk仅能在其子对象内部使用。cacheGroups中还可以在子对象内部使用name字段,当不设置name字段时,提取后的chunk与其子对象名相同。

上述配置中还有几个字段没有介绍,如果感兴趣可以查看官网文档

相关代码

为了证明webpack能够提取公共资源,我们建立了index.jsdetail.js文件,两个文件中都引用了React包,我们尝试对两个文件中的reactreact-dom进行提取。

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import common from '../../commons';

class Resource extends React.Component {
constructor() {
super(...arguments);
}

render() {
return (
<div>
<p>{common()}</p>
<p>resource extraction</p>
</div>
);
}
}

ReactDOM.render(<Resource />, document.getElementById('root'));
  • detail.js
import React from 'react';
import common from '../../commons';

class Detail extends React.Component {
constructor() {
super(...arguments);
}
render() {
return (
<div>
<p>{common()}</p>
<input type="text" />
</div>
);
}
}

export default Detail;

相关配置

  • webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
resource: './src/split/index',
detail: './src/split/detail'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_bundle.js',
chunkFilename: '[name].chunk.js'
},
mode: 'production', // mode需要设置为production
module: {
rules: [
{
test: /.js$/,
use: 'babel-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/split/index.html`),
filename: 'index.html',
chunks: ['vendors', 'resource', 'detail']
})
],
// 公共资源提取配置
optimization: {
splitChunks: {
minSize: 0,
cacheGroups: {
// 提取公共包
vendor: {
test: /(react|react-dom)/,
name: 'vendors',
chunks: 'all'
},
// 提取公共文件
commons: {
chunks: 'all',
minChunks: 2
}
}
}
}
};

结果展示

项目中的所有代码已经上传到github仓库resource-extraction中,可以下载下来执行一下。

当我们未配置optimization.splitChunks时,执行npm run build的构建结果如下:

当配置后,构建的结果如下所示:

通过上面的结果可以看出,通过使用SplitChunksPlugin我们可以将公共资源(reactreact-dom)提出到vendors.chunk.js文件中,将引用的commons文件提取到commons~detail~split.chunk.js文件中,对应打包后的boundle文件体积都有减小。因为detail只引用了react包,所以减少的体积较少。

参考资料

  1. 提取页面公共资源,by 程柳锋
  2. Webpack 的 Bundle Split 和 Code Split 区别和应用, by JS 菌
  3. webpack document