import-on-interaction

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Import On Interaction

交互时导入

tl;dr: lazy-load non-critical resources when a user interacts with UI requiring it
Your page may contain code or data for a component or resource that isn't immediately necessary. For example, part of the user-interface a user doesn't see unless they click or scroll on parts of the page. This can apply to many kinds of first-party code you author, but this also applies to third-party widgets such as video players or chat widgets where you typically need to click a button to display the main interface.
简而言之:当用户与需要非关键资源的UI交互时,懒加载这些资源
你的页面可能包含某个组件或资源的代码或数据,这些内容并非立即必需。例如,用户只有点击或滚动页面某些部分才会看到的UI组件。这适用于你编写的许多第一方代码,也适用于第三方小部件,比如视频播放器或聊天小部件,通常你需要点击按钮才能显示它们的主界面。

When to Use

适用场景

  • Use this when you have third-party widgets (video players, chat widgets) that are costly to load eagerly
  • This is helpful for deferring non-critical code until the user actually needs it
  • Use this to improve First Input Delay (FID) and Time to Interactive (TTI)
  • 当你有第三方小部件(视频播放器、聊天小部件),且立即加载成本很高时使用此方法
  • 这有助于将非关键代码延迟到用户实际需要时再加载
  • 使用此方法可改善首次输入延迟(FID)和交互就绪时间(TTI)

Instructions

实施步骤

  • Use dynamic
    import()
    to load modules on user interaction (click, hover, etc.)
  • Implement facades (lightweight placeholders) for heavy third-party embeds
  • For first-party code, prefer prefetching over import-on-interaction when possible
  • Consider preconnecting to required origins on hover to reduce latency
  • Use
    React.lazy
    with
    Suspense
    for component-level import-on-interaction in React
  • 使用动态
    import()
    在用户交互(点击、悬停等)时加载模块
  • 为重量级第三方嵌入实现外观(轻量级占位符)
  • 对于第一方代码,尽可能优先使用预获取(prefetch)而非交互时导入
  • 考虑在悬停时预连接到所需源以减少延迟
  • 在React中,结合使用
    React.lazy
    Suspense
    实现组件级别的交互时导入

Details

详细说明

Loading these resources eagerly (i.e right away) can block the main thread if they are costly, pushing out how soon a user can interact with more critical parts of a page. This can impact interaction readiness metrics like First Input Delay, Total Blocking Time and Time to Interactive. Instead of loading these resources immediately, you can load them at a more opportune moment, such as:
  • When the user clicks to interact with that component for the first time
  • Scrolls the component into view
  • or deferring load of that component until the browser is idle (via requestIdleCallback).
The different ways to load resources are, at a high-level:
  • Eager - load resource right away (the normal way of loading scripts)
  • Lazy (Route-based) - load when a user navigates to a route or component
  • Lazy (On interaction) - load when the user clicks UI (e.g Show Chat)
  • Lazy (In viewport) - load when the user scrolls towards the component
  • Prefetch - load prior to needed, but after critical resources are loaded
  • Preload - eagerly, with a greater level of urgency
Import-on-interaction for first-party code should only be done if you're unable to prefetch resources prior to interaction. The pattern is however very relevant for third-party code, where you generally want to defer it if non-critical to a later point in time. This can be achieved in many ways (defer until interaction, until the browser is idle or using other heuristics).
Lazily importing feature code on interaction is a pattern used in many contexts. One place you may have used it before is Google Docs, where they save loading 500KB of script for the share feature by deferring its load until user-interaction.
Another place where import-on-interaction can be a good fit is loading third-party widgets.
如果立即加载这些资源(即马上加载),若资源成本较高,可能会阻塞主线程,从而推迟用户与页面更关键部分交互的时间。这会影响交互就绪指标,如首次输入延迟(FID)总阻塞时间(TBT)交互就绪时间(TTI)。与其立即加载这些资源,不如在更合适的时机加载,例如:
  • 用户首次点击与该组件交互时
  • 组件滚动到视图中时
  • 或者将组件加载延迟到浏览器空闲时(通过requestIdleCallback
从高层次来看,资源加载的不同方式包括:
  • 立即加载(Eager):马上加载资源(脚本的常规加载方式)
  • 路由级懒加载(Lazy (Route-based)):用户导航到某路由或组件时加载
  • 交互时懒加载(Lazy (On interaction)):用户点击UI(如“显示聊天”)时加载
  • 视口内懒加载(Lazy (In viewport)):用户滚动到组件附近时加载
  • 预获取(Prefetch):在需要之前加载,但在关键资源加载完成后
  • 预加载(Preload):立即加载,且优先级更高
只有当你无法在交互前预获取资源时,才应该对第一方代码使用交互时导入模式。不过,该模式对于第三方代码非常适用,通常你希望将非关键的第三方代码延迟到稍后的时间点加载。这可以通过多种方式实现(延迟到交互时、浏览器空闲时或使用其他启发式方法)。
在交互时懒加载功能代码是许多场景中使用的模式。你可能之前在Google Docs中见过这种模式:他们通过延迟分享功能的加载,直到用户交互时才加载,从而节省了500KB脚本的加载成本。
交互时导入的另一个适用场景是加载第三方小部件。

"Fake" loading third-party UI with a facade

用外观模拟第三方UI加载

You might be importing a third-party script and have less control over what it renders or when it loads code. One option for implementing load-on-interaction is straight-forward: use a facade. A facade is a simple "preview" or "placeholder" for a more costly component where you simulate the basic experience, such as with an image or screenshot. It's terminology we've been using for this idea on the Lighthouse team.
When a user clicks on the "preview" (the facade), the code for the resource is loaded. This limits users needing to pay the experience cost for a feature if they're not going to use it. Similarly, facades can preconnect to necessary resources on hover.
Third-party resources are often added to pages without full consideration for how they fit into the overall loading of a site. Synchronously-loaded third-party scripts block the browser parser and can delay hydration. If possible, 3P script should be loaded with async/defer (or other approaches) to ensure 1P scripts aren't starved of network bandwidth. Unless they are critical, they can be a good candidate for shifting to deferred late-loading using patterns like import-on-interaction.
你可能正在导入第三方脚本,但对其渲染内容或加载代码的时间控制较少。实现交互时加载的一个简单选项是使用外观(facade)。外观是高成本组件的简单“预览”或“占位符”,你可以用图片或截图模拟基本体验。这是Lighthouse团队对这个概念的称呼。
当用户点击“预览”(外观)时,资源的代码才会加载。这样可以避免用户为他们不会使用的功能付出体验成本。同样,外观可以在悬停时预连接到必要的资源。
第三方资源通常被添加到页面中,而没有充分考虑它们如何融入网站的整体加载流程。同步加载的第三方脚本会阻塞浏览器解析器,并可能延迟 hydration(水合)。如果可能,第三方脚本应使用async/defer(或其他方法)加载,以确保第一方脚本不会被占用网络带宽。除非它们是关键资源,否则它们非常适合使用交互时导入等模式进行延迟加载。

Video Player Embeds

视频播放器嵌入

A good example of a "facade" is the YouTube Lite Embed by Paul Irish. This provides a Custom Element which takes a YouTube Video ID and presents a minimal thumbnail and play button. Clicking the element dynamically loads the full YouTube embed code, meaning users who never click play don't pay the cost of fetching and processing it.
A similar technique is used in production on a few Google sites. On Android.com, rather than eagerly loading the YouTube video player embed, a thumbnail with a fake player button is shown to users. When they click it, a modal loads which auto-plays the video using the full-fat YouTube video player embed.
外观的一个很好的例子是Paul Irish开发的YouTube Lite Embed。它提供了一个自定义元素,接受YouTube视频ID,并显示最小化的缩略图和播放按钮。点击该元素会动态加载完整的YouTube嵌入代码,这意味着从未点击播放的用户无需承担获取和处理该代码的成本。
谷歌的一些网站在生产环境中使用了类似的技术。在Android.com上,他们没有立即加载YouTube视频播放器嵌入,而是向用户显示带有假播放按钮的缩略图。当用户点击它时,会加载一个模态框,使用完整的YouTube视频播放器嵌入自动播放视频。

Authentication

身份验证

Apps may need to support authentication with a service via a client-side JavaScript SDK. These can occasionally be large with heavy JS execution costs and one might rather not eagerly load them up front if a user isn't going to login. Instead, dynamically import authentication libraries when a user clicks on a "Login" button, keeping the main thread more free during initial load.
应用可能需要通过客户端JavaScript SDK支持与服务的身份验证。这些SDK有时体积很大,执行成本高,如果用户不打算登录,你可能不想在初始加载时就立即加载它们。相反,当用户点击“登录”按钮时动态导入身份验证库,这样在初始加载时可以让主线程更空闲。

Chat widgets

聊天小部件

Calibre app improved performance of their Intercom-based live chat by 30% through usage of a similar facade approach. They implemented a "fake" fast loading live chat button using just CSS and HTML, which when clicked would load their Intercom bundles.
Postmark noted that their Help chat widget was always eagerly loaded, even though it was only occasionally used by customers. The widget would pull in 314KB of script, more than their whole home page. To improve user-experience, they replaced the widget with a fake replica using HTML and CSS, loading the real-thing on click. This change reduced Time to Interactive from 7.7s to 3.7s.
Calibre应用通过使用类似的外观方法,将基于Intercom的实时聊天性能提升了30%。他们仅使用CSS和HTML实现了一个“假的”快速加载实时聊天按钮,点击时才会加载Intercom包。
Postmark指出,他们的帮助聊天小部件总是被立即加载,尽管客户只是偶尔使用。该小部件会引入314KB的脚本,比他们整个主页的大小还大。为了改善用户体验,他们用HTML和CSS制作的假副本替换了该小部件,点击时才加载真实的小部件。这一变化将交互就绪时间从7.7秒减少到3.7秒。

Others

其他案例

Ne-digital used a React library for animated scrolling back to the top of a page when a user clicks on a "scroll to top" button. Rather than eagerly loading the react-scroll dependency for this, they load it on interaction with the button, saving ~7KB:
js
handleScrollToTop() {
  import('react-scroll').then(scroll => {
    scroll.animateScroll.scrollToTop({
    })
  })
}
Ne-digital使用了一个React库,当用户点击“返回顶部”按钮时,实现动画滚动到页面顶部。他们没有立即加载react-scroll依赖,而是在用户与按钮交互时才加载,节省了约7KB的体积:
js
handleScrollToTop() {
  import('react-scroll').then(scroll => {
    scroll.animateScroll.scrollToTop({
    })
  })
}

How do you import-on-interaction?

如何实现交互时导入?

Vanilla JavaScript

原生JavaScript

In JavaScript, dynamic import() enables lazy-loading modules and returns a promise and can be quite powerful when applied correctly. Below is an example where dynamic import is used in a button event listener to import the lodash.sortby module and then use it.
js
const btn = document.querySelector("button");

btn.addEventListener("click", (e) => {
  e.preventDefault();
  import("lodash.sortby")
    .then((module) => module.default)
    .then(sortInput()) // use the imported dependency
    .catch((err) => {
      console.log(err);
    });
});
Prior to dynamic import or for use-cases it doesn't fit as well, dynamically injecting scripts into the page using a Promise-based script loader was also an option:
js
const loginBtn = document.querySelector("#login");

loginBtn.addEventListener("click", () => {
  const loader = new scriptLoader();
  loader
    .load(["//apis.google.com/js/client:platform.js?onload=showLoginScreen"])
    .then(({ length }) => {
      console.log(`${length} scripts loaded!`);
    });
});
在JavaScript中,dynamic import()支持懒加载模块并返回一个Promise,正确应用时功能非常强大。下面是一个示例,在按钮事件监听器中使用动态导入来导入lodash.sortby模块并使用它。
js
const btn = document.querySelector("button");

btn.addEventListener("click", (e) => {
  e.preventDefault();
  import("lodash.sortby")
    .then((module) => module.default)
    .then(sortInput()) // 使用导入的依赖
    .catch((err) => {
      console.log(err);
    });
});
在dynamic import出现之前,或者对于它不太适用的场景,使用基于Promise的脚本加载器将脚本动态注入页面也是一种选择:
js
const loginBtn = document.querySelector("#login");

loginBtn.addEventListener("click", () => {
  const loader = new scriptLoader();
  loader
    .load(["//apis.google.com/js/client:platform.js?onload=showLoginScreen"])
    .then(({ length }) => {
      console.log(`${length} scripts loaded!`);
    });
});

React

React

Let's imagine we have a Chat application which has a
<MessageList>
,
<MessageInput>
and an
<EmojiPicker>
component (powered by emoji-mart, which is 98KB minified and gzipped). It can be common to eagerly load all of these components on initial page-load.
js
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import EmojiPicker from './EmojiPicker';

const Channel = () => {
  ...
  return (
    <div>
      <MessageList />
      <MessageInput />
      {emojiPickerOpen && <EmojiPicker />}
    </div>
  );
};
Breaking the loading of this work up is relatively straight-forward with code-splitting. The
React.lazy
method makes it easy to code-split a React application on a component level using dynamic imports. The
React.lazy
function provides a built-in way to separate components in an application into separate chunks of JavaScript with very little legwork. You can then take care of loading states when you couple it with the Suspense component.
js
import React, { lazy, Suspense } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';

const EmojiPicker = lazy(
  () => import('./EmojiPicker')
);

const Channel = () => {
  ...
  return (
    <div>
      <MessageList />
      <MessageInput />
      {emojiPickerOpen && (
        <Suspense fallback={<div>Loading...</div>}>
          <EmojiPicker />
        </Suspense>
      )}
    </div>
  );
};
We can extend this idea to only import code for the Emoji Picker component when the Emoji icon is clicked in a
<MessageInput>
, rather than eagerly when the application initially loads:
js
import React, { useState, createElement } from "react";
import MessageList from "./MessageList";
import MessageInput from "./MessageInput";
import ErrorBoundary from "./ErrorBoundary";

const Channel = () => {
  const [emojiPickerEl, setEmojiPickerEl] = useState(null);

  const openEmojiPicker = () => {
    import(/* webpackChunkName: "emoji-picker" */ "./EmojiPicker")
      .then((module) => module.default)
      .then((emojiPicker) => {
        setEmojiPickerEl(createElement(emojiPicker));
      });
  };

  const closeEmojiPickerHandler = () => {
    setEmojiPickerEl(null);
  };

  return (
    <ErrorBoundary>
      <div>
        <MessageList />
        <MessageInput onClick={openEmojiPicker} />
        {emojiPickerEl}
      </div>
    </ErrorBoundary>
  );
};
假设我们有一个聊天应用,包含
<MessageList>
<MessageInput>
<EmojiPicker>
组件(由emoji-mart提供支持,其压缩后大小为98KB)。通常在初始页面加载时会立即加载所有这些组件。
js
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import EmojiPicker from './EmojiPicker';

const Channel = () => {
  ...
  return (
    <div>
      <MessageList />
      <MessageInput />
      {emojiPickerOpen && <EmojiPicker />}
    </div>
  );
};
通过代码分割(code-splitting)可以很容易地拆分这些加载工作。
React.lazy
方法使得使用动态导入在组件级别对React应用进行代码分割变得简单。
React.lazy
函数提供了一种内置方式,可以将应用中的组件分离到单独的JavaScript块中,只需很少的工作量。结合Suspense组件,你还可以处理加载状态。
js
import React, { lazy, Suspense } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';

const EmojiPicker = lazy(
  () => import('./EmojiPicker')
);

const Channel = () => {
  ...
  return (
    <div>
      <MessageList />
      <MessageInput />
      {emojiPickerOpen && (
        <Suspense fallback={<div>Loading...</div>}>
          <EmojiPicker />
        </Suspense>
      )}
    </div>
  );
};
我们可以扩展这个想法,仅当用户点击
<MessageInput>
中的表情图标时才导入Emoji Picker组件的代码,而不是在应用初始加载时立即导入:
js
import React, { useState, createElement } from "react";
import MessageList from "./MessageList";
import MessageInput from "./MessageInput";
import ErrorBoundary from "./ErrorBoundary";

const Channel = () => {
  const [emojiPickerEl, setEmojiPickerEl] = useState(null);

  const openEmojiPicker = () => {
    import(/* webpackChunkName: "emoji-picker" */ "./EmojiPicker")
      .then((module) => module.default)
      .then((emojiPicker) => {
        setEmojiPickerEl(createElement(emojiPicker));
      });
  };

  const closeEmojiPickerHandler = () => {
    setEmojiPickerEl(null);
  };

  return (
    <ErrorBoundary>
      <div>
        <MessageList />
        <MessageInput onClick={openEmojiPicker} />
        {emojiPickerEl}
      </div>
    </ErrorBoundary>
  );
};

Vue

Vue

In Vue.js, a similar import-on-interaction pattern can be accomplished in a few different ways. One way is to dynamically import the
Emojipicker
Vue component using dynamic import wrapped in a function i.e
() => import("./Emojipicker")
. Typically doing this will have Vue.js lazy-load the component when it needs to be rendered.
We can then gate lazy-loading behind a user-interaction. Using a conditional
v-if
on the picker's parent
div
which is toggled by clicking a button, we can then both conditionally fetch and render the
Emojipicker
component when the user clicks.
html
<template>
  <div>
    <button @click="show = true">Load Emoji Picker</button>
    <div v-if="show">
      <emojipicker></emojipicker>
    </div>
  </div>
</template>

<script>
  export default {
    data: () => ({ show: false }),
    components: {
      Emojipicker: () => import("./Emojipicker"),
    },
  };
</script>
The import-on-interaction pattern should be possible with most frameworks and libraries that support dynamic component loading, including Angular.
在Vue.js中,可以通过几种不同的方式实现类似的交互时导入模式。一种方式是使用包裹在函数中的动态导入来动态导入
Emojipicker
Vue组件,即
() => import("./Emojipicker")
。通常这样做会让Vue.js在需要渲染组件时懒加载它。
然后我们可以将懒加载限制在用户交互之后。通过在选择器的父
div
上使用条件
v-if
,并通过点击按钮切换它,我们可以在用户点击时条件式地获取并渲染
Emojipicker
组件。
html
<template>
  <div>
    <button @click="show = true">Load Emoji Picker</button>
    <div v-if="show">
      <emojipicker></emojipicker>
    </div>
  </div>
</template>

<script>
  export default {
    data: () => ({ show: false }),
    components: {
      Emojipicker: () => import("./Emojipicker"),
    },
  };
</script>
交互时导入模式应该可以在大多数支持动态组件加载的框架和库中实现,包括Angular

Import-on-interaction for first-party code as part of progressive loading

第一方代码的交互时导入作为渐进式加载的一部分

Loading code on interaction also happens to be a key part of how Google handles progressive loading in large applications like Flights and Photos. To illustrate this, let's take a look at an example previously presented by Shubhie Panicker.
Imagine a user is planning a trip to Mumbai, India and they visit Google Hotels to look at prices. All of the resources needed for this interaction could be loaded eagerly upfront, but if a user hasn't selected any destination, the HTML/CSS/JS required for the map would be unnecessary.
In the simplest download scenario, imagine Google Hotels is using naive client-side rendering (CSR). All the code would be downloaded and processed upfront: HTML, followed by JS, CSS and then fetching the data, only to render once we have everything. However, this leaves the user waiting a long time with nothing displayed on-screen. A big chunk of the JavaScript and CSS may be unnecessary.
Next, imagine this experience moved to server-side rendering (SSR). We would allow the user to get a visually complete page sooner, which is great, however it wouldn't be interactive until the data is fetched from the server and the client framework completes hydration.
SSR can be an improvement, but the user may have an uncanny valley experience where the page looks ready, but they are unable to tap on anything. Sometimes this is referred to as rage clicks as users tend to click over and over again repeatedly in frustration.
Returning to the Google Hotels search example, if we zoom in to the UI a little we can see that when a user clicks on "more filters" to find exactly the right hotel, the code required for that component is downloaded.
在交互时加载代码也是谷歌在Flights和Photos等大型应用中处理渐进式加载的关键部分。为了说明这一点,让我们看一下Shubhie Panicker之前展示的例子。
假设用户计划前往印度孟买,并访问Google Hotels查看价格。此交互所需的所有资源都可以立即加载,但如果用户尚未选择任何目的地,地图所需的HTML/CSS/JS就是不必要的。
在最简单的下载场景中,假设Google Hotels使用原生客户端渲染(CSR)。所有代码都会立即下载和处理:HTML,然后是JS、CSS,接着获取数据,只有在所有内容都准备好后才会渲染。然而,这会让用户长时间等待,屏幕上什么都不显示。很大一部分JavaScript和CSS可能是不必要的。
接下来,假设将此体验迁移到服务器端渲染(SSR)。我们可以让用户更快地获得视觉上完整的页面,这很好,但在从服务器获取数据并完成客户端框架的hydration之前,页面无法交互。
SSR可能是一种改进,但用户可能会遇到“恐怖谷”体验:页面看起来已准备就绪,但他们无法点击任何内容。有时这被称为“愤怒点击”,因为用户会反复点击以表达不满。
回到Google Hotels搜索示例,我们放大UI可以看到,当用户点击“更多筛选条件”以找到合适的酒店时,该组件所需的代码才会被下载。

Only very minimal code is downloaded initially and beyond this, user interaction dictates which code is sent down when.

最初只下载极少的代码,除此之外,用户交互决定何时发送哪些代码。

There are a number of important aspects to interaction-driven late-loading:
  • First, we download the minimal code initially so the page is visually complete quickly.
  • Next, as the user starts interacting with the page we use those interactions to determine which other code to load. For example loading the code for the "more filters" component.
  • This means code for many features on the page are never sent down to the browser, as the user didn't need to use them.
交互驱动的延迟加载有几个重要方面:
  • 首先,我们最初只下载最少的代码,以便页面快速呈现视觉完整状态。
  • 接下来,当用户开始与页面交互时,我们利用这些交互来决定加载哪些其他代码。例如加载“更多筛选条件”组件的代码。
  • 这意味着页面上许多功能的代码永远不会发送到浏览器,因为用户不需要使用它们。

How do we avoid losing early clicks?

如何避免丢失早期点击?

In the framework stack used by these Google teams, we can track clicks early because the first chunk of HTML includes a small event library (JSAction) which tracks all clicks before the framework is bootstrapped. The events are used for two things:
  • Triggering download of component code based on user interactions
  • Replaying user interactions when the framework finishes bootstrapping
Other potential heuristics one could use include, loading component code:
  • A period after idle time
  • On user mouse hover over the relevant UI/button/call to action
  • Based on a sliding scale of eagerness based on browser signals (e.g network speed, Data Saver mode etc).
在这些谷歌团队使用的框架栈中,我们可以提前跟踪点击,因为第一块HTML包含一个小型事件库(JSAction),它会在框架启动前跟踪所有点击。这些事件用于两个目的:
  • 根据用户交互触发组件代码的下载
  • 框架启动完成后重放用户交互
其他可能使用的启发式方法包括,在以下时机加载组件代码:
  • 空闲时间过后一段时间
  • 用户将鼠标悬停在相关UI/按钮/调用操作上时
  • 根据浏览器信号(如网络速度、数据节省模式等)调整加载优先级

What about data?

数据怎么办?

The initial data which is used to render the page is included in the initial page's SSR HTML and streamed. Data that is late loaded is downloaded based on user interactions as we know what component it goes with.
This completes the import-on-interaction picture with data-fetching working similar to how CSS and JS function. As the component is aware of what code and data it needs, all of its resources are never more than a request away.
This functions as we create a graph of components and their dependencies during build time. The web application is able to refer to this graph at any point and quickly fetch the resources (code and data) needed for any component. It also means we code-split based on the component rather than the route.
For a walkthrough of the above example, see Elevating the Web Platform with the JavaScript Community.
用于渲染页面的初始数据包含在初始页面的SSR HTML中并流式传输。延迟加载的数据会根据用户交互下载,因为我们知道它属于哪个组件。
这完成了交互时导入的场景,数据获取的工作方式与CSS和JS类似。由于组件知道它需要哪些代码和数据,其所有资源都只需一次请求即可获取。
这是因为我们在构建时创建了组件及其依赖项的图。Web应用可以随时引用此图,并快速获取任何组件所需的资源(代码和数据)。这也意味着我们基于组件而不是路由进行代码分割。
有关上述示例的详细讲解,请查看Elevating the Web Platform with the JavaScript Community

Trade-offs

权衡

Shifting costly work closer to user-interaction can optimize how quickly pages initially load, however the technique is not without trade-offs.
What happens if it takes a long time to load a script after the user clicks?
In the Google Hotels example, small granular chunks minimize the chance a user is going to wait long for code and data to fetch and execute. In some of the other cases, a large dependency may indeed introduce this concern on slower networks.
One way to reduce the chance of this happening is to better break-up the loading of, or prefetch these resources after critical content in the page is done loading. I'd encourage measuring the impact of this to determine how much it's a real application in your apps.
What about lack of functionality before user interaction?
Another trade-off with facades is a lack of functionality prior to user interaction. An embedded video player for example will not be able to autoplay media. If such functionality is key, you might consider alternative approaches to loading the resources, such as lazy-loading these third-party iframes on the user scrolling them into view rather than deferring load until interaction.
将高成本的工作推迟到用户交互时进行,可以优化页面的初始加载速度,但这种技术并非没有权衡。
如果用户点击后加载脚本需要很长时间怎么办?
在Google Hotels示例中,小粒度的代码块将用户等待代码和数据获取与执行的时间降到最低。在其他一些情况下,大型依赖项确实可能在较慢的网络上引发这种担忧。
减少这种情况发生的一种方法是更好地拆分加载,或者在页面关键内容加载完成后预获取这些资源。我建议衡量这种方法的影响,以确定它在你的应用中是否真的适用。
用户交互前功能缺失怎么办?
外观的另一个权衡是用户交互前功能缺失。例如,嵌入的视频播放器将无法自动播放媒体。如果此类功能至关重要,你可能需要考虑其他加载资源的方法,比如在用户将第三方iframe滚动到视图中时懒加载,而不是延迟到交互时加载。

Replacing interactive embeds with a static variant

用静态变体替换交互式嵌入

We have discussed the import-on-interaction pattern and progressive loading, but what about going entirely static for the embeds use-case?
The final rendered content from an embed may be needed immediately in some cases e.g a social media post that is visible in the initial viewport. This can also introduce its own challenges when the embed brings in 2-3MB of JavaScript. Because the embed content is needed right away, lazy-loading and facades may be less applicable.
If optimizing for performance, it's possible to entirely replace an embed with a static variant that looks similar, linking out to a more interactive version (e.g the original social media post). At build time, the data for the embed can be pulled in and transformed into a static HTML version.
While static replacements can be good for performance, they do often require doing something custom so keep this in mind when evaluating your options.
我们已经讨论了交互时导入模式和渐进式加载,但对于嵌入场景,完全使用静态变体怎么样?
在某些情况下,嵌入的最终渲染内容可能需要立即显示,例如初始视口中可见的社交媒体帖子。当嵌入带来2-3MB的JavaScript时,这也会带来自身的挑战。因为嵌入内容是立即需要的,懒加载和外观可能不太适用。
如果要优化性能,可以完全用外观相似的静态变体替换嵌入,链接到更具交互性的版本(例如原始社交媒体帖子)。在构建时,可以获取嵌入的数据并将其转换为静态HTML版本。
虽然静态变体对性能有好处,但它们通常需要自定义实现,因此在评估选项时请记住这一点。

Conclusions

结论

First-party JavaScript often impacts the interaction readiness of modern pages on the web, but it can often get delayed on the network behind non-critical JS from either first or third-party sources that keep the main thread busy.
In general, avoid synchronous third-party scripts in the document head and aim to load non-blocking third-party scripts after first-party JS has finished loading. Patterns like import-on-interaction give us a way to defer the loading of non-critical resources to a point when a user is much more likely to need the UI they power.
第一方JavaScript通常会影响现代网页的交互就绪性,但它常常会被第一方或第三方的非关键JS阻塞在网络上,导致主线程繁忙。
一般来说,避免在文档头部使用同步第三方脚本,并尽量在第一方JS加载完成后加载非阻塞的第三方脚本。交互时导入等模式为我们提供了一种将非关键资源的加载推迟到用户更可能需要其支持的UI的时机的方法。

Source

来源