本文主要介绍前端性能优化中的资源预加载,不仅会介绍常规的一些预加载手段;还会介绍工程实践中的应用。
涉及内容:
前言
1.常规做法
1)async/defer:无阻塞加载
defer:DOMContentLoaded事件触发前执行;在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded事件触发前执行,因此最好只包含一个延迟脚本;
async:加载完立即执行,无法控制执行时机和执行顺序。适用于无依赖的外部独立资源。
不足:仅限于脚本资源;执行时机不可控或存在执行顺序问题,用于非关键资源。
2)使用ajax加载资源:可以实现预加载。
不足:优先级较低,无法对首屏资源提前加载。
原始解析做法:
采用预解析(扫描)器:
不足:
-仅限解析HTML中收集到的外链资源,对JS异步加载的资源无法提前收集。
-未暴露类似于Preload的onload事件。
资源推送:
特点:多页面共享pushcache(动态数据json除外)
适用场景:如果不推送这个资源,浏览器就会请求这个资源。
需要注意:要确保没有发起不必要的推送,浪费流量。可以使用preload标签代替,或者在HTTP头中加nopush属性。
Tips:如果服务器或者浏览器不支持HTTP/2,那么浏览器就会按照preload来处理这个头信息,预加载指定的资源文件。
-Edge和Safari的支持不好,慎用;
-如果浏览器不从pushcache中获取资源,推送反而不利于网页性能;
-只能推送不带请求体的GET和HEAD请求;
-pushcache中的资源只能使用一次;
-不考虑客户端的缓存,始终推送:
-只对第一次访问的用户开启服务器推送;
-保守起见,推送原本内联的资源,这样即使多推,也比内联效果好点;
-将资源文件的缓存状态更新至客户端的Cookie。
-仅能推送同源资源;
-pushcache:只在会话中存在,一旦会话结束就会被释放;
-无load/error事件
【问题】
1)动态资源的提前推送,注意参数需固定,不带随机变量的;
2)服务端渲染(直出同构)
比如node层模板渲染时,将同步接口中拉取的数据同步数据先缓存起来,如挂载在window._data,使用时直接从全局变量上读取。
好了,上面介绍了一些常用的预加载方法,下面主角登场~
preload和prefetch
概念
preload
prefetch
首次渲染时不需要,之后可能需要。优先级较低,在浏览器空闲时才会下载。使用场景:比如当前页可能跳转的页面,或者条件加载的资源。
特点
-preload的资源存储在内存缓存中(没有设置资源的缓存策略时);
-下载但不执行;
-异步加载,不影响当前页面的渲染;
-提前加载资源,在真正使用时,直接从从缓存中读取;
-使用场景
-当分析当前页面用户高频点击的链接,分析提取跳转页上的资源,使用prefetch预加载。
-font字体文件的预加载由于字体文件必须等到CSSOM构建完成并且作用到页面元素了才会开始加载,会导致页面字体样式闪动。而浏览器为了避免FOUT,会尽量等待字体加载完成后,再显示应用了该字体的内容,会导致加载完成前显示空白。
检测prelaod和prefetch的支持情况
let{relList}=document.createElement('link');returnrelList&&relList.supports&&relList.supports('preload');如何使用
不同值表明资源类型,对应的优先级不同:style,script,image,media,document,font。(问题:官方说法:不带“as”属性的preload的优先级将会等同于异步请求。测试后发现不写as并没有发请求。)
注意事项
-造成二次下载
-同一资源分别使用as='style'和as='script'预加载,会造成二次下载;
-prefetch和preload同时对同一资源使用,会造成二次下载;
-实际是script脚本,但使用as='style'会造成二次下载;
-preload字体时不带crossOrigin(默认指定anonymous匿名,不带认证信息),同样会造成二次下载preload字体时即使同域也需要带crossOrigin,否则同样会造成二次下载;Requestswithoutcredentialsuseaseparateconnection。
-没有使用preload资源,Chrome会在onload事件3s后做出警示,避免无效的优化,浪费流量。
-浏览器对同一域名有并行加载数限制,因此考虑域名拆分等优化。
实践
背景知识
//若支持preload,异步下载完不会立即执行
参见loadCSS,提供了css的preload的polyfill实现(只展示关键代码):
适用于对于首页无关的样式:
由于preload能够异步加载样式,因此可以避免在加载首页无关样式时阻塞初始渲染。
对于首页初始渲染中重要的样式:
1)内联(注意,会将静态资源的缓存策略与页面的缓存策略捆绑)
2)HTTP/2的serverPush
“知道了preload和prefetch的用途,那如何结合项目实践呢?由于webpack目前基本是项目必备,所以首先介绍结合webpack的使用;然后对quiklink进行简单介绍。”
结合webpack的实践
1.使用插件:PreloadWebpackPlugin
常用的配置如下:
newPreloadWebpackPlugin({//preloadorprefetch方式rel:'',/**即
1)根据chunk类型进行处理:
include:'asyncChunks'|'initial'|'allChunks'asyncChunks:import()动态导入的模块,可以使用prefetch方式异步加载模块;
initial:初始化需要的模块;
allChunks:处理所有模块(asyncChunks&initial)。
2)对已知命名的chunk,可以更精确的使用数组的方式配置需要使用的chunk
include:['vendor','index']注意事项
1)需要结合HtmlWebpackPlugin插件使用
2)必须放到HtmlWebpackPlugin后面,因为PreloadWebpackPlugin需要使用其提供的hook钩子将构造的插入html中:
plugins:[newHtmlWebpackPlugin(),newPreloadWebpackPlugin()]使用效果
对某个页面中include的资源,最终会在对应页面head中插入link标签:
2.结合import()
好处:拆分chunk,减少首屏js体积。
如果工程没有使用HtmlWebpackPlugin,可以对动态导入的资源做如下处理:
import(/*webpackPrefetch:true*/)import(/\*webpackPreload:true\*/)【版本限制】需webpackv4.6.0+才支持预取和预加载。本地测试后,发现prefetch可用,preload无效。
quiklink
旨在成为根据用户viewport中的链接预取内容的简易解决方案。
工作原理
1)IntersectionObserver(交叉观察器):检测当前视口的links
lettarget=document.getElementById('a');io=newIntersectionObserver(entries=>{},{threshold:[1]//交叉区域为1时会触发callback});io.observe(target);【备注】常规的主要是通过getBoundingClientRect()获取元素在视口中的详细位置,来实现滚动加载以及吸附等功能。
2)requestIdleCallback:等到浏览器空闲时;
注意其和requestAnimationFrame的区别.
3)检查当前的网络环境:
navigator.connection.effectiveType//4G、2G...;
4)提供了多种方式预取链接
工作流程:
1)浏览器空闲时,获取页面所有a标签的链接links;
2)使用IntersectionObserver对link进行监听;
3)在视口区的link,使用prefetch下载;
4)判断当前网络状况,若使用的是2G或者开启了省流模式(data-saver),则不做处理。
data-saver:Theusermayenablesuchpreference,ifmadeavailablebytheuseragent,duetohighdatatransfercosts,slowconnectionspeeds,orotherreasons.
题外话:prefetch有点偷流量的意思,用户想看什么才消耗对应资源产生的流量,而prefetch则会擅自做主,偷偷下载很多可能并不需要的资源(在早前流量特贵的时候这么做,估计会被骂...)
使用说明
总结
综合来看,PreloadWebpackPlugin更适合对chunk而非html文件的处理;而quikLink更适合博客类的网站,或者服务端渲染的页面,这样才能实现"秒开"的预期效果。