1、lazy loading
我们通过一个示例来说明什么是懒加载。
// a,js
import './common'
console.log('A')
export default 'A'
// b.js
import './common'
console.log('B')
export default 'B'
// common.js
console.log('公共模块')
export default 'common'
// 异步代码
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {
console.log(a)
})
import(/* webpackChunkName: 'b'*/ './b').then(function(b) {
console.log(b)
})
document.addEventListener('click', () => {
// 异步加载lodash模块
import('lodash').then(({default: _}) => {
console.log(_.join(['3', '4']))
})
})
目录结构:
配置webpack.config.js
:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
entry: {
main: './lazyloading/index.js'
},
output: {
path: path.resolve(__dirname, 'build'), // 打包文件的输出目录
filename: '[name].bundle.js', // 代码打包后的文件名
publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址
chunkFilename: '[name].js' // 代码拆分后的文件名
},
// 拆分代码配置项
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
lodash: {
name: 'lodash',
test: /[\\/]node_modules[\\/]/,
priority: 10
},
common: {
name: 'common',
minSize: 0, //表示在压缩前的最小模块大小,默认值是 30kb,如果没设置为0,common模块就不会抽离为公共模块,因为原始大小小于30kb
minChunks: 2, // 最小公用次数
priority: 5, // 优先级
reuseExistingChunk: true // 公共模块必开启
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
plugins: [
new CleanWebpackPlugin(), // 会删除上次构建的文件,然后重新构建
new BundleAnalyzerPlugin()
]
}
我们打包执行index.html
,当点击页面的时候,异步加载lodash
并输出内容,演示如下:
第一次进入页面时并没有加载lodash
模块,当我点击页面时,浏览器再去加载lodash
,并执行输出,这就是所谓的懒加载。
其实懒加载就是通过import
去异步加载一个模块,至于什么时候去加载,这个要根据业务逻辑来写,比如弹窗组件、模态框组件等等,都是点击按钮之后才出现。
懒加载能够加快页面的加载速度,如果你把详情页、弹窗等页面全部打包放在一个js
中,当用户只是访问首页,只需要首页的代码,不需要其他页面的代码,加入多余的代码只会使得加载时间变长。
import
后面返回的是一个 then
,说明这是一个 promise
类型,一些低端的浏览器不支持 promise
,比如 IE
,如果要使用这种异步的代码,就要使用 babel
以及 babel-polyfill
来做转换,因为我使用的是高版本的 chrome
浏览器,对 ES6
语法是支持的,所以我并没有安装 babel
也能使用。
现在我们再列举一个示例证实懒加载在性能方面的优化
更改index.js
:
// 异步代码
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {
console.log(a)
})
import(/* webpackChunkName: 'b'*/ './b').then(function(b) {
console.log(b)
})
document.addEventListener('click', () => {
var element = document.createElement('div')
element.innerHTML = "hello world"
document.body.appendChild(element)
})
重新打包,并打开 index.html
,打开浏览器控制台,按 ctrl + shift + p
,输入 coverage
就能看到当前页面加载的 js
代码未使用率,红色区域表示未被使用的代码段
这里一开始红色区域的代码未被使用,当我点击了页面后,变成绿色,页面出现了 Hello World
,说明代码被使用了
页面刚加载的时候,异步的代码根本就不会执行,但是我们却把它下载下来,实际上就会浪费页面执行性能,webpack
就希望像这样交互的功能,应该把它放到一个异步加载的模块去写
新建一个 click.js
文件
function handleClick() {
var element = document.createElement('div')
element.innerHTML = 'hello world'
document.body.appendChild(element)
}
export default handleClick
并且将 index.js
文件改为异步的加载模块:
document.addEventListener('click', () => {
import('./click.js').then(({default: fn}) => {
fn()
})
})
重新打包,使用 coverage
分析
当加载页面的时候,没有加载业务逻辑,当点击网页的时候,才会加载 0.js
模块,也就是我们在 index.js
中异步引入的 click.js
当click.js
文件越大时,懒加载对性能的提升越加明显
所以想写出高性能的代码,不仅仅考虑缓存,更要考虑到代码的使用率
所以 webpack
在打包过程中,是希望我们多写这种异步的代码,才能提升网站的性能,这也是为什么 webpack
的 splitChunks
中的 chunks
默认是 async
2、prefetch
异步能提高你网页打开的性能,而同步代码是增加一个缓存,对性能的提升是非常有限的,因为缓存一般是第二次打开网页或者刷新页面的时候,缓存很有用,但是网页的性能一般是用户第一次打开网页,看首屏的时候。
当然,这也会出现另一个问题,就是当用户点击的时候,才去加载业务模块,如果业务模块比较大的时候,用户点击后并没有立马看到效果,而是要等待几秒,这样体验上也不好,怎么去解决这种问题
一:如果访问首页的时候,不需要加载详情页的逻辑,等用户首页加载完了以后,页面展示出来了,页面的带宽被释放出来了,网络空闲了,再「偷偷」的去加载详情页的内容,而不是等用户去点击的时候再去加载
这个解决方案就是依赖 webpack
的 Prefetching/Preloading 特性
修改index.js
:
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */ './click.js').then(({default: fn}) => {
fn()
})
})
webpackPrefetch: true
会等你主要的 JS
都加载完了之后,网络带宽空闲的时候,它就会预先帮你加载好
重新打包后刷新页面,注意看 Network
可以看到还没有点击页面时,当主要的js
都加载完后,0.js
(也就是click.js
)在网络空闲时就加载了。点击页面时,第二次加载就利用缓存中的0.js
,发现加载时间由29ms
缩减到4ms
,可想而知预加载对页面性能提升的重要性。
这里我们使用的是 webpackPrefetch
,还有一种是 webpackPreload
区别:与 prefetch 相比,Preload 指令有很多不同之处:
Prefetch会等待核心代码加载完之后,有空闲之后再去加载。Preload 会和核心的代码并行加载,还是不推荐
总结:
针对优化,不仅仅是局限于缓存,缓存能带来的代码性能提升是非常有限的,而是如何让代码的使用率最高,有一些交互后才用的代码,可以写到异步组件里面去,通过懒加载的形式,去把代码逻辑加载进来,这样会使得页面访问速度变的更快,如果你觉得懒加载会影响用户体验,可以使用 Prefetch 这种方式来预加载,不过在某些游览器不兼容,会有兼容性的问题,重点不是在 Prefetch 怎么去用,而是在做前端代码性能优化的时候,缓存不是最重要的点,最重要的是代码使用的覆盖率上(coverage)