预取(Prefetching)

预取是应用程序在用户实际需要这些模块之前开始在后台任务中下载模块的一种方式。 理想的解决方案是仅预取极有可能从用户交互中执行的最少量代码,而避免预取任何不会使用到的 JavaScript。

只下载和执行最少量的 JavaScript 是 Qwik应用擅长的领域。 由于 Qwik 能够理解单个组件的使用方式(以及未使用的组件),它还可以最好地决定应该预取哪些bundles。

请记住,resumability 和 hydration 之间的区别 是, resumability 允许 Qwik 应用程序避免执行 JavaScript 来恢复事件侦听器、组件树和应用程序状态。 通过从根本上拆分组件的事件侦听器、渲染函数和状态,预取的代码量也比传统方法明显减少。

Collecting Used Symbols

当 Qwik 渲染应用程序时,它能够收集在渲染过程中使用了哪些“symbols”。 symbols包括组件的各个部分,优化器提取 出这些部分以分解应用程序。 通常会被组件提取出 单独的事件侦听器、组件状态和组件渲染器本身 几个部分。

例如,一个产品页面可能在“添加到购物车”按钮上有一个点击监听器,当点击这个按钮时,用户应该立即得到反馈以显示该产品已添加到购物车中。 在此示例中,Qwik optimizer将能够理解用户需要交互的唯一symbol是“添加到购物车”按钮的click事件侦听器。

对于我们的“添加到购物车”示例,optimizer将仅收集click事件侦听器的symbol,以及添加到购物车这部分UI的渲染器。 但是,它不必下载、hydrate和重新渲染应用程序的任何其他部分,因为甚至不可能重新渲染页面的其他部分。

由于 Qwik 了解什么是可能的,它能够只为事件侦听器预取代码,而不是为整个应用程序或路由预取所有 JavaScript。 这与传统方法相反,在传统方法中,只是为了让应用程序添加点击事件侦听器,就要预取用于加载整个应用程序或路由以及框架的代码。

预取策略

预取策略是决定 Qwik 应该在后台预取哪些 JavaScript(如果有)的逻辑。 默认情况下,Qwik 会预取页面上所有可见的监听器。要配置预取策略,请使用 renderToStream() 函数的 options 参数, 通常在 src/entry.ssr.tsx 源文件中找到。提供最佳预取策略是 Qwik 将继续研究和试验的领域。

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

预取实现

浏览器提供了多种“实现”或应用[预取策略](#prefetching-strategy)的方法, Qwik 可以配置为某种实现方式,每种方式各有利弊。 根据配置,生成的 HTML 内容将包括预取实现。

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      implementation: {
        // custom prefetching implementation
      },
    },
    ...opts,
  });
}
选项描述
prefetchEventDispatch a qprefetch event with detailed data containing the urls that should be prefetched. The event dispatch script will be inlined into the document's HTML.
linkInsertInsert the <link> element into the document. When using html-append, it will render each <link> directly within the html, appended at the end of the body. Using the js-append option, it will instead insert some JavaScript, which creates the elements at runtime and appends them at the end of the body.
linkRelThis option is used to define the rel attribute of the <link> element. When the linkInsert option is used, the default is prefetch. Other options include preload and modulepreload.
workerFetchInsertPrefetch urls by calling a fetch() for each module, with the goal that it populate's the network cache.

Request/Response Cache and Service Workers

The preferred caching strategy used by Qwik City, is to use a Service Worker to populate the browser's Cache. Qwik itself should be configured to use the prefetchEvent implementation, which will dispatch a qprefetch event. Next, Qwik City will listen for this event and communicate with its service worker to persist the Request / Response object pairs so they are cached in long-lived memory.

By using a service worker to intercept fetch requests from the browser, this approach allows fine-grain caching control, along with preventing multiple-requests for the resource.

Using the <link> element with the rel attribute is a common approach by today's frameworks, and Qwik can use this method by configuring the linkInsert and linkRel options. The challenge with the link rel approach is lack of support on all devices, at least at the time of writing. Additionally, during development, it can be misleading that it works everywhere; while on mobile devices it is not easily visible that link prefetching is working correctly.

For example, Safari (the browser powering iPhones and iPads) does not support modulepreload. This is significant because mobile devices maybe benefit the most from module preloading. Next is Firefox, which does not support link rel prefetch when on https.

Prefetch is a feature that's supposed to help make our visitor's experiences faster but with the wrong combination of browser and CDN / server it can actually make experiences slower!

- Rel=prefetch and the Importance of Effective HTTP/2 Prioritisation

Additionally, it may be possible to fire off a multiple requests for the same resource. For example, let's say we want to prefetch module-a.js, and while that's being downloaded (which may take a short time, or a very long time), the user interacts with the app, which then decides to actually request and execute module-a.js. At the time of writing, browsers will often fire off a second request, which makes matters worse.

Web Worker Fetch

A prefetching approach that can work is using a web worker to fetch() a JavaScript file, with the goal of priming the browser cache with the module. By using a web worker, the fetch and logic lives on another thread. Next, the fetch response should have an immutable or long cache-control header, so the browser doesn't make a second network request.

The downside is the fetched response is thrown away, and it's only at the browser level that hopefully caches the file.

Frequently Asked Prefetching Questions

QUESTION: Is lazy loading on user events slow because the user must wait for the code to download?

Yes, that would create a noticeable delay, especially on slow 3G networks. This is why code prefetching is an important part of Qwik applications.

Prefetching code ensures that all of the necessary code for running the application is fetched immediately on navigating to the page. This way, when the user performs an action, the code for that action comes from the prefetch cache rather than the network. The result is that the code execution is instant.

QUESTION: Doesn't code prefetch results in the same behavior as existing frameworks that download and execute all of the code eagerly?

No, for several reasons:

  • Existing frameworks must download and execute all of the code (hydration) before the application can be interactive. Typically the download of the code is a smaller time cost than the execution of the code.
  • Qwik code prefetch only downloads but does not execute the code. Therefore even if Qwik prefetches the same amount of code as the existing frameworks, the result is significant time cost savings.
  • Qwik only prefetches the code which is needed for the current page. Qwik avoids downloading code associated with components that are static. In the worst case, Qwik prefetches the same amount of code as the existing frameworks' best case. In most cases, Qwik prefetches a small fraction of code compared to the existing frameworks.
  • Prefetching of code can happen on other threads than the main thread. Many browsers can even pre-parse the AST of the code off the main thread.
  • If the user interaction happens before the prefetch is completed, the browser will automatically prioritize the interaction chunk before the remaining prefetch chunks.
  • Qwik can break up the application into many small chunks, and these chunks can be downloaded in the order of probability that the user will interact with them. Existing frameworks have trouble breaking up applications into small chunks, and there is no easy way to prioritize the chunk download order because hydration requires a single "main" entry point to the application.

QUESTION: Who is responsible for knowing what code to prefetch?

Qwik can automatically generate the prefetch instructions as part of the SSR rendering. By executing the application, Qwik has runtime knowledge of which components are visible, which events the users can trigger and what code will need to be downloaded. The result is that the prefetch is an ideal set of files for this page. No action on the developers' part is required other than configuring the renderToStream() with prefetching strategy.

Made with ❤️ by