探索Web渲染:流html
#javascript #性能 #流媒体

Kylir Horton在flickr上的照片

5部分系列:探索Web渲染

  1. 同构JavaScript和水合
  2. 部分水合(又称岛屿)
  3. 渐进水合
  4. 流html µ正在阅读此
  5. 服务器组件

欢迎回到有关Web渲染的以表演为重点的系列中的第四篇文章;我们在很短的时间内走了很长一段路,所以感谢那些跟随的人。该讨论始于客户端渲染(CSR),出于性能原因而进步并鼓励服务器端渲染(SSR)。请记住,服务器是此策略的关键,因为使用它来消除在浏览器中使用JavaScript的需求。正如part 2 of this series about islands rendering所述,过去10年中,中端和高级手机之间的快速增长的性能鸿沟表示,更快的硬件不再是为了更好地性能而依靠更快的硬件。相反,必须提高执行效率,并且每字节最昂贵的资产下载是JavaScript。因此,最小化JavaScript的使用是良好表现的关键,因为本系列中的先前文章已经解释了。

试图防止任何与术语相关的误解,流媒体是一个通常与音频和视频相关的常见术语。但是,流媒体HTML是一项无关,基于文本的技术,专注于尽快向用户提供内容。尽管两者都可以在浏览器中完成,但后者是本文的重点。

流媒体HTML与迄今为止本系列中探讨的基于水合的主题截然不同。让我们通过在深入研究细节之前提供简短的历史,一般定义和一个抽象的例子来开始我们的调查。早在1994年,Netscape Navigator 1.0 Beta引入了一个名为progressive HTML rendering的功能,该功能允许响应HTML定期冲洗到块中的浏览器,直到响应完成为止。实际上,HTTP 1.1后来增加了对这样做的官方支持,称为chunked transfer encoding,其中一流可以转移多个块,因此流媒体名称为“流”。流式渲染有效地是同一概念的现代重命名,它使用单个HTTP响应来递增服务器渲染页面的HTML,同时最小化所使用的JavaScript的量。更具体地说,初始流响应在等待数据返回时包含数据独立的组件和占位符内容的静态内容。同时,在服务器等待其数据请求完成时,HTTP连接保持打开状态。随后,每一位响应都将在用户浏览器准备就绪后立即流式传输,然后将其各自的占位符HTML替换,最后,当所有数据请求完成时,连接都关闭。相反,传统服务器渲染等待页面的完整输出完成,包括所有响应开始之前的所有数据请求;大多数服务器渲染的网页以这种方式加载。

提供一些额外的清晰度,请考虑与客户,餐桌,服务员,厨房和食物盘的晚餐类比。客户订购了3盘食物A(红色),B(绿色)和C(蓝色),分别至少到大多数耗时的准备。借助传统的服务器渲染,服务员将等待厨房完成所有3个板,然后再一次将它们全部退回桌子。具体来说,使用下图,步骤1显示板A已完成,但必须在厨房里等待。步骤2显示了板B的完成,其后是步骤3,该步骤3显示了板C最后完成。直到第4步,所有盘子都不会立即离开厨房,将其带到餐桌上。请注意,没有盘子可以单独离开厨房;逆是流渲染启用的主要增强。

Diagram showing how traditional server rendering requires all plates from the kitchen to be complete before being delivered to the customer's table

传统服务器渲染要求所有板都必须完成

相反,流媒体允许服务员在厨房完成时将每个盘子带到桌子上。具体来说,步骤1显示板A作为准备食用。在步骤2的情况下,客户在桌子上接收盘子A的同时,厨房完成了板B。同样,在步骤3中,随着板C的完成,服务员将板B带到餐桌上。到第4步,服务员将所有3个盘子带到了客户的桌子上。显然,在流媒体示例中,客户可以开始更快地进食。我知道我希望有哪个服务员!

Diagram showing how streaming rendering allows any plate to be delivered to the customer's table as soon as the kitchen completes it

流渲染允许将每个板放在桌子上,因为它准备就绪

流响应可以以两种方式之一发生:(1)按顺序和(2)级排出。顾名思义,按处分流的方式将HTML以与撰写的顺序相同的顺序传递到浏览器。这意味着只需要HTTP,但没有JavaScript!例如,如果需要3个数据请求来构建页面的输出,并且第一个请求最慢,则第一个请求必须完成并在每个剩余数据请求将其内容交付到页面之前返回其HTML;这与上面的传统服务器渲染板相似,特别是迫使完成的数据请求在服务之前等待。不幸的是,不可行的流媒体可能需要浏览器中的一小部分JavaScript才能渲染HTML的每个部分,但是在每个数据请求完成后立即响应是一个巨大的优势;因此,包括React在内的大多数现代框架都使用此方法。更具体地说,每个占位符都用标识符渲染,每个内容块之后都有一小部分JavaScript,用于查找相应的占位符ID,然后用新内容替换HTML;以下代码示例将详细显示。

现在已经了解了基本原理,为了更好地阐明细节的现实近似React行为。使用koude0内置组件与通过renderToPipeableStream(Node.js)或renderToReadableStream(其他Runtimes)渲染结合使用,允许全序的流式传输支持。从数据库中加载特定于用户的数据通常可以是延迟的来源,因此,如果用户登录,则假设具有静态内容的演示主页应用程序和动态用户配置文件下拉列表。具体来说,<HomepageLayout /><Carousel /><Offers /><LearnMore />组件ast Outport static HTML,而我们的<UserProfile />组件根据从数据库中获取的数据动态构建的HTML返回动态构建的HTML。该应用程序可以这样构建:

<HomepageLayout>
  <Suspense fallback={<LoadingSpinner />}>
    <UserProfile />
  </Suspense>
  <Carousel />
  <Offers />
  <LearnMore />
</HomepageLayout>

React将呈现此上自上而下,因此首先将渲染<HomepageLayout> layout component及其子女 - 所有这些都是呈现的剩余组件。首先,这些孩子是<UserProfile />周围的悬念边界。由于在服务器上获取了<UserProfile />数据,因此显示了后备组件<LoadingSpinner />。接下来,<Carousel /><Offers /><LearnMore />组件将其静态内容按顺序呈现。最后,由于所有组件都呈现了一次,因此浏览器中的组件树看起来像这样(从一个代码块变为下一个,被启动变化包围,最终更改html注释):

<HomepageLayout>

  <!-- START CHANGE -->
  <div id=suspense-placeholder-1>
    <LoadingSpinner />
  </div>
  <!-- END CHANGE -->

  <Carousel />
  <Offers />
  <LearnMore />
</HomepageLayout>

一旦将数据完全获取在服务器上,<UserProfile />组件流的剩余HTML在DOM末尾浏览器的浏览器:

<HomepageLayout>
  <div id=suspense-placeholder-1>
    <LoadingSpinner />
  </div>
  <Carousel />
  <Offers />
  <LearnMore />
</HomepageLayout>

<!-- START CHANGE -->
<!-- begin UserProfile HTML -->
<ul>
  <li>menu item 1</li>
  <li>menu item 2</li>
  <li><button type=button>Logout</button></li>
</ul>
<script type=text/javascript id=suspense-render-1>
  const suspenseRender = document
    .getElementById(‘suspense-render-1’) // Assume this works
    .previousElementSibling;
  document
    .getElementById(‘suspense-placeholder-1’)
    .replaceWith(suspenseRender);

  // Hydration code here...
</script>
<!-- end UserProfile HTML -->
<!-- END CHANGE -->

当执行同步悬浮式-1 <script>时,<UserProfile /> html被复制到正确的位置中,然后被水合:

<HomepageLayout>

  <!-- START CHANGE -->
  <ul>
    <li>menu item 1</li>
    <li>menu item 2</li>
    <li><button type=button>Logout</button></li>
  </ul>
  <!-- END CHANGE -->

  <Carousel />
  <Offers />
  <LearnMore />
</HomepageLayout>
<!-- begin UserProfile HTML -->
<ul>
  <li>menu item 1</li>
  <li>menu item 2</li>
  <li><button type=button>Logout</button></li>
</ul>
<script type=text/javascript id=suspense-render-1>
  const suspenseRender = document
    .getElementById(‘suspense-render-1’)
    .previousElementSibling;
  document
    .getElementById(‘suspense-placeholder-1’)
    .replaceWith(suspenseRender);

  // Hydration code here...
</script>
<!-- end UserProfile HTML -->

在其所需位置的最终HTML保湿后仍具有完全互动性。 <script>标签可以有效地忽略,因为它已完成。

在审查了其基本和实际方面后,流渲染仍然可以通过评估其优点和缺点来更好地理解。

流渲染可为传统服务器渲染提供许多好处。将数据获取和渲染移到服务器通常涉及延迟惩罚,因为两者都必须在发送响应之前完全完成。流媒体通过允许将后备UI元素代替数据依赖性组件来消除这种限制,直到可以将后者传递到浏览器为止。此外,如上面的React示例所示,HTML块可以以任何顺序流式传输和小尾声JavaScript有效载荷以将HTML交换为最终位置。除了本系列概述的众多性能好处外,客户渲染的应用程序通过在服务器上进行数据获取和渲染来享受安全益处,因为供应商,技术和API代币远离窥视眼睛。如果使用edge rendering交付,流媒体应用程序通常会像访问端的应用程序一样快。

流媒体有一些弊端。一旦响应开始并选择了响应代码(例如200),就无法更改它,因此数据获取期间发生的错误将不得不让用户以另一种方式知道。另外,并非所有框架和运行时间都支持流,因此请仔细选择。 Markoone of the first(2014!)JavaScript框架以支持流媒体,而ReactSolidJS也对流媒体也有很大的支持。

我们的流媒体旅程已经结束,所以我希望它令人愉悦和启发!如果您对此主题或任何其他主题有任何疑问或反馈,请与me on Twitter分享您的想法。我一定会喜欢您的来信!并确保查看Babbel’s engineering team on Twitter,以了解有关该部门正在发生的事情的更多信息。当我们探索服务器组件时,本系列的下一篇也是最后一篇文章将真正是史诗般的努力!他们不仅是为了反应