为什么 Firefox 在这个简单的示例中不显示部分接收的 HTML

问题描述 投票:0回答:1

我编写了一个简单的 NodeJS 代码来处理 HTML 流,类似于 Next.js 的做法,但没有 React。代码首先将占位符 HTML 作为部分 HTML 片段发送,然后停止请求,并发送修改占位符的

<script>
标记。

https://gist.github.com/peat-psuwit/53a0b13dfda8781ac34cf6e66cc92ec0(编辑:也包含在下面)

在 Chrome 上,这显示了预期结果:在 HTTP 请求停止时显示文本“正在加载...”。然后,5 秒后,在同一请求中发送修改单词“Loading...”的脚本,以及单词“Hello, world!”显示。

但是,在 Firefox 上,它只是等待整个 HTTP 请求完成后再渲染。这意味着它只显示白色页面 5 秒钟,然后跳到“Hello, world!”状态。

我的理解是,正如我所说的

res.write()
,NodeJS 自动使用 HTTP 的分块传输功能将骨架、部分 HTML 代码首先传输到浏览器,从而允许看到“正在加载...”文本。我的问题是为什么它不适用于 Firefox?或者也许我应该做一些额外的事情来使它工作,并且它意外地在 Chrome 上工作?

这是 Ubuntu 22.04 上的 Chrome (Chromium) 124.0.6367.60 和 Firefox 124.0.2。


有问题的示例代码:

import { setTimeout } from "node:timers/promises";
import express from "express";

/* Simple HTML streaming concept, inspired by now NextJS do it, but without React.
 * TODO: figure out why in the world does Firefox not show the "Loading..." as soon
 * as it's received.
*/

async function getContent() {
  await setTimeout(5000);

  return {
    id: "main-content",
    innerHTML: "<h1>Hello, world!</h1>",
  };
}

const app = express();

app.get("/", async (req, res) => {
  res.status(200);

  // TODO: maybe de-indent.
  res.write(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>Hello, world</title>
      </head>
      <body>
        <div id="main-content">Loading...</div>
  `);

  let content = await getContent();

  res.write(`
        <script>
          document.getElementById(${JSON.stringify(content.id)}).innerHTML = ${JSON.stringify(content.innerHTML)};
        </script>
  `);

  res.write(`
      </body>
    </html>
  `);

  res.end();
});

console.log("http://localhost:3000");
app.listen(3000);
node.js http browser
1个回答
0
投票

回答我自己的问题,原来“附加的东西”是这一行:

res.header('Content-Type', 'text/html; charset=utf-8');

我的逻辑是,如果没有这个标头,Firefox 可能无法推断部分响应是 HTML 或它是什么字符集。因此,Firefox 可能会缓冲响应,直到它对内容类型或字符集有足够的信心。有了标头,Firefox 就不必进行猜测,并且可以立即开始将响应解析为 HTML。

我已将修复程序以及更多评论包含在新要点中here,并且还将在下面重现。


/*
 * Simple HTML streaming concept, inspired by how NextJS do it, but without
 * React.
 *
 * It works by initially sending a skeleton HTML to the browser, but,
 * importantly, this skeleton is unclosed, which allows server to stream in
 * more content. However, you cannot go back to edit the HTML content you've
 * already sent. Or can you???
 *
 * Turns out, if the additional content is a <script> tag which changes the
 * innerHTML of an existing node, then you _can_ change anything you've already
 * sent. This is essentially now Next.js can send stream additional stuff to the
 * page under the same connection without additional requests, however RSC
 * (React Server Component) payload is used instead of raw HTML.
 *
 * Have fun!
 * 
 * Author: Ratchanan Srirattanamet
 * SPDX-License-Identifier: CC0-1.0
 */

import { setTimeout } from "node:timers/promises";
import express from "express";

async function getContent() {
  // Simulates querying DB, doing formatting etc. (Exacerbated).
  await setTimeout(5000);

  return {
    id: "main-content",
    innerHTML: "<h1>Hello, world!</h1>",
  };
}

const app = express();

app.get("/", async (req, res) => {
  res.status(200);
  // Without this, browsers might not be able to infer from the partial response
  // that this is an HTML and might not start rendering.
  res.header('Content-Type', 'text/html; charset=utf-8');

  // The skeleton HTML.
  // TODO: maybe de-indent.
  res.write(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>Hello, world</title>
      </head>
      <body>
        <div id="main-content">Loading...</div>
  `);

  // Now let's generate the actual content.
  let content = await getContent();

  // And then send the JS to instruct the browser to plug the content into the
  // skeleton.
  res.write(`
        <script>
          document.getElementById(${JSON.stringify(content.id)}).innerHTML = ${JSON.stringify(content.innerHTML)};
        </script>
  `);

  // Now that everything is sent to the client. Be courteous and send the
  // closing tags to the client. This is probably not strictly needed, but why
  // not?
  res.write(`
      </body>
    </html>
  `);

  // And finally, closes the request-response cycle.
  res.end();
});

console.log("http://localhost:3000");
app.listen(3000);

© www.soinside.com 2019 - 2024. All rights reserved.