我们知道,webpack种chunks的默认配置是async
,也就是说webpack
默认只对异步代码进行代码分割。为什么会这样呢?
之前,我们引用的
jquery或lodash
这样的库,我们对这样的代码进行代码分割,不是可以有效提升代码的性能吗? 实际上,当我们把jquery或lodash
这样的库打包生成一个单独的文件的时候,第一次访问的时候,我们加载就可以了,第二次访问的时候,我们就可以借助缓存,提高我们的加载速度。但这样,也只不过是提高我们第二次访问页面的速度。
那真正对页面性能做优化,webpack希望达到的一个效果是第一次访问页面的时候,加载速度就是最快的
,如果想实现这样的效果,靠把jquery或lodash
这样的库打包生成一个单独的文件,是满足不了我们的需求的。那webpack
真正希望我们编写代码的方式是什么样子的呢?
假如我们的index.js
里有如下代码:
实现的功能很简单,当我们点击页面的时候,生成一个div标签,设置标签内容为LEE YANG
,然后把标签挂载到页面上
document.addEventListener('click', () => {
const element = document.createElement('div');
element.innerHTML = 'LEE YANG';
document.body.appendChild(element)
})
然后我们打包运行 npm run build
看起来,我们写的代码是没有任何问题的,但是这段代码完全没有优化的空间了吗? 或者说,这是不是最标准的webpack推荐的一种代码编写的方式呢?实际上不是的。
我们打开控制台,然后输入commond+shift+p
(MAC命令)或者ctrl+shift+p
(window命令),就可以出现这个界面
然后搜索
show coverage
这个关键词,搜到后点击这个功能点击之后会出现下边这个界面
然后点击黑色圆点,开启录制功能
开启后,圆点变成红色,然后我们刷新页面,就可以看到页面加载了一个文化是
main.js
文件,我们可以看到这个文件的利用率,只有74.7%
我们点击main.js就可以看到,我们就可以看到哪些代码是在这个页面被用到的部分,哪些是没有被用到的部分
- 通过上边的图,我们可以看到,页面刷新,我们往DOM上绑定了一个
click
事件,这个事件回调里的代码现在是没有任何用处的,因为页面加载了之后,只有点击了页面之后,才会用到这些代码,我们现在点击一下页面,可以看到,代码被用到了,颜色变成了绿色
image.png
而一开始,刚加载的时候,click事件回调里的代码根本就不会执行,不会执行的代码我们却让页面一加载的时候就把它下载下来,实际上就会浪费页面加载的性能,那webpack
希望,页面交互的代码,应该怎么写呢?
应该把交互的代码放到一个异步加载的模块里去写,我们改造一下代码,新建一个click.js
文件
然后在
index.js
里引入,下边代码的意思是,当我们点击的时候,异步引入handleClick
方法,并执行然后我们打包一下
npm run build
,打包后,我们再打开show coverage
,发现我们的代码利用率,从刚才的74.7%
变成了现在的79.7%
- 之所以利用率变高,是因为,一开始页面加载的时候,并没有加载引入
handleClick
方法这个业务逻辑,而是在我们点击的时候,才开始加载1.js
image.png - 而
1.js
就是我们写的业务逻辑
image.png - 这才是让页面加载速度最快的一种正确的编写方式
所以,我们在写高性能前端代码的时候,现在应该重点考虑的,其实不是缓存这样的东西,而是代码的使用率(使用率越高,我们看到首屏的加载时间越短)
- 所以
webpack
它在打包过程种,是希望我们尽可能多写这种异步加载
的代码的。同时它会认为,同步的代码打包在一起,生成一个vendor.js
意义是不大的,只有多写一些异步的代码,才能让我们网站的性能真正得到提升,这也就是为什么webpack
的配置项里splitChunks
值默认为async
,也就是说,它认为只有异步组件才能真正的提升网页的打包性能。而同步的代码只能增加一个缓存,实际上对性能的提升是非常有限的。
image.png
项目中真正用到异步代码的典型场景。
-
比如,我们点击更换头像时
image.png -
会弹出更换的框
image.png - 那么这个框的代码实际上在头像显示那个页面加载的时候,就不应该加载出来。什么时候加载呢? 实际上应该是在点击更换头像按钮的时候才去加载这个弹框的代码是比较合适的。
- 但是这又带来新的问题,如果点击的时候才去加载弹框的逻辑代码,那么用户的点击交互反应速度就会变慢,那怎么样才能解决这个问题呢?
prefetching 和 preloading
思路是,假如一开始,访问头像展示页的时候,不需要加载更换头像这一块的逻辑,只加载其他部分的逻辑,等这些逻辑都加载完后,页面就展示出来了,当页面被展示出来的时候,页面上的带宽已经被释放出来了,这个时候,我们认为网络已经空闲了,这个头像展示页相关的核心逻辑已经展示出来了,那么我们可以不可以在这个空闲的时候,偷偷把更换头像的弹框的代码下载下来,而不是等点击更换头像的时候才去下载,这样等我们再去点击更换头像的时候,是不是就会迅速的展示出头像更换页?
如果能抓住这个空闲的时间,那就既满足了主要页面核心代码加载非常快的一个要求,又满足了点击更换头像弹框展示非常快的要求。所以这是一个最好的解决方案了
-
这个解决方案就是依赖于 prefetching 和 preloading 这两个webpack打包的特性来帮助我们实现的
image.png - 具体怎么做呢? 我们可以利用一个
魔法注释
的语法,复制下边代码
image.png - 放到我们的
import
语句前
image.png - 它的意思就是,当我们触发click事件的时候,会帮我们加载
click.js
这个文件,当然它并不是非得等我们点击页面的时候才会帮我们加载这个文件,而是一旦发现现在主要的JS已经加载完成了之后,网络带宽有空闲的时候,它就会偷摸的帮我们把JS预先加载好。
我们来试一下效果,为了有对比,我们先把魔法注释去掉,保持原来的代码,打包后
-
打包后,运行代码,一开始只加载下边这两个
image.png - 然后我们点击页面,会加载引入
click.js
这个逻辑
image.png -
然后我们把魔法注释加上去,重新打包运行
image.png - 初始就会在网络空闲的时候加载了引入
click.js
这个逻辑
image.png -
当我们点击页面,会再加载一次
image.png - 因为
1.js
已经加载过了,所以点击页面再加载1.js
的时候用时非常短,因为可以1.js
已经加载过了,再次加载可以充分利用缓存节省时间
所以prefetching 和 preloading 就是这样的功能。但是两者之间还是有些区别的
prefetch会等待我们的核心代码加载完成之后,页面带宽空闲的时候,再去加载prefetch对应的文件
-
preload 它是和主的业务文件一起加载的。
image.png 推荐用prefetch(在某些浏览器上可能会有些兼容问题,这一点要注意)