简介:代码分割是webpack最大的一个特点,通过这个特点可以将你的代码切割进多个bundles里面,从而从而可以进行按需加载或者并行加载,可以实现更小的bundle和控制资源加载的优先级。
在说代码分割之前,我们先来了解几个概念:
1、bundles:bundles是指webpack打包后文件的统称,单一入口打包出来就只有一个文件,图片,字体文件等其他格式的文件也会被打包进这个bundle文件以base64的形式存在。但是实际开发中我们往往不会这么做,这样会打包出来一个很大的js文件,缺点不必多说,首先是第一次加载时会将所有资源都加载进来,造成首次加载白屏时间过长,第二是无法利用浏览器的并行加载,使加载时间过长,第三是会加载一些可能用不上的资源,造成带宽浪费。
2、chunk:chunk的意思是代码块,一个bundle由一个或者多个chunk组成,一个chunk由一个或多个module组成,chunk是进行代码切割的基础。对于bundle与chunk的之间的关系,webpack打包后的结果拆分成多个代码块,我们称之为chunk,这些chunk是bundles中的一员,因此也可以称之为bundle,就像有一瓶水,我们把它倒进几个杯子,一瓶水就相当于这里的bundles,一杯水就相当于chunk。
3、module:模块,我觉得这与我们项目中架构的模块有所不同,webpack中的模块即文件,每个文件都被看做一个模块。打个比方,假如我们有一个登录模块,里面包含了js,css,img等,在抽象概念中,我们将这些文件抽象地统称为一个模块,即登录模块,而webpack眼中则不同,它是一个文件就是一个模块,即以物理文件的形式划分模块。模块是进行代码分割的最小单位,也就说,我们不能对一个moudle进行更加颗粒化分割,即使这个文件很大。打个比方,现在有一个第三方库,这个库有1M,我们不可能将这个库分割成两个512KB的bundle。当然这得看库的设计者怎么设计打包后的文件了,如果此库被打包成一个文件,我们是无法分割的,比如jquery。如果像是element-ui,库本身对每个组件进行了单独的打包,并不是将所有组件打包进一个文件,所以我们可以进行按需加载。当然对于一些单个较大的文件,我们还有其它优化方式,比如混淆压缩,tree-shaking等。
webpack中代码分割的方式,通常有如下三种:
1、Entry Points:通过配置入口文件来进行分割,这是最简单和最直接的方式,但是这种方式有一定缺点,可能造成代码重复打包,本文讨论的是另一种方式,本文不做讨论。
2、Prevent Duplication: 使用splitChunksPlugin来进行公共代码提取。
3、Dynamic Imports:通过动态代码加载来分割代码,使用import()方法。
调用import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中。import方法依赖于Promise,如果需要在低版本浏览器使用,需要进行polyfill。
import的使用
//a.js
import('./b.js').then(b => {
//doSomething
})
import 规范不允许控制模块的名称或其他属性,因为 "chunks" 只是 webpack 中的一个概念,但是我们可以通过注释接收一些特殊的参数,而无须破坏规定:
webpackChunkName:手动指定模块的名称
webpackMode:指定webpack以什么模式解析动态导入
//index.js
let util = 'a'
import(`./util/${util}.js`).then(res => {})
//util/a.js
export function add() {
console.log('add')
}
//util/b.js
export const arr = [1, 2, 3]
打包后的目录结构如下
我们可以看到,除了生成一个主bundle(index.js),还生成了1.js和2.js,分别对应a.js和b.js的chunk。
注意事项:
//index.js
import('./util/a')
import('./util/b')
//util/a.js
import { arr } from './b'
export function add() {
console.log('add' + arr)
}
//util/b.js
export const arr = [1, 2, 3]
上面代码打包结果:
乍一看,发现并没问题,但是当我们打开打包后的文件,b模块被打包进了1.js,也就是a模块拆分出来的chunk里面
//1.js
;(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
[1, 2],
[
,
function(module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, 'arr', function() {
return arr
})
const arr = [1, 2, 3]
},
function(module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, 'add', function() {
return add
})
/* harmony import */
var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1)
function add() {
console.log('add' + _b__WEBPACK_IMPORTED_MODULE_0__['arr'])
}
}
]
])
然后我们查看2.js
//2.js
;(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
[2],
[
,
function(module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, 'arr', function() {
return arr
})
const arr = [1, 2, 3]
}
]
])
然后我们发现2.js就是b模块,这样b模块就被重复打包了。
再来看另一种情况
//index.js
import { add } from './util/a'
import { arr } from './util/b'
add()
//util/a.js
export function add() {
console.log('add' + arr)
}
import('./b')
//util/b.js
export const arr = [1, 2, 3]
此时打包结果如下,并没有生成额外的chunk。
总结:webpack在处理模块时,从入口文件开始,如果遇到静态导入的模块,则打包进当前chunk,如果遇到动态导入,则判断当前chunk是否已经包含此模块,如果已经包含,则不会生成额外的chunk,如果没有则生成新的chunk。在处理静态导入时,不管有没有此模块的chunk,都会将动态模块打包进当前chunk。