preload
preload 用来声明当前页面的关键资源,强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源。浏览器一定会加载这些资源。
<link rel="preload" as="font" href="<%= require('/assets/fonts/AvenirNextLTPro-Demi.otf') %>" crossorigin>
preload 的字体资源必须设置 crossorigin 属性
preload 的字体资源必须设置 crossorigin 属性,否则可能会导致重复加载。
原因是在浏览器中不同资源加载具有不同的优先级规则。
下面是在 Blink 内核的 Chrome 46 及更高版本中不同资源的加载优先级情况:
可以看到:
- css 样式文件中
@font-face依赖的字体文件加载的优先级是 Highest; - 使用
preload预加载字体文件时,采用匿名模式的 CORS 去加载,优先级是 High
如果不指定 crossorigin 属性(即使同源),浏览器会采用匿名模式的 CORS 去 preload。
为了让后续请求能命中缓存,需要给 preload 的链接加上 crossorigin 属性,这样预加载请求头会带上 origin, 且与后续的请求具有相同的优先级,从而做到命中缓存。
crossorigin 跨域属性
crossorigin 跨域属性允许配置元素(如 <link>、<script>、<img>、<video> 和 <audio>)获取数据的 CORS 请求。
具有以下可能的值:
anonymous:对元素的 CORS 请求不设置凭据标志。use-credentials:对元素的 CORS 请求设置凭证标志,意味着请求将提供凭据。"":如crossorigin或crossorigin="",效果和设置anonymous一样。- 无效的关键字和空字符串也会被当作
anonymous关键字使用。
未指定 crossorigin 属性(即默认情况)时,CORS 不会使用。
在非同源情况下,设置 anonymous 将不会通过 cookies、客户端 SSL 证书或 HTTP 认证交换用户凭据。
prefetch
prefetch 用来声明将来可能用到的资源,在浏览器空闲时进行加载,什么时间加载这个资源是由浏览器来决定的,浏览器不一定会加载这些资源。
<link rel="prefetch" href="img.png">
没有合法 https 证书的站点无法使用
prefetch,预提取的资源不会被缓存(实际使用过程中发现,原因未知)
preload 和 prefetch 的下载行为
正确使用 preload/prefetch 不会造成二次下载,也就说:当页面上使用到这个资源时候 preload 资源还没下载完,这时候不会造成二次下载,会等待第一次下载并执行脚本。
- 对于
preload来说,一旦页面关闭了,它就会立即停止preload获取资源; - 而对于
prefetch资源,即使页面关闭,prefetch发起的请求仍会进行不会中断。
preload 和 prefetch 的缓存行为
当资源被 preload 或者 prefetch 后,会从网络堆栈传输到 HTTP 缓存并进入渲染进程的内存缓存。
-
如果资源可以被缓存(例如,存在有效的
cache-control和max-age),它将存储在 HTTP 缓存中,可用于当前和未来的会话。 -
如果资源不可缓存,则不会将其存储在 HTTP 缓存中。它会被缓存到内存缓存中并保持不变直到它被使用。
使用场景
目前浏览器基本上都具备预测解析能力,可以提前解析入口 html 中外链的资源,因此入口脚本文件、样式文件等不需要特意进行 preload。
但是一些隐藏在 CSS 和 JavaScript 中的资源,如字体文件,本身是首屏关键资源,但当 css 文件解析之后才会被浏览器加载。这种场景适合使用 preload 进行声明,尽早进行资源加载,避免页面渲染延迟。
与 preload 不同,prefetch 声明的是将来可能访问的资源,因此适合对异步加载的模块、可能跳转到的其他路由页面进行资源缓存;对于一些将来大概率会访问的资源也较为适用。
总结
- 大部分场景下无需特意使用
preload或prefetch - 类似字体文件这种隐藏在脚本、样式中的首屏关键资源,建议使用
preload - 异步加载的模块(典型的如单页应用中的非首页)建议使用
prefetch - 大概率即将被访问到的资源可以使用
prefetch提升性能和体验
webpack 中的具体实践
使用 webpack 插件 preload-webpack-plugin,结合 html-webpack-plugin 在构建过程中自动化插入 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/] // 资源白名单
})
]
include属性默认取值asyncChunks,表示仅预加载异步js模块;- 如果需要预加载图片、字体等资源,则需要将其设置为
allAssets,表示处理所有类型的资源。 - 通过
fileBlacklist或fileWhitelist进行控制预加载范围 - 对于异步加载的模块,还可以通过 webpack 内置的
/* webpackPreload: true */标记进行更细粒度的控制。
// webpack 会生成标签添加到 html 页面头部。
import(/* webpackPreload: true */ 'AsyncModule');
prefetch的配置与preload类似,但无需对as属性进行设置。
vue-cli@3 的默认配置
- 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') 进行修改和删除。