预取模块

Qwik 提供了多种预先预取模块的策略。此页面介绍了 Qwik 预取的 **低级** 功能。

预取模块允许应用程序在用户实际需要之前,在后台开始下载必要的代码。理想的解决方案是仅预取最少量的相关代码,这些代码很可能在用户交互中执行,同时避免任何 *不会* 使用的 JavaScript。

Qwik 应用程序擅长下载和执行最少的 JavaScript,从而优化资源使用和性能。通过了解各个组件的使用情况,Qwik 可以有效地决定哪些捆绑包应该预取。这种有针对性的方法确保只加载必要的代码。

请记住,可恢复性和水合 之间的关键区别在于,可恢复性使 Qwik 应用程序能够避免执行 JavaScript 只是为了恢复事件监听器、组件树和应用程序状态。通过从根本上将组件的事件监听器、渲染函数和状态分解,与传统方法相比,要预取的代码量要少得多。

收集已使用符号

当 Qwik 渲染应用程序时,它能够收集在渲染过程中使用的“符号”。符号包括组件的各个部分,这些部分由 优化器 提取,以分解应用程序。单个事件监听器、组件状态和组件渲染器本身是可能被提取的不同符号的示例。

例如,考虑一个产品页面,该页面大部分是静态的,只有一个“添加到购物车”按钮。当单击按钮时,用户应该立即收到反馈,以显示已将产品添加到购物车。在此示例中,Qwik 优化器将能够理解用户可以交互的唯一符号是“添加到购物车”按钮的单击事件监听器。

对于我们的“添加到购物车”示例,优化器仅收集单击事件监听器和添加到购物车小部件的渲染器的符号。无需下载、水合和重新渲染与之无关的应用程序的任何其他部分。这证明了 Qwik 能够确定哪些交互是可能的,以及仅预取事件监听器所需的代码。相比之下,传统方法需要预取整个应用程序或路由,包括框架代码,只是为了添加单击事件监听器。

预取策略

预取策略是决定 Qwik 应该在后台预取哪些 JavaScript(如果有)的逻辑。默认情况下,Qwik 将预取页面上所有可见的监听器。要配置预取策略,请使用 renderToStream() 函数的选项参数,该参数通常位于 src/entry.ssr.tsx 源文件中。提供最佳预取策略是 Qwik 的持续承诺。

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      // custom prefetching config
    },
    ...opts,
  });
}

实现

浏览器提供了多种方法来实现 预取策略。可以配置 Qwik 以优先使用一种实现而不是另一种实现,每种实现都有其优缺点。根据此配置,生成的 HTML 内容将包含所选的预取实现。

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      implementation: {
        // custom prefetching implementation
      },
    },
    ...opts,
  });
}
选项描述
prefetchEvent使用 detail 数据(包含应预取的 URL)分派 qprefetch 事件。事件分派脚本将内联到文档的 HTML 中。默认情况下,prefetchEvent 实现将设置为 always
linkInsert<link> 元素插入文档。使用 html-append 时,它将直接在 html 中渲染每个 <link>,并将其附加到正文的末尾。使用 js-append 选项,它将改为插入一些 JavaScript,该 JavaScript 在运行时创建元素并将它们附加到正文的末尾。
linkRel此选项用于定义 <link> 元素的 rel 属性。使用 linkInsert 选项时,默认值为 prefetch。其他选项包括 preloadmodulepreload
workerFetchInsert通过为每个模块调用 fetch() 来预取 URL,目的是填充网络缓存。

分派的预取事件

推测模块预取 是首选的缓存策略。此策略监听 qprefetch 事件,该事件由 Qwik 框架分派。该事件包含一个 URL 列表,后台线程应使用这些 URL 来预填充浏览器的 缓存

应将 Qwik 配置为使用 prefetchEvent 实现,该实现将分派 qprefetch 事件。默认情况下,prefetchEvent 实现将设置为 always。接下来,推测模块预取 将监听此事件,并与其服务工作者通信以持久化请求/响应对象对,以便它们在长期内存中缓存。

通过使用服务工作者拦截来自浏览器的fetch请求,这种方法允许对缓存进行细粒度控制,并防止对同一资源的重复请求。

以下是手动分派事件的示例。这些事件由 Qwik 本身分派,不需要开发人员手动分派。此外,服务工作者 会自动添加这些事件的监听器。

dispatchEvent(new CustomEvent("qprefetch", { detail: {
  bundles: [...]
}}));

使用带有rel属性的<link>元素是当今框架的常见方法,Qwik 可以通过配置linkInsertlinkRel选项来使用这种方法。尽管 link rel 方法有效,但目前在所有设备上都缺乏支持,至少在撰写本文时是这样。此外,在开发过程中,假设它在所有地方都能正常工作可能会产生误导,因为移动设备上的预取并不容易看到。

例如,Safari 不支持modulepreload。这很重要,因为移动设备可能从模块预加载中获益最多。类似地,Firefox 在https上不支持 link rel prefetch

预取是一项旨在提高访问者体验速度的功能。但是,其有效性会因浏览器和 CDN/服务器的组合而异,突出了优化设置以确保最佳性能的重要性。

- Rel=prefetch 和有效 HTTP/2 优先级的意义

此外,可能存在对同一资源的多个请求。例如,假设我们想要预取module-a.js,并且无论它需要多长时间,它都在下载,用户与应用程序交互。然后,应用程序决定实际请求并执行module-a.js。在撰写本文时,浏览器通常会发出第二个请求,从而使情况变得更糟。

  • 即使它在 HTML 规范中,也不意味着您的最终用户正在正确地预加载您的应用程序。 Can I Use: modulepreload
  • 不受 Firefox 支持。

Web Worker Fetch

workerFetchInsert 指示 Qwik 使用 Web Worker 来fetch() JavaScript 文件,目的是用模块填充浏览器缓存。通过使用 Web Worker,fetch 和缓存逻辑存在于另一个线程上。fetch 响应还将具有immutable或长时间的缓存控制标头,因此浏览器不会发出第二个网络请求。

此设置的缺点是,获取的响应会被丢弃,并且只有在浏览器级别,希望文件会被缓存。

预取常见问题

问题用户事件上的延迟加载是否很慢,因为用户必须等待代码下载?

是的,这会造成明显的延迟,尤其是在网络速度慢的情况下。这就是为什么代码预取是 Qwik 应用程序的重要组成部分。

预取代码确保在导航到页面时立即获取运行应用程序所需的所有代码。这样,当用户执行操作时,该操作的代码来自预取缓存,而不是网络。结果是代码执行是即时的。

问题代码预取是否会导致与现有框架相同的行为,这些框架会急切地下载和执行所有代码?

不,原因有几个。

  • 现有框架必须下载并执行所有代码(水合)才能使应用程序具有交互性。通常,代码下载的时间成本小于代码执行的时间成本。
  • Qwik 代码预取仅下载代码,而不执行代码。因此,即使 Qwik 预取与现有框架相同的代码量,结果也会节省大量时间成本。
  • Qwik 仅预取当前页面所需的代码。Qwik 避免下载与静态组件相关的代码。在需要预取更多代码的情况下,Qwik 仍然只达到现有框架认为的最佳情况。在大多数情况下,与现有框架相比,Qwik 预取的代码量要少得多。
  • 代码预取可以在主线程之外的其他线程上进行。许多浏览器甚至可以在主线程之外预先解析代码的 AST。
  • 如果用户交互发生在预取完成之前,浏览器会自动将交互块的优先级置于剩余的预取块之前。
  • Qwik 可以将应用程序分解成许多小块,这些小块可以按用户与它们交互的概率顺序下载。现有框架难以将应用程序分解成小块,并且没有简单的方法来优先排序块下载顺序,因为水合需要应用程序的单个“主”入口点。

问题谁负责知道要预取哪些代码?

Qwik 可以自动生成预取指令作为 SSR 渲染的一部分。通过执行应用程序,Qwik 可以实时了解哪些组件可见,用户可以触发哪些事件以及需要下载哪些代码。结果是,预取是此页面的理想文件集。除了将预取策略添加到renderToStream()之外,开发人员无需执行任何操作。

贡献者

感谢所有帮助改进此文档的贡献者!

  • adamdbradley
  • RATIU5
  • manucorporat
  • literalpie
  • saikatdas0790
  • the-r3aper7
  • mhevery
  • mrhoodz
  • thejackshelton
  • maiieul
  • jemsco