预加载

预加载 (<link rel="preload">) 是一种浏览器优化,允许更早地请求关键资源(可能发现较晚)。
如果您对如何手动排序关键资源的加载感到满意,它会对 Core Web Vitals 中的加载性能和指标产生积极影响。
也就是说,预加载不是灵丹妙药,需要了解一些权衡。

在优化交互时间或首次输入延迟等指标时,预加载可用于加载交互所需的 JavaScript 包(或块)。
请记住,在使用预加载时需要非常小心,避免以延迟首次内容绘制或最大内容绘制所需的资源(如英雄图像或字体)为代价来提高交互性。

如果尝试优化第一块 JavaScript 的加载,还可以考虑在文档 <head> vs. <body> 中使用 <script defer> 来帮助及早发现这些资源。



在单页应用程序中预加载

虽然预取是缓存可能很快会被请求的资源的好方法,但可以预加载需要立即使用的资源。
也许它是在初始渲染中使用的某种字体,或者用户立即看到的某些图像。

假设我们的 EmojiPicker 组件应该在初始渲染时立即可见。
虽然它不应该包含在主包中,但它应该并行加载。
就像 prefetch 一样,可以添加一个神奇的注释,让 Webpack 知道应该预加载这个模块。

1
2

const EmojiPicker = import(/* webpackPreload: true */ "./EmojiPicker");
const path = require("path");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const PreloadWebpackPlugin = require("preload-webpack-plugin");

module.exports = {
  entry: {
    main: "./src/index.js",
    emojiPicker: "./src/components/EmojiPicker.js"
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"]
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  resolve: {
    extensions: ["*", ".js", ".jsx"]
  },
  mode: "development",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].bundle.js"
  },
  plugins: [
    new HTMLWebpackPlugin({
      template: path.resolve(__dirname, "dist", "index.html")
    }),
    new PreloadWebpackPlugin({
      rel: "preload",
      as: "script",
      include: ["emojiPicker"]
    })
  ],
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9000
  }
};

Webpack 4.6.0+ 允许通过在导入中添加 /* webpackPreload: true */ 来预加载资源。
为了在旧版本的 webpack 中进行预加载,需要将 preload-webpack-plugin 添加到您的 webpack 配置中。

构建应用程序后,可以看到 EmojiPicker 将被预取。

Asset                             Size       Chunks                          Chunk Names
    emoji-picker.bundle.js         1.49 KiB   emoji-picker [emitted]          emoji-picker
    vendors~emoji-picker.bundle.js 171 KiB    vendors~emoji-picker [emitted]  vendors~emoji-picker
    main.bundle.js                 1.34 MiB   main  [emitted]                 main

Entrypoint main = main.bundle.js
(preload: vendors~emoji-picker.bundle.js emoji-picker.bundle.js)

实际输出作为链接标签可见,在文档头部带有 rel="preload"

<link rel="prefetch" href="emoji-picker.bundle.js" as="script" />
<link rel="prefetch" href="vendors~emoji-picker.bundle.js" as="script" />

预加载的 EmojiPicker 可以与初始包并行加载。
与预取不同的是,浏览器在是否认为它有足够好的互联网连接和带宽来实际预取资源方面仍然有发言权,预加载的资源无论如何都会被预加载。

无需等待 EmojiPicker 在初始渲染后加载,资源将立即可供我们使用!
由于我们正在以更智能的顺序加载资产,初始加载时间可能会显着增加,具体取决于您的用户设备和互联网连接。
仅在初始渲染后约 1 秒预加载必须可见的资源。


预加载 + 异步 hack

如果希望浏览器以高优先级下载脚本,但不阻止解析器等待脚本,可以利用下面的 preload + async hack
在这种情况下,预加载可能会延迟其他资源的下载,但这是开发人员必须做出的权衡:

<link rel="preload" href="emoji-picker.js" as="script">
<script src="emoji-picker.js" async>

在 Chrome 95+ 中预加载

由于对 Chrome 95+ 中预加载队列跳转行为的一些修复,该功能在更广泛地使用时稍微安全一些。
Chrome 对预加载的新建议的 Pat Meenan 建议:

  • 将它放在 HTTP 标头中将领先于其他所有内容
    通常,预加载将按照解析器对 >= 中的任何内容的顺序加载,因此请小心将预加载放在 HTML 的开头。

  • 字体预加载可能最好在头部的末尾或正文的开头

  • 导入预加载应该在需要导入的脚本标签之后完成(因此首先加载/解析实际脚本)

  • 图像预加载将具有低优先级,应相对于异步脚本和其他低/最低优先级标签进行排序


结论

同样,请谨慎使用预加载并始终衡量其对生产的影响。

如果图像的预加载在文档中比它更早,这可以帮助浏览器发现它(以及相对于其他资源的顺序)。

如果使用不当,预加载会导致图像延迟首次内容绘制(例如 CSS、字体)—— 与您想要的相反。

另请注意,要使此类重新确定优先级工作有效,还取决于服务器正确确定请求的优先级。

您可能还会发现 <link rel="preload"> 在需要获取脚本而不执行它们的情况下很有帮助。


知识点

  • webpack

  • <link rel="preload">