这篇文章主要讲述前端资源预加载方案与可控预加载方案。 资源预加载对于前端来说并不陌生,可以提升性能,preload 与 prefetch 便是常用的预加载实现方案,但是当预加载过度使用时,也会适得其反,这时可控预加载方案便登场了。

# preload

preload 是一个新的 Web 标准,在页面生命周期中提前加载你指定的资源,同时确保在浏览器的主要渲染机制启动之前。这样就可以保证了其不会阻止浏览器的渲染并提前加载资源以此来提高性能。通常使用 preload 是用来加载图片、CSS、JavaScript 和字体文件。preload 的语法和加载 CSS 类似

1
2
<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">

浏览器遇到 rel= ‘preload’ 的标签就会将其推入到预加载器中,这个预加载器也将用于其他我们所需要的,各种各样的,任意类型的资源。为了完成基本的配置,你还需要通过 hrefas 属性指定需要被预加载资源的资源路径及其类型。

preload 支持常见如下资源的加载:

fetch、font、image、script、style

如果资源(css 或 js 等)是通过 preload 预加载的,那么在 preload 下载完资源后,资源只是被缓存起来,浏览器不会对其执行任何操作,不执行脚本,不应用样式表。

这样做的好处是:

  • 将加载和执行分离开,可不阻塞渲染和 document 的 onload 事件
  • 提前加载指定资源,不再出现依赖的 font 字体隔了一段时间才刷出
  • reload 使开发能够自定义资源的加载逻辑,且无需忍受基于脚本的资源加载器带来的性能损失。可以使用 Preload 进行 CSS 资源的预加载、并且同时具备:高优先级、不阻塞渲染等特性。
  • 在首屏渲染(关键 CSS)中用到一些隐藏资源,比如字体,较大的图片资源等。我们可以内联关键 CSS 的前面使用标签提前预加载这些重要资源

常见应用

  1. 字体提前加载,防止闪烁

    web 字体对页面的渲染也是很重要的,字体的引用被深埋在 css 中,即便预加载器有提前解析 css,也无法确定包含字体信息的选择器是否会真正作用在 dom 节点上。所以为了减少 FOUT (无样式字体闪烁,flash of unstyled text) 需要预加载字体文件

    1
    <link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />
  2. 动态加载脚本,但不执行

    通常我们可能想在当前页去加载下一页的资源,但是在 preload 的情况下,我们常常使用动态创建 script 标签的形式,但是动态创建 script 标签的话,js 代码会立即执行。在有了 preload 之后,就可以做到动态加载,延迟执行

    1
    2
    3
    4
    5
    let link = document.createElement("link");
    link.href = "myscript.js";
    link.rel = "preload";
    link.as = "script";
    document.head.appendChild(link);

    上面这段代码可以让你预先加载脚本,下面这段代码可以让脚本执行

    1
    2
    3
    let script = document.createElement("script");
    script.src = "myscript.js";
    document.body.appendChild(script);

# prefetch

prefetch (链接预取)是一种浏览器机制,其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。网页向浏览器提供一组预取提示,并在浏览器完成当前页面的加载后开始静默地拉取指定的文档并将其存储在缓存中。当用户访问其中一个预取文档时,便可以快速的从浏览器缓存中得到。 prefetch 是一个低优先级的资源提示,允许浏览器在后台空闲时获将来可能用得到的资源,并且将他们存储在浏览器的缓存中。一旦一个页面加载完毕就会开始下载其他的资源,然后当用户点击了一个带有 prefetched 的连接,它将可以立刻从缓存中加载内容。

1
2
<link rel="prefetch" href="static/img/delay_load_img.a5bb7c33.png">
<link rel="prefetch" href="myscript.js" as="script">

假设 delay_load_img 是非首屏需要的图片的资源,设置 rel 属性为 prefetch 后,在首屏的请求列表中将会有对 delay_load_img 的加载请求。在后续使用 delay_load_img 的时候,network 中也会有对 delay_load_img 的访问请求。虽然这个请求的 status 也是 200,但有一个特殊的标记 prefetch cache,表明这次请求的资源来自 prefetch 缓存。

# preload 与 prefetch 的区别

  • 大部分场景下无需特意使用 preload 。可以通过 Chrome 的 Devtool Performance 工具,来查看阻塞页面首屏渲染的的 JS 脚本或者 CSS 样式文件,并将其设置为 preload
  • 类似字体文件这种隐藏在脚本、样式中的首屏关键资源,建议使用 preload
  • 异步加载的模块(典型的如单页系统中的非首页)建议使用 prefetch
  • 大概率即将被访问到的资源可以使用 prefetch 提升性能和体验。

preload 是为了尽早加载首屏需要的关键资源,强制浏览器尽快加载

prefetch 是为了尽早加载加来可能访问的资源,在浏览器空闲时进行加载

另外,preload 与 prefetch 都不会阻塞页面的 onload

# 浏览器资源加载优先级规则

浏览器首先会按照资源默认的优先级确定加载顺序:

  • html, css, font 这三种类型的资源优先级最高;
  • 然后是 preload 资源(通过 <link rel=“preload"> 标签预加载)
  • 接着是图片、语音、视频;
  • 最低的是 prefetch 预读取的资源。

# 可控预加载

预加载如果使用得当,能提升页面加载和渲染的速度,但如果过度使用 <link rel="preload"> 预加载不需要的资源,会占用其他更重要资源的带宽,因此我们需要做可控预加载,可控预加载的目标是:

  • 保证核⼼资源提前加载
  • 在不影响核⼼资源加载性能的情况下,帮助其他即将要⽤到的资源进⾏预加载

实现方式

  1. 关闭 vue-cli 默认的预加载配置

    vue-cli 默认开启了静态资源(bundle)的预加载。对于⼤型 spa 应⽤来说,在做了按路由和模块分包的情况下,会产出许多独⽴的 bundle ⽂件。如果对这些 bundle 开启默认的预加载后,会出现⾮核⼼ bundle 抢占核⼼ bundle 的⽹络,影响页⾯性能

    构建配置 vue.config.js 如下:

    1
    2
    3
    4
    5
    6
    7
    module.exports = {
    chainWebpack: (config) => {
    // 其他构建配置
    config.plugins.delete('prefetch') // 关闭默认 prefetch
    config.plugins.delete('preload') // 关闭默认 preload
    },
    }
  2. 使用可控预加载

    可控预加载,⽬标是通过⼀个配置⽂件,来指定访问哪些页⾯时,预加载哪些 bundle 资源,需要这几部

    • 需要精确进⾏预加载的 bundle 命名

      通过 webpack 注释的⽅式,显⽰指定 bundle 名称,如下图:

    • 不同页⾯的 bundle 预加载映射规则

    • 构建时收集所有 bundle 信息并往 html 中注⼊预加载代码

未完待续~