不使用JavaScript实现无序流式传输HTML

原文:https://lamplightdev.com/blog/2024/01/10/streaming-html-out-of-order-without-javascript/

让我们从一个演示开始:https://ooo.lamplightdev.workers.dev

这是一个简单的页面,显示了一个包含10个项目的列表。尝试在浏览器中启用和禁用JavaScript,你会注意到几个事情:

  1. '应用外壳'首先渲染 - 你会看到标题和页脚,但列表项目将要渲染的地方有一个加载占位符。

  2. 一秒钟后,加载占位符被列表项目替换 - 但每个项目本身都有一个加载占位符。

  3. 然后项目内容无序渲染,替换加载占位符 - 你首先看到项目5,然后随着它们的生成,看到其他项目。

  4. 如果查看页面源代码,你会发现HTML按发送顺序排列 - 而不是渲染顺序

  5. 该页面使用了无自定义元素的Shadow DOM

挺不错的,对吧?虽然这可能是一个刻意构建的例子,但这是一种有趣的技术,以前不使用JavaScript是不可能实现的。

查看此演示的代码,或者继续阅读以了解其工作原理。

背景

HTML流式传输

流式传输HTML的概念 - 将HTML从Web服务器发送到浏览器时,将其分块发送 - 并不新鲜。在现代前端框架和单页面应用盛行的初期,它似乎被搁置了一旁 - 在那里,整个页面在浏览器中生成 - 但随着摆锤向着使用全栈框架进行服务器端渲染的方向摆动,流式响应再次变得流行起来

与等待整个响应生成后再将其发送到浏览器相比,流式传输HTML的优势是显而易见的 - 你可以立即渲染一些内容,向用户指示正在发生的事情,并且你可以在等待响应的耗时部分生成时提前下载CSS和JavaScript等资源

直到目前为止,缺乏的是一种无序流式传输HTML的方法 - 也就是说,按照生成的顺序将HTML分块流式传输,而不用担心将分块按顺序发送到浏览器 - 并且仍然使浏览器按照正确的顺序渲染HTML块,就像上面的演示一样。

现代全栈框架通过使用各种聪明的技术实现了这种功能,所有这些技术都需要对特定框架的支持以及大量的JavaScript。如果你的用例可以接受这一点,那可能没什么问题,但是如果我们能够在没有任何JavaScript或框架的情况下实现相同的效果会怎样呢? 现在你可以了

Shadow DOM

Shadow DOM是一种在页面的其余部分隔离渲染DOM的方法。虽然通常与自定义元素相关联,但Shadow DOM可以与任何HTML标签一起使用,比如普通的<div>标签。

它还有插槽的概念 - 标记为插槽的标签可以从父标签的其他位置渲染HTML进来,方法是在要渲染的标签上指定一个slot属性。在这里,Shadow 根节点附加到外部<div>标签,并且内部<div>标签被渲染到该Shadow 根节点中的插槽中:

<div>
  #shadowroot
    <header>标题</header>
    <main>
      <slot name="content"></slot>
    </main>
    <footer>页脚</footer>
  <div slot="content">
    这个div将被渲染到上面的插槽中。神奇!
  </div>
</div>

要求

那么,如何使用Shadow DOM无序流式传输HTML呢?你需要一些东西:

  1. 支持流式响应的http服务器。你很幸运,几乎所有语言都普遍支持这一点。我选择了Hono,因为它是一个轻量级服务器,基于Web标准构建,在node上运行以及各种边缘平台上运行。值得注意的是,这并不依赖于JavaScript后端 - 同样的效果可以在PHP、Java、Go等上实现。

注意

所有浏览器都支持流式传输HTML,但是Safari似乎对何时触发流式传输有较高的阈值 - 这似乎在512字节左右。这样做的结果是,Safari会缓冲内容,直到达到该阈值,然后渲染它所拥有的内容再进行流式传输剩余内容。

  1. 支持流式传输的模板语言

。理论上,你不需要模板语言 - 你可以手工编写HTML并手动管理流式传输 - 但那是很多工作。在JavaScript世界中,没有多少独立的模板语言支持流式传输,但最近有一个叫做SWTL的项目支持。SWTL被创建用于Service Workers,但由于我们始终使用Web标准,因此也可以在服务器上使用。SWTL的另一个好处是你几乎可以将任何东西都放入其中 - 异步函数、生成器、数组、响应 - 它都能处理。

  1. 声明性Shadow DOM - 直到最近,自定义Shadow DOM是一种仅限浏览器的技术 - 只能使用JavaScript在浏览器中创建Shadow DOM - 但现在,得益于声明性Shadow DOM(DSD),你可以在服务器上创建Shadow DOM,而浏览器会通过在<template>标签上使用新的shadowrootmode属性来渲染它而无需JavaScript。然后,Shadow 根节点会自动附加到包含元素上:
<div>
  <template shadowrootmode="open">
    <header>标题</header>
    <main>
      <slot name="content"></slot>
    </main>
    <footer>页脚</footer>
  </template>

  <div slot="content">
    这个div将在没有JavaScript的情况下渲染到上面的插槽中。更多魔法!
  </div>
</div>

浏览器支持

截至撰写本文时,DSD在Chrome和Safari中得到支持。Firefox也即将支持,预计将在2024年2月/3月(版本123/124)发布 - 因此,如果你的目标是面向现代浏览器,这很快将成为一个你可以自由使用的技术。

对于尚未支持它的浏览器,有一个polyfill可用,如果你确实需要它的话。


将其组合在一起

那么,初始演示是如何创建的呢?让我们通过一个简化的代码示例来分解它:

import { Hono } from 'hono';
import { stream } from 'hono/streaming';

import { render, html } from 'swtl';

import {
  delayed,
  createReadableStreamFromAsyncGenerator
} from './utils.js';

const app = new Hono();

app.get('/', (ctx) => {
  /*
    由SWTL提供的`html`标记模板字面量允许传入异步函数。
    在这里,插槽内容被包裹在一个引入人为延迟的函数中。
  */
  const template = ({ name }) => html`
    <html>
      <head>
        <title>流式示例>
      </head>
      <body>
        <div>
          <template shadowrootmode="open">
            <header>标题</header>
            <main>
              <slot name="content"></slot>
            </main>
            <footer>页脚</footer>
          </template>

            <!--
              上面的HTML首先发送给浏览器
            -->

            <!--
              添加了人为延迟的插槽内容
              模拟服务器响应缓慢的情况:
            -->

            ${delayed(1000, html`
              <p slot="content">
                你好,${name}!
              </p>
            `)}

            <!--
              一旦延迟内容已发送,剩余的HTML将发送给浏览器
            -->
          </div>
        </div>
      </body>
    </html>
  `;

  return stream(ctx, async (stream) => {
    ctx.res.headers.set('Content-Type', 'text/html');

    /*
      最后,`render`方法将输出转换为异步生成器,
      然后将其转换为编码流并作为生成时管道传输到响应中。
    */
    await stream.pipe(
      createReadableStreamFromAsyncGenerator(
        render(
          template({ name: 'Ada' })
        )
      )
    );
  });
});

export default app;

就是这样!浏览此演示的代码并自己尝试一下。我很乐意听到你对这种技术的想法 - 以及你能想到的任何新奇用例 - 所以请联系我。下次见👋。

感谢@passle,SWTL的作者,为本文进行校对和反馈。_

2024-03-14

访问量 12

扫码关注公众号“前端微志”

第一时间获取新周刊

预览图片