•
阅读时长 2 分钟

preload 和 fetchpriority的区别

目录

图片等组件运行以后才插入,浏览器就要等到那时才知道图片地址。想让它更早加载,可以在 HTML 里声明 preload;fetchpriority 则是对已经发现的资源给出优先级提示。

两者都和加载有关,但影响的步骤不同。用一张晚出现的图片,能把这个区别看得比较清楚。

图片延后出现,服务端延后返回

创建一个空目录,把下面存为 index.html:

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>资源顺序</title>
    <script>
      const mode = new URLSearchParams(location.search).get("mode") || "baseline";
      if (mode === "preload")
        document.write('<link rel="preload" as="image" href="/asset.svg">');
    </script>
  </head>
  <body>
    <h1>资源发现时间实验</h1>
    <p>mode=baseline / preload / priority;人为延迟插入图片,不代表真实站点。</p>
    <div id="image"></div>
    <pre id="result"></pre>
    <script>
      setTimeout(() => {
        const image = new Image();
        image.width = 800;
        image.height = 400;
        if (mode === "priority") image.fetchPriority = "high";
        image.src = "/asset.svg";
        image.onload = () => {
          document.querySelector("#result").textContent = JSON.stringify(
            performance
              .getEntriesByName(new URL("/asset.svg", location.href).href)
              .map((entry) => ({
                start: entry.startTime,
                duration: entry.duration,
                bytes: entry.transferSize,
              })),
            null,
            2,
          );
        };
        document.querySelector("#image").append(image);
      }, 300);
    </script>
  </body>
</html>

创建 server.mjs:

import http from "node:http";
import { readFile } from "node:fs/promises";
http
  .createServer(async (req, res) => {
    const url = new URL(req.url, "http://localhost");
    if (url.pathname === "/asset.svg") {
      setTimeout(() => {
        if (res.destroyed) return;
        res.writeHead(200, {
          "Content-Type": "image/svg+xml",
          "Cache-Control": "no-store",
        });
        res.end(
          '<svg xmlns="http://www.w3.org/2000/svg" width="800" height="400"><rect width="800" height="400" fill="#d9e6e3"/></svg>',
        );
      }, 120);
      return;
    }
    if (url.pathname !== "/") {
      res.writeHead(404);
      return res.end();
    }
    try {
      res.writeHead(200, {
        "Content-Type": "text/html; charset=utf-8",
        "Cache-Control": "no-store",
      });
      res.end(await readFile(new URL("index.html", import.meta.url)));
    } catch {
      res.end("缺少 index.html");
    }
  })
  .listen(3000, "127.0.0.1");

运行 node server.mjs,依次访问 http://127.0.0.1:3000/?mode=baseline、?mode=preload、?mode=priority。

页面等待 300ms 才插入图片,服务端等待 120ms 返回。baseline 没有提示,preload 在 head 提前声明地址,priority 只在插入图片时设置 high。

记录请求什么时候开始

图片加载后,页面显示 Resource Timing 的 start、duration 和 bytes。start 相对于这次页面的时间原点,不是服务器的绝对时间。

在本地 HTTP、Edge 155.0.4283.45 下,每个条件使用三个独立浏览器上下文,服务端设置 no-store,记录过下面一组开始时间:

条件三次 startTime
baseline322.7ms、317.7ms、321.2ms
preload12.4ms、12.3ms、13.0ms
priority321.9ms、314.6ms、316.0ms

这组数据说明 preload 让浏览器更早发现地址。插入时才设置 high,仍然要等到插入以后才能请求。这里没有制造资源竞争,不能拿它评价 fetchpriority 在复杂页面里的调度收益,也没有测量 LCP。

手动重跑时,用新的浏览器上下文,或者关闭缓存并重新加载,每个条件重复几次。不要把三次不同缓存状态的访问直接拿来比较。

提前下载也占用带宽

preload 的 URL、as 和跨源凭据模式要与实际使用匹配,否则可能不能复用。提前加载的资源应该很快被使用,不然它可能和更关键的请求抢带宽。

prefetch 偏向未来可能用到的资源,preconnect 准备连接,dns-prefetch 处理域名解析。它们不是同一个开关。属性说明参考 MDN preload 文档 和 fetchpriority 文档。

首屏图片是否要提前加载,还是应该结合页面实际的发现时机和 LCP 看。并不是所有图片都设置 high,就能让页面更快。