当前位置: 首页 > Web前端 > HTML

使用Preload&Prefetch优化前端页面的资源加载

时间:2023-04-02 18:58:41 HTML

对于前端页面来说,静态资源的加载对页面性能有着至关重要的作用。本文将介绍浏览器提供的两条资源指令——preload/prefetch,可以辅助浏览器优化资源加载的顺序和时机,提高页面性能。1.从一个例子入手如上图所示,我们开发了一个简单的收银机。在支付过程中,您可以展开优惠券列表,选择对应的优惠券。从动图可以看出,第一次展开列表时,优惠券的背景逐渐显示出来,体验不是很好。问题的原因也很明显。由于后台使用了视觉化设计的图片,展开优惠券列表时需要加载优惠券列表。背景淡出的过程其实就是加载图片的过程;当网络速度慢时,这个问题会更加明显。那么,如何解决这个问题呢?仔细分析后,我们会发现问题的原因是背景图片的加载时间来不及。如果可以在优惠券列表渲染之前加载背景图,就不会出现这个问题。从这个思路出发,我们可能会想到以下两种解决方案:使用内联图片,即将图片转成base64编码的data-url。这样一来,图片信息实际上就集成到了css文件中,避免了图片资源的单独加载。但是图像内联会增加css文件的大小,增加首屏渲染时间。使用js代码预加载图片preloadImage(){constimgList=[require('@/assets/imgs/error.png'),require('@/assets/imgs/ticket_bg.png')];for(leti=0;i标签实现预加载。其中,rel="prefetch"称为Resource-Hints(资源提示),是辅助浏览器进行资源优化的指令。类似的指令还有rel="preload",我们后面会提到。......查看当前优惠券列表的加载效果。果然,顺利达到了我们预期的效果。那么浏览器是如何做到的呢?我们打开Chrome的Network面板看看:可以看到首屏的请求列表中已经出现了优惠券背景图片ticket\_bg.png的加载请求,而且这个请求本身看起来和普通请求没什么区别;展开coupon列表后,在网络中添加了一个新的ticket\_bg.png访问请求。我们很快发现,虽然这个请求的状态也是200,但是它有一个特殊的标记——prefetchcache,说明这次请求的资源来自于prefetchcache。这个性能验证了上面prefetch的定义,即浏览器在空闲时预加载资源,真正使用时直接从浏览器缓存中快速获取。3、预加载从上面的案例中,我们体会到了浏览器预加载资源的强大能力。其实preloading是一个广义的概念,prefetch只是其中的一种具体实现方式。本节介绍另一种预加载方式preload。上面我们提到了preload和prefetch都属于浏览器的Resource-Hints,用来辅助浏览器进行资源优化。为了区分两者,prefetch通常译为预取,preload译为预加载。元素的rel属性的属性值preload允许你在你的HTML页面中的元素内部写一些声明性的资源获取请求,它可以在页面加载后立即指示需要哪些资源。对于立即需要的资源,您可能希望在页面加载生命周期的早期开始获取它们,在浏览器的主要渲染机制介入之前预加载它们。这种机制可以让资源更早地加载和可用,并且不太可能阻塞页面的初始渲染,从而提高性能。简单来说,就是通过标签显式声明一个高优先级资源,强制浏览器提前请求该资源,不会阻塞正常文件的加载。我们还用一个实际案例进行详细介绍。上图是我们开发的另一台收银机。出于本地化考虑,设计中使用了自定义字体。开发完成后,我们发现第一次加载页面时,文字会短暂闪烁(FOUT,FlashofUnstyledText),在网络不好的情况下更加明显(如动图所示).原因是字体文件是css导入的,会在css解析后加载。在加载完成之前,浏览器只能使用退化的字体。也就是说字体文件加载的太晚了,需要告诉浏览器提前加载,这也正是preload派上用场的地方。我们在入口html文件的头部添加preload标签:..."crossorigin>"crossorigin>...再次查看页面第一次加载的效果:闪烁的字体样式没有了!让我们比较一下使用预加载前后的网络面板。使用前:使用后:可以发现字体文件的加载时机明显提前,浏览器收到html后很快就加载了。注意:预加载链接必须设置as属性声明资源类型(字体/图片/样式/脚本等),否则浏览器可能无法正确加载资源。四、Preload和Prefetch的具体做法1.Preload-webpack-plugin上一篇我们给出的两个例子都是在入口html中手动添加相关代码:........."crossorigin>"crossorigin>...这样显然不够方便,资源路径是硬编码在页面中的(实际上,ticket_bg.a5bb7c33.后缀中的hash本身是行不通的)。webpack插件preload-webpack-plugin可以帮助我们自动化这个过程,结合htmlWebpackPlugin在构建过程中插入链接标签。constPreloadWebpackPlugin=require('preload-webpack-plugin');...plugins:[newPreloadWebpackPlugin({rel:'preload',as(entry){//资源类型if(/\.css$/.test(entry))return'style';if(/\.woff$/.test(entry))return'font';if(/\.png$/.test(entry))return'image';返回'script';},include:'asyncChunks',//预加载模块范围,也可以取值'initial'|'allChunks'|'allAssets',fileBlacklist:[/\.svg/]//资源黑名单fileWhitelist:[/\.script/]//资源白名单})]PreloadWebpackPlugin配置总体来说比较简单,需要注意include属性。该属性的默认值为'asyncChunks',表示只预加载异步js模块;如果需要预加载图片、字体等资源,需要设置为'allAssets',即处理所有类型的资源。但是一般情况下,我们不想把preloading的范围扩大太多,所以需要通过fileBlacklist或者fileWhitelist来控制。对于异步加载的模块,还可以通过webpack内置的/_webpackPreload:true_/标志进行更细粒度的控制。以下面的代码为例,webpack会生成一个标签,并添加到HTML页面的头部。import(/*webpackPreload:true*/'AsyncModule');注意:prefetch的配置和preload类似,只是不需要设置as属性。2.使用场景从上面的介绍我们可以看出preload的初衷是尽快加载首屏需要的关键资源,从而提高页面渲染性能。目前浏览器基本具备预测分析能力,可以提前分析出入口html中外部链接的资源,所以不需要预加载入口脚本文件、样式文件等。但是,一些隐藏在CSS和JavaScript中的资源,比如字体文件,虽然是首屏的关键资源,但浏览器只有在解析完CSS文件后才会加载这些资源。这种场景适合使用preload的声明,尽早加载资源,避免页面渲染延迟。与preload不同,prefetch声明了以后可能访问的资源,因此适用于异步加载的模块和其他可能重定向到的路由页面的资源缓存;对于一些以后访问概率较大的资源,比如上面案例中的优惠券、列表的背景图、常见的加载失败图标等,也比较适用。3.BestPractices基于上面的使用场景分享,我们可以总结出一个更通用的最佳实践:在大多数场景下,不需要刻意使用类似preload的字体文件,这些字体文件是隐藏在脚本中的首屏关键资源和风格,建议对异步加载的模块(典型的如单页系统中的非首页)使用preload。建议对可能即将访问的资源使用预取。您可以使用预取来提高性能和体验。4.vue-cli3默认配置为preload,一个VueCLI应用会自动为初始渲染需要的所有文件生成preloadhints。这些提示会被@vue/preload-webpack-plugin注入,可以通过chainWebpack的config.plugin('preload')修改和删除。prefetch默认情况下,VueCLI应用程序将自动为所有生成为异步块的JavaScript文件生成预取提示(通过动态import()进行按需代码拆分的产物)。这些提示会被@vue/preload-webpack-plugin注入,可以通过chainWebpack的config.plugin('prefetch')修改和删除。五、总结和陷阱1、preload和prefetch的本质是预加载,即先加载再执行,加载和执行解耦。2、preload和prefetch不会阻塞页面的onload。3、preload用于声明当前页面的关键资源,强制浏览器尽快加载;而prefetch用于声明以后可能会用到的资源,在浏览器空闲的时候加载。4.不要滥用preload和prefetch,需要在合适的场景下使用。5、preload的字体资源必须设置crossorigin属性,否则会导致重复加载。原因是如果不指定crossorigin属性(即使同源),浏览器会采用匿名方式使用CORS预加载,导致两次请求无法共享缓存。6、关于preload和prefetch资源的缓存,在google开发者的一篇文章中有解释:如果资源可以被缓存(比如有有效的cache-control和max-age),则存储在HTTP缓存(即磁盘缓存),可供当前或未来的任务使用;如果资源不能缓存在HTTP缓存中,而是放在内存缓存中直到使用。但是,我们在Chrome浏览器(80版)中进行了测试,结果却不太一样。将服务器的缓存策略设置为no-store,观察资源加载情况。可以发现第二次加载ticket_bg.png并不是从本地缓存中获取,而是仍然从服务器中加载。因此,如果要使用prefetch,相应的资源必须要有合理的缓存控制。7、没有有效https证书的站点不能使用prefetch,预取资源不会被缓存(实际使用中发现,原因不明)。8、最后我们来看下preload和prefetch的浏览器兼容性。可见目前两者的兼容性不是很好。幸运的是,不支持preload和prefetch的浏览器会自动忽略它,所以它们可以作为一个渐进增强功能来优化我们页面的资源加载,提升性能和用户体验。作者:沙朝恒