观察下面的代码:
//index.js
import _ from 'lodash';//大小为1mb
console.log(_.join(['a', 'b', 'c'],'-'))//这一行的大小为1mb
我们假设lodash包的大小为1mb,index.js中的console.log(_.join(['a', 'b', 'c'],'-'))
也需要1mb的大小。这样打包之后,我们发现:
- 打包后生成的
main.js
文件较大(2mb左右),加载时间会很长; - 一般情况下,我们不会更改lodash包中的内容,但是index.js中的代码我们可能会随着业务需求的改变而修改,所以当我们改变了index.js 的内容之后,用户需要重新加载一次main.js,响应时间较长,浪费了网络资源。
有没有方法能够使lodash和业务代码分开打包呢?
这其实就是代码分割!
手写代码分割:
我们新建一个lodash.js
import _ from 'lodash';
window._ = _
在lodash.js文件中单独引入lodash,并将lodash挂载到window上面。
修改index.js:
console.log(_.join(['a', 'b', 'c'],'-'))
然后修改webpack.config.js:
...
entry: {
lodash: './src/js/lodash.js',//添加新的入口文件,并且lodash.js应该在main的上面
main: './src/js/index.js'
},
...
现在再次打包,我们发现打包后生成了两个文件,分别是main.js(1mb)和lodash.js(1mb),如果此时我们的业务逻辑发生了变更修改了main.js,用户就只需要再次加载main.js就行了,而不需要加载没有发生改变的lodash.js,lodash.js可以直接从缓存中进行加载。
注意: 这两种方式下的第一次加载都需要加载接近2mb的文件,但是代码分割后的打包文件有两个,浏览器可以并行加载(并不是一定会并行加载),加载的时间也有可能减少。但是第二次加载时,如果业务代码发生了改变,代码分割的优势就出来了。
webpack中的Code Splitting
手动进行代码分割是十分麻烦的,而webpack中的一些插件可以帮助我们自动进行代码分割。
修改webpack.config.js:
optimization: {
splitChunks: {
chunks: 'all'
}
}
//index.js
import _ from 'lodash';
console.log(_.join(['a', 'b', 'c'],'-'))
再次打包,最后仍然可以生成两个打包文件:
这里的index.js中是同步代码,如果我们异步引入lodash,即使不配置 splitChunks: { chunks: 'all' }
,webpack依然会进行代码分割,因为其具有默认配置项 splitChunks: { chunks: 'async' }
,表示webpack默认会对异步引入的包进行代码分割,例如下面的index.js:
//index.js
function getComponent(){
return import('lodash').then(({default: _ })=>{
var ele = document.createElement('div')
ele.innerHTML = _.join(['Niall','August'],' ')
return ele
})
}
getComponent().then( ele => {
document.body.appendChild(ele)
})
打包后:
其中的0.js就包含了异步引入的lodash包,而main.js中仍然只包含了业务代码。
这里还可以使用webpack的魔法注释修改0.js为自己想命名的名字:
//index.js
function getComponent(){
return import(/* webpackChunkName:"lodash" */'lodash').then(({default: _ })=>{
var ele = document.createElement('div')
ele.innerHTML = _.join(['Niall','August'],' ')
return ele
})
}
getComponent().then( ele => {
document.body.appendChild(ele)
})
打包后:
SplitChunksPlugin 配置参数详解
如果我们没有配置SplitChunksPlugin,那么webpack将使用一下的默认配置项:
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true//复用已被打包的模块
}
}
}
}
};
配置项 | 取值 | 作用 |
---|---|---|
chunks | all | 对所用的代码进行代码分割 |
chunks | async | 仅仅对异步的代码进行代码分割 |
cacheGroups.group | 一个对象,如vendors:{……} | 打包的文件如何分组分组打包的判断依据,详情可见后文 |
minSize | int | 当文件大小小于该值(以字节为大小单位)的时候,不进行代码分割 |
maxSize | int | 当打包后的代码文件大小大于该值时,对其再进行一次代码分割(一般不使用) |
minChunks | int | 当该模块的引入次数大于该值时才对该模块进行代码分割 |
maxAsyncRequests | int | 最多将打包文件分割为该值个文件,例如,当值为5的时候,前四个需要分割打包的group会分别形成js文件,之后的group全部加入到第五个js文件(一般情况下不需要改变) |
maxInitialRequests | int | 和maxAsyncRequests 类似,但是仅对入口文件起效(一般情况下不需要改变) |
automaticNameDelimiter | 任意字符 | 文件名中的连接符,默认是‘~’ |
name | boolean | 是否启用自定义文件名(一般不需要改变) |
vendors表示一个打包组,它的配置项表明了那些文件可以分到vendors下, vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }
,test 后面是一个正则表达式,当文件资源路径符合该正则表达式时,就将该文件归于vendors,当某一文件符合多个cacheGroups.group的要求时,根据cacheGroups.group.priority来决定其归于哪一个cacheGroups.group