共计 5630 个字符,预计需要花费 15 分钟才能阅读完成。
对于前端页面来说,动态资源的加载对页面性能起着至关重要的作用。本文将介绍浏览器提供的两个资源指令 -preload/prefetch,它们可能辅助浏览器优化资源加载的程序和机会,晋升页面性能。
一、从一个实例开始
如上图所示,咱们开发了一个简略的收银台,领取过程中能够开展优惠券列表抉择相应的券。从动图能够看到,列表第一次开展时,优惠券背景有一个逐步显示的过程,体验上不是很好。
问题的起因也很显著,因为背景应用了视觉特意设计的图片,优惠券列表开展时须要去加载图片,背景渐显的过程实际上就是图片加载的过程;当网速慢的时候,这个问题会更加显著。那么,怎么解决这个问题呢?
仔细分析一下,咱们会发现问题的起因在于背景图的加载机会太晚。
如果能在优惠券列表渲染前加载好背景图,这个问题就不会呈现。从这个思路登程,咱们可能想到以下两个计划:
- 应用内联图片,也就是将图片转换为 base64 编码的 data-url。这种形式,其实是将图片的信息集成到 css 文件中,防止了图片资源的独自加载。但图片内联会减少 css 文件的大小,减少首屏渲染的工夫。
- 应用 js 代码对图片进行预加载
preloadImage() {
const imgList = [require('@/assets/imgs/error.png'),
require('@/assets/imgs/ticket_bg.png')
];
for (let i = 0; i < imgList.length; i++) {const newIMG = new Image();
newIMG.src = imgList[i];
}
}
这种计划次要是利用浏览器的缓存机制,由 js 代码在特定机会提前加载相应图片,优惠券列表渲染时就能够间接从缓存获取。不过,这种计划减少了额定的代码,须要本人管制好加载机会,并且将图片的 url 硬编码在了逻辑中。
能够看出,以上两种计划可能解决咱们的问题,但都存在一些毛病。
那么,有没有更好的解决方案呢?答案是 prefetch- 一种由浏览器原生提供的预加载计划。
二、什么是 prefetch?
prefetch(链接预取)是一种浏览器机制,其利用浏览器闲暇工夫来下载或预取用户在不久的未来可能拜访的文档。网页向浏览器提供一组预取提醒,并在浏览器实现以后页面的加载后开始静默地拉取指定的文档并将其存储在缓存中。当用户拜访其中一个预取文档时,便能够疾速的从浏览器缓存中失去。–MDN
具体来说,浏览器通过 <link rel=”prefetch” href=”/library.js”> 标签来实现预加载。
其中 rel=”prefetch” 被称为 Resource-Hints(资源提醒),也就是辅助浏览器进行资源优化的指令。
相似的指令还有 rel=”preload”,咱们会在后文提及。
<head>
...
<link rel="prefetch" href="static/img/ticket_bg.a5bb7c33.png">
...
</head>
查看当初优惠券列表的加载成果。
果然,胜利达成了咱们冀望的成果。那么浏览器是如何做的呢?咱们关上 Chrome 的 Network 面板一探到底:
能够看到,在首屏的申请列表中曾经呈现了优惠券背景图 ticket\_bg.png 的加载申请,申请自身看起来和一般申请没什么不同;开展优惠券列表后,network 中减少了一次新的 ticket\_bg.png 拜访申请,咱们很快发现,这个申请的 status 尽管也是 200,但有一个非凡的标记—prefetch cache,表明这次申请的资源来自 prefetch 缓存。这个体现验证了上文中 prefetch 的定义,即浏览器在闲暇工夫事后加载资源,真正应用时间接从浏览器缓存中疾速获取。
三、Preload
从下面的案例,咱们领会到了浏览器预加载资源的弱小能力。实际上,预加载是一个狭义的概念,prefetch 只是具体实现形式之一,本节咱们介绍下另外一种预加载形式 preload。上文咱们提到,preload 与 prefetch 同属于浏览器的 Resource-Hints,用于辅助浏览器进行资源优化。为了对两者进行辨别,prefetch 通常翻译为预提取,preload 则翻译为预加载。
元素的 rel 属性的属性值 preload 可能让你在你的 HTML 页面中元素外部书写一些申明式的资源获取申请,能够指明哪些资源是在页面加载实现后即刻须要的。对于这种即刻须要的资源,你可能心愿在页面加载的生命周期的晚期阶段就开始获取,在浏览器的主渲染机制染指前就进行预加载。这一机制使得资源能够更早的失去加载并可用,且更不易阻塞页面的初步渲染,进而晋升性能。
简略来说,就是通过 <link rel=”preload” href=”xxx” as=”xx”> 标签显式申明一个高优先级资源,强制浏览器提前申请资源,同时不阻塞文档失常 onload。咱们同样用一个理论案例进行具体介绍。
上图是咱们开发的另外一个收银台,出于本地化的思考,设计上应用了自定义字体。开发实现后咱们发现,页面首次加载时文字会呈现短暂的字体款式闪动(FOUT,Flash of Unstyled Text),在网络状况较差时比拟显著(如动图所示)。究其原因,是字体文件由 css 引入,在 css 解析后才会进行加载,加载实现之前浏览器只能应用降级字体。也就是说,字体文件加载的机会太迟,须要通知浏览器提前进行加载,这恰好是 preload 的用武之地。
咱们在入口 html 文件 head 退出 preload 标签:
<head>
...
<link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Demi.otf') %>" crossorigin>
<link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Regular.otf') %>" crossorigin>
...
</head>
再次查看页面首次加载的成果:
字体款式闪动的景象没有了!咱们比照下应用 preload 前后的 network 面板。
应用前:
应用后:
能够发现字体文件的加载机会显著提前了,在浏览器接管到 html 后很快就进行了加载。
留神:preload link 必须设置 as 属性来申明资源的类型(font/image/style/script 等),否则浏览器可能无奈正确加载资源。
四、Preload 和 Prefetch 的具体实际
1、preload-webpack-plugin
前文中咱们举的两个例子,都是在入口 html 手动增加相干代码:
<head>
...
<link rel="prefetch" href="static/img/ticket_bg.a5bb7c33.png">
...
</head>
<head>
...
<link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Demi.otf') %>" crossorigin>
<link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Regular.otf') %>" crossorigin>
...
</head>
这显然不够不便,而且将资源门路硬编码在了页面中(实际上,ticket_bg.a5bb7c33.png 后缀中的 hash 是构建过程主动生成的,所以硬编码的形式很多场景下自身就行不通)。webpack 插件 preload-webpack-plugin 能够帮忙咱们将该过程自动化,联合 htmlWebpackPlugin 在构建过程中插入 link 标签。
const PreloadWebpackPlugin = require('preload-webpack-plugin');
...
plugins: [
new PreloadWebpackPlugin({
rel: 'preload',as(entry) { // 资源类型
if (/\.css$/.test(entry)) return 'style';
if (/\.woff$/.test(entry)) return 'font';
if (/\.png$/.test(entry)) return 'image';
return 'script';
},
include: 'asyncChunks', // preload 模块范畴,还可取值 'initial'|'allChunks'|'allAssets',
fileBlacklist: [/\.svg/] // 资源黑名单
fileWhitelist: [/\.script/] // 资源白名单
})
]
PreloadWebpackPlugin 配置总体上比较简单,须要留神的是 include 属性。该属性默认取值 ’asyncChunks’,示意仅预加载异步 js 模块;如果须要预加载图片、字体等资源,则须要将其设置为 ’allAssets’,示意解决所有类型的资源。
但个别状况下咱们不心愿把预加载范畴扩得太大,所以须要通过 fileBlacklist 或 fileWhitelist 进行管制。
对于异步加载的模块,还能够通过 webpack 内置的 /_ webpackPreload: true _/ 标记进行更细粒度的管制。
以上面的代码为例,webpack 会生成 <link rel=”preload” href=”chunk-xxx.js” as=”script”> 标签增加到 html 页面头部。
import(/* webpackPreload: true */ 'AsyncModule');
备注:prefetch 的配置与 preload 相似,但无需对 as 属性进行设置。
2、应用场景
从前文的介绍可知,preload 的设计初衷是为了尽早加载首屏须要的要害资源,从而晋升页面渲染性能。
目前浏览器基本上都具备预测解析能力,能够提前解析入口 html 中外链的资源,因而入口脚本文件、款式文件等不须要特意进行 preload。
然而一些暗藏在 CSS 和 JavaScript 中的资源,如字体文件,自身是首屏要害资源,但当 css 文件解析之后才会被浏览器加载。这种场景适宜应用 preload 进行申明,尽早进行资源加载,防止页面渲染提早。
与 preload 不同,prefetch 申明的是未来可能拜访的资源,因而适宜对异步加载的模块、可能跳转到的其余路由页面进行资源缓存;对于一些未来大概率会拜访的资源,如上文案例中优惠券列表的背景图、常见的加载失败 icon 等,也较为实用。
3、最佳实际
基于上面对应用场景的分享,咱们能够总结出一个比拟通用的最佳实际:
- 大部分场景下无需特意应用 preload
- 相似字体文件这种暗藏在脚本、款式中的首屏要害资源,倡议应用 preload
- 异步加载的模块(典型的如单页零碎中的非首页)倡议应用 prefetch
- 大概率行将被拜访到的资源能够应用 prefetch 晋升性能和体验
4、vue-cli3 的默认配置
- preload
默认状况下,一个 Vue CLI 利用会为所有初始化渲染须要的文件主动生成 preload 提醒。这些提醒会被 @vue/preload-webpack-plugin 注入,并且能够通过 chainWebpack 的 config.plugin(‘preload’)进行批改和删除。
- prefetch
默认状况下,一个 Vue CLI 利用会为所有作为 async chunk 生成的 JavaScript 文件 (通过动静 import() 按需 code splitting 的产物)主动生成 prefetch 提醒。这些提醒会被 @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 去 preload,导致两次申请无奈共用缓存。
6、对于 preload 和 prefetch 资源的缓存,在 Google 开发者的一篇文章中是这样阐明的:如果资源能够被缓存(比如说存在无效的 cache-control 和 max-age),它被存储在 HTTP 缓存(也就是 disk cache)中,能够被当初或未来的工作应用;如果资源不能被缓存在 HTTP 缓存中,作为代替,它被放在内存缓存中直到被应用。
然而咱们在 Chrome 浏览器(版本号 80)中进行测试,后果却并非如此。将服务器的缓存策略设置为 no-store,察看下资源加载状况。
能够发现 ticket_bg.png 第二次加载并未从本地缓存获取,依然是从服务器加载。因而,如果要应用 prefetch,相应的资源必须做好正当的缓存管制。
7、没有非法 https 证书的站点无奈应用 prefetch,预提取的资源不会被缓存(理论应用过程中发现,起因未知)。
8、最初咱们来看下 preload 和 prefetch 的浏览器兼容性。
能够看到,两者的兼容性目前都还不是太好。好在不反对 preload 和 prefetch 的浏览器会主动疏忽它,因而能够将它们作为一种渐进加强性能,优化咱们页面的资源加载,晋升性能和用户体验。
作者:Sha Chaoheng