对我们的下一本书感兴趣吗?了解更多关于 使用 React 构建大型 JavaScript Web 应用程序

性能模式

预加载

预加载 (<link rel="preload">) 是 浏览器优化,它允许更早地请求关键资源(这些资源可能在后期被发现)。如果您习惯于考虑如何手动排序关键资源的加载,它可以对加载性能和指标产生积极影响,尤其是 核心 Web 指标。也就是说,预加载不是万能药,需要意识到一些权衡取舍。

HTML5 iconindex.html
1<link rel="preload" href="emoji-picker.js" as="script">
2 ...
3 </head>
4 <body>
5 ...
6 <script src="stickers.js" defer></script>
7 <script src="video-sharing.js" defer></script>
8 <script src="emoji-picker.js" defer></script>

当针对 首次可交互时间首次输入延迟 等指标进行优化时,预加载对于加载交互所需的 JavaScript 包(或块)非常有用。请记住,使用预加载时需要格外小心,因为您希望避免以延迟资源(如英雄图像或字体)为代价来提高交互性,这些资源对于 首屏内容绘制最大内容绘制 至关重要。

如果您尝试优化首方 JavaScript 的加载,也可以考虑在文档 <head> 中使用 <script defer> 而不是 <body> 来帮助尽早发现这些资源。


单页应用程序中的预加载

虽然 **预取** 是缓存可能很快被请求的资源的绝佳方法,但我们可以 **预加载** 需要立即使用的资源。也许是初始渲染时使用的特定字体,或者用户立即看到的特定图像。

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

const EmojiPicker = import(/* webpackPreload: true */ "./EmojiPicker");

ChatInput.js
icon-square-bigwebpack.config.js
1import React, { Suspense, lazy } from "react";
2import Send from "./icons/Send";
3import Emoji from "./icons/Emoji";
4
5const EmojiPicker = lazy(() => import("./EmojiPicker"));
6const ChatInput = () => {
7 const [pickerOpen, togglePicker] = React.useReducer(state => !state, true);
8
9 return (
10 <div className="chat-input-container">
11 <input type="text" placeholder="Type a message..." />
12 <Emoji onClick={togglePicker} />
13 {pickerOpen && (
14 <Suspense fallback={<p id="loading">loading</p>}>
15 <EmojiPicker />
16 </Suspense>
17 )}
18 <Send />
19 </div>
20 );
21};
22
23console.log("ChatInput loading", Date.now());
24
25export default ChatInput;

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)

实际输出在我们的文档 head 中可见,是一个带有 rel="preload"link 标签。

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

预加载的 EmojiPicker 可以与初始包并行加载。与 prefetch 不同,浏览器仍然可以选择是否认为网络连接和带宽足够好以实际预取资源,而 **预加载** 资源无论如何都会被预加载。

我们不再需要等到初始渲染后才加载 EmojiPicker,该资源将立即对我们可用!由于我们以更智能的顺序加载资产,初始加载时间可能会显著增加,具体取决于用户的设备和网络连接。只预加载在初始渲染后大约 1 秒内必须可见的资源。


预加载 + async hack

如果您希望浏览器以高优先级下载脚本,但不阻塞解析器等待脚本,您可以利用下面的预加载 + 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 的开头。
  • 字体预加载最好放在 head 的末尾或 body 的开头
  • 导入预加载应在需要导入的脚本标签之后完成(因此实际脚本首先被加载/解析)
  • 图像预加载将具有较低的优先级,应相对于异步脚本和其他低/最低优先级标签进行排序

结论

再次强调,请谨慎使用预加载,并在生产环境中衡量其影响。如果图像的预加载比它早出现在文档中,这可以帮助浏览器发现它(并相对于其他资源进行排序)。如果使用不当,预加载会导致图像延迟首次内容绘制(例如 CSS、字体) - 这与您想要的效果相反。还要注意,对于这种重新优先级排序工作是否有效,它还取决于 服务器是否正确优先级排序请求

您可能还会发现 <link rel="preload"> 在您需要 不执行 脚本的情况下很有用。

web.dev 的各种文章介绍了如何使用预加载