持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
在 webpack 中,总共提供了三种方式来实现代码拆分(Code Splitting):
这里我们主要讲如何通过splitChunks抽取公共代码。在讲解之前,再来复习下 webpack 中三个重要的概念:module、chunks、bundle。
splitChunks 默认配置
由于 Webpack 做到了开箱即用,所以splitChunks是有默认配置的:
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
name: true,
cacheGroups: {
vendors: {
// // 正则规则,如果符合就提取 chunk
test: /[\/]node_modules[\/]/,
// 缓存组优先级,当一个模块可能属于多个 chunkGroup,这里是优先级
priority: -10
},
default: {
minChunks: 2,
priority: -20,
// 如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再重新产生一个
reuseExistingChunk: true
}
}
}
}
};
splitChunks默认配置对应的就是 chunk 生成的第二种情况:通过写代码时主动使用import()来动态加载。下面来看下使用import()来写代码,在 webpack 打包的时候有什么不同。
创建index.js,使用import()动态加载react模块,同时为了方便跟踪产出物,在这里使用了 webpack 的魔法注释,保证输出的 bundle 名称,后面也使用这种方式。内容如下:
// index.js
import(/* webpackChunkName: "react" */ 'react');
添加webpack.config.js,内容如下:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'production',
entry: {
main: './src/index.js'
},
plugins: [new BundleAnalyzerPlugin()]
};
在这里使用了webpack-bundle-analyzer插件来查看 webpack 打包情况。
执行webpack --config webpack.config.js命令
可以发现,index.js 打包出来了两个文件 react.js 和 main.js,参考下图可知:
index.js打包出来了两个文件react.js和main.js;react.js是被拆分出来的,内容实际是 react;react.js被拆分出来是因为splitChunks默认配置chunks='async'。理解splitChunks.chunks三个值
splitChunks中的chunks是一个很重要的配置项,表示从哪些 chunks 里面抽取代码,chunks的三个值有:"initial"、 "all"、 "async", 默认就是是async。
为了理解splitChunks.chunks三个值的差异,下面通过实例来帮助我们理解。首先创建两个文件a.js和b.js:
// a.js
import react from 'react'
import $ from 'jquery'
const a = 'I am a.js'
export default a
// b.js
import $ from 'jquery'
import(/* webpackChunkName: "b-react" */ 'react')
const b = 'I am b.js'
export default b
这两个文件的特点是:
react模块被两个文件都引入了,不同的是a.js是同步引入,b.js是动态引入;jquery模块在两个文件中都被引入,并且都是同步引入;
下面是我们的webpack.config.js文件内容,我们主要修改是chunks的三个值:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'development',
entry: {
a: './src/a.js',
b: './src/b.js'
},
plugins: [new BundleAnalyzerPlugin()],
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
chunks: 'async', // 这里是我们修改的地方,async|initial|all
test: /[\/]node_modules[\/]/
}
}
}
}
};
chunks='async'
执行打包命令后,dist文件如下:
通过实践观察,在这种模式下:
在a.js和b.js都同步引入的jquery被打包进了各自的 bundle 中没有拆分出来共用,说明在这种配置下只会针对动态引入的的代码进行拆分;
react在a.js和b.js表现不同:
在a.js因为是同步引入的,设置的chunks='async',所以不被拆分出去;在b.js是动态引入的,符合chunks='async'的设置,所以被单独拆到vendors-node_modules_react_index_js;chunks='initial'
initial 即原始的最初的意思,原则就是有共用的情况即发生拆分。首先,动态引入的模块不受影响,它是无论如何都会被拆分出去的。而对于同步引入的代码,如果有多处都在使用,则拆分出来共用,至于共同引用多次会被拆分,是通过minChunks单独配置的,针对这个原则,我们再来看下上面的代码拆分的结果:
因为jquery模块是a.js和b.js共用的代码,所以单独拆除来放到vendors-node_modules_jquery_dist_jquery_js.js中;react在b.js因为用的是动态引入,所以被拆成了b-react.js(名字来自于设置的魔法注释);a.js的react则被拆到了vendors-node_modules_react_index_js.js;chunks='all'
在chunks='initial'配置下,虽然a.js和b.js都引入了react,但是因为引入方式不同,而没有拆分在一起,而是各自单独拆封成一个 chunk,要想把react放到一个文件中,就要使用chunks='all'了。下面是chunks='all'的配置结果:
通过执行打包结果,跟我们的预期一致,chunks='all'的配置下能够最大程度的生成复用代码,所以一般来说chunks='all'是推荐的方式,但是async和initial也有其存在的必要,理解三者差异,根据项目实际代码拆分需求来配置即可。
使用 cacheGroups
cacheGroups(缓存组)是 webpack splitChunks 最核心的配置,splitChunks的配置项都是作用于cacheGroup上的,也就是cacheGroups缓存组可以继承和覆盖来自 splitChunks.* 的任何选项。
为什么取名叫缓存组呢?
比如 index.js 引入了两个库,一个是 lodash,一个是 jquery,当 webpack 打包的时候发现这两个库都满足上面的拆分要求,于是先打包 lodash,打包完后放在缓存组当中缓存起来,接着继续打包 jquery,打包完后也放在缓存组中,这样就可以在一个打包文件中同时容纳了 lodash 和jquery,如果没有缓存组的概念,就需要打包成两个文件,现在只需要打包成一个文件即可。
我们可以做一个实验来验证下:
// a.js
import react from 'react'
import $ from 'jquery'
const a = 'I am a.js'
export default a
webpack.config.js配置:
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
chunks: 'all',
test: /[\/]node_modules[\/]/
}
}
}
},
打包后的文件为vendors-node_modules_jquery_dist_jquery_js-node_modules_react_index_js.js,从下面图中可以看到这个文件包含了 jquery 和 react。
如果想要分开打包呢?
只需要给每个库分别取一个名字,这里我们没有把库的名字写死,而是通过在程序打包过程中动态获取满足打包条件的库的名字,作为打包之后的文件名,webpack 是支持这种写法的,这也是在实际使用中常用方式。具体配置如下:
cacheGroups: {
vendors: {
chunks: 'all',
test: /[\/]node_modules[\/]/,
name(module) {
const packageName = module.context.match(
/[\/]node_modules[\/](.*?)([\/]|$)/
)[1]
return `${packageName.replace('@', '')}`
}
}
}
从下图可以看到把 react 和 jquery 进行分开打包:
注意:cacheGroups不会对异步代码生效,因为异步代码肯定会分割的。
暂无评论内容