Created
April 23, 2024 17:46
-
-
Save peat-psuwit/549c4c70473f9aab9214ec2b8c87a351 to your computer and use it in GitHub Desktop.
streamingHTML, v2 (now works with Firefox)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment