Last active
June 20, 2025 20:12
-
-
Save patheticGeek/9dc0a2e8dba6bcd770b2f98d393c541d to your computer and use it in GitHub Desktop.
streaming-by-hand
This file contains hidden or 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
| { | |
| "name": "streaming-by-hand", | |
| "version": "1.0.0", | |
| "description": "", | |
| "type": "module", | |
| "main": "streaming-by-hand-extras.js", | |
| "scripts": { | |
| "start": "nodemon streaming-by-hand-extras.js" | |
| }, | |
| "keywords": [], | |
| "author": "", | |
| "license": "ISC", | |
| "dependencies": { | |
| "express": "^4.18.3", | |
| "morgan": "^1.10.0", | |
| "nanoid": "^5.0.6", | |
| "nodemon": "^3.1.0" | |
| } | |
| } |
This file contains hidden or 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
| import express from "express"; | |
| import morgan from "morgan"; | |
| import { nanoid } from "nanoid"; | |
| import { Readable } from "stream"; | |
| const app = express(); | |
| const port = 3000; | |
| app.use(morgan("combined")); | |
| const h = (component, children = [], props = {}) => { | |
| return { | |
| component, | |
| children: Array.isArray(children) ? children : [children], | |
| props, | |
| }; | |
| }; | |
| const attribAlias = { className: "class" }; | |
| const getAlias = (key) => attribAlias[key] || key; | |
| const selfClosing = { | |
| br: true, | |
| input: true, | |
| }; | |
| const renderTag = (tag, props = {}) => { | |
| if (!tag) return ""; | |
| const attrib = Object.entries(props) | |
| .map(([k, v]) => `${getAlias(k)}="${v}"`) | |
| .join(" "); | |
| if (selfClosing[tag]) return `<${tag}${attrib ? ` ${attrib}` : ""} />`; | |
| return { start: `<${tag}${attrib ? ` ${attrib}` : ""}>`, end: `</${tag}>` }; | |
| }; | |
| const renderTree = (readable, item, promises) => { | |
| // console.log("renderTree item", item); | |
| if (item === undefined || item === null) { | |
| return; | |
| } else if (["string", "number", "boolean"].includes(typeof item)) { | |
| if (!readable) return item; | |
| readable.push(item); | |
| } else if (typeof item === "object") { | |
| // weird js - we can destructure item even if it is a string | |
| const { component, children, props } = item; | |
| if (typeof component === "string") { | |
| const result = renderTag(component, props); | |
| if (typeof result === "string") { | |
| if (!readable) return result; | |
| else readable.push(result); | |
| } | |
| if (!readable) { | |
| return `${result.start}${children.map((child) => | |
| renderTree(readable, child, promises) | |
| )}${result.end}`; | |
| } else { | |
| readable.push(result.start); | |
| children.forEach((child) => renderTree(readable, child, promises)); | |
| readable.push(result.end); | |
| } | |
| } else if (typeof component === "function") { | |
| const result = component({ ...props, children }); | |
| if (result instanceof Promise) { | |
| const id = `${component.name}-${nanoid()}`; | |
| promises.push({ result, id }); | |
| renderTree(readable, h("template", "", { id })); | |
| } else { | |
| renderTree(readable, result, promises); | |
| } | |
| } | |
| } | |
| }; | |
| const getRedirectMeta = (url) => { | |
| return h("meta", undefined, { | |
| "http-equiv": "refresh", | |
| content: `0; url = ${url}`, | |
| }); | |
| }; | |
| const getReplacer = (id, rendered) => { | |
| return renderTree( | |
| undefined, | |
| h( | |
| "script", | |
| `document.querySelector("#${id}").insertAdjacentHTML("afterEnd", "${rendered}");document.querySelector("#${id}").remove()` | |
| ) | |
| ); | |
| }; | |
| class NotFoundError extends Error { | |
| url; | |
| constructor({ message, url }) { | |
| super(message); | |
| this.url = url; | |
| } | |
| } | |
| const notFound = ({ message, url } = {}) => { | |
| return new NotFoundError({ message, url }); | |
| }; | |
| const render = async (response, tree) => { | |
| const readable = new Readable({ | |
| read() { | |
| // lie, keep telling there is more data | |
| return true; | |
| }, | |
| }); | |
| readable.pipe(response); | |
| const promises = []; | |
| renderTree(readable, tree, promises); | |
| const toWait = []; | |
| for (const promise of promises) { | |
| const wait = promise.result | |
| .then((value) => { | |
| const result = renderTree(undefined, value); | |
| const rendered = renderTree(undefined, result); | |
| readable.push(getReplacer(promise.id, rendered)); | |
| }) | |
| .catch((err) => { | |
| if (err instanceof NotFoundError) { | |
| readable.push(getRedirectMeta(err.url || "/404")); | |
| } else { | |
| readable.push(getRedirectMeta(err.url || "/500")); | |
| } | |
| }); | |
| toWait.push(wait); | |
| } | |
| await Promise.all(toWait); | |
| // end the response | |
| readable.push(null); | |
| }; | |
| function Button({ value }) { | |
| return h("button", value); | |
| } | |
| async function AwaitMe({ wait = 1 }) { | |
| await new Promise((resolve) => setTimeout(resolve, wait * 1000)); | |
| if (Math.random() > 0.5) throw notFound(); | |
| return h("p", `i came after ${wait}s`); | |
| } | |
| function App() { | |
| return h( | |
| "main", | |
| [ | |
| h("h1", "Hello world"), | |
| h(AwaitMe, undefined, { wait: 5 }), | |
| h(Button, null, { value: "Click Me" }), | |
| h(AwaitMe), | |
| h("br"), | |
| h("p", "hi"), | |
| ], | |
| { className: "bg-blue" } | |
| ); | |
| } | |
| app.get("/", async (req, res) => { | |
| res.setHeader("Content-Type", "text/html"); | |
| await render(res, h(App)); | |
| }); | |
| app.get("404", async (req, res) => { | |
| await render(res, h("main", "Not found")); | |
| }); | |
| app.get("500", async (req, res) => { | |
| await render(res, h("main", "Server error")); | |
| }); | |
| app.listen(port, () => { | |
| console.log(`Example app listening on port ${port}`); | |
| }); |
This file contains hidden or 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
| import express from "express"; | |
| import morgan from "morgan"; | |
| import { nanoid } from "nanoid"; | |
| import { Readable } from "stream"; | |
| const app = express(); | |
| const port = 3000; | |
| app.use(morgan("combined")); | |
| const h = (component, children = [], props = {}) => { | |
| return { | |
| component, | |
| children: Array.isArray(children) ? children : [children], | |
| props, | |
| }; | |
| }; | |
| const attribAlias = { className: "class" }; | |
| const getAlias = (key) => attribAlias[key] || key; | |
| const selfClosing = { | |
| br: true, | |
| input: true, | |
| }; | |
| const renderTag = (tag, props = {}) => { | |
| if (!tag) return ""; | |
| const attrib = Object.entries(props) | |
| .map(([k, v]) => `${getAlias(k)}="${v}"`) | |
| .join(" "); | |
| if (selfClosing[tag]) return `<${tag}${attrib ? ` ${attrib}` : ""} />`; | |
| return { start: `<${tag}${attrib ? ` ${attrib}` : ""}>`, end: `</${tag}>` }; | |
| }; | |
| const renderTree = (readable, item, promises) => { | |
| // console.log("renderTree item", item); | |
| if (item === undefined || item === null) { | |
| return; | |
| } else if (["string", "number", "boolean"].includes(typeof item)) { | |
| if (!readable) return item; | |
| readable.push(item); | |
| } else if (typeof item === "object") { | |
| // weird js - we can destructure item even if it is a string | |
| const { component, children, props } = item; | |
| if (typeof component === "string") { | |
| const result = renderTag(component, props); | |
| if (typeof result === "string") { | |
| if (!readable) return result; | |
| else readable.push(result); | |
| } | |
| if (!readable) { | |
| return `${result.start}${children.map((child) => | |
| renderTree(readable, child, promises) | |
| )}${result.end}`; | |
| } else { | |
| readable.push(result.start); | |
| children.forEach((child) => renderTree(readable, child, promises)); | |
| readable.push(result.end); | |
| } | |
| } else if (typeof component === "function") { | |
| const result = component({ ...props, children }); | |
| if (result instanceof Promise) { | |
| const id = `${component.name}-${nanoid()}`; | |
| promises.push({ result, id }); | |
| renderTree(readable, h("template", "", { id })); | |
| } else { | |
| renderTree(readable, result, promises); | |
| } | |
| } | |
| } | |
| }; | |
| const getReplacer = (id, rendered) => { | |
| return renderTree( | |
| undefined, | |
| h( | |
| "script", | |
| `document.querySelector("#${id}").insertAdjacentHTML("afterEnd", "${rendered}");document.querySelector("#${id}").remove()` | |
| ) | |
| ); | |
| }; | |
| const render = async (readable, tree) => { | |
| if (!tree) throw new Error("Pass a component to render"); | |
| if (!readable) throw new Error("Pass an readable to output to"); | |
| const promises = []; | |
| renderTree(readable, tree, promises); | |
| console.log("renderTree done", promises); | |
| const toWait = []; | |
| for (const promise of promises) { | |
| const wait = promise.result.then((value) => { | |
| const result = renderTree(undefined, value); | |
| const rendered = renderTree(undefined, result); | |
| readable.push(getReplacer(promise.id, rendered)); | |
| }); | |
| toWait.push(wait); | |
| } | |
| return Promise.all(toWait); | |
| }; | |
| function Button({ value }) { | |
| return h("button", value); | |
| } | |
| async function AwaitMe({ wait = 1 }) { | |
| await new Promise((resolve) => setTimeout(resolve, wait * 1000)); | |
| return h("p", `i came after ${wait}s`); | |
| } | |
| function App() { | |
| return h( | |
| "div", | |
| [ | |
| h("h1", "Hello world"), | |
| h(AwaitMe, undefined, { wait: 5 }), | |
| h(Button, null, { value: "Click Me" }), | |
| h(AwaitMe), | |
| h("br"), | |
| h("p", "hi"), | |
| ], | |
| { className: "bg-blue" } | |
| ); | |
| } | |
| app.get("/", async (req, res) => { | |
| const readable = new Readable({ | |
| read() { | |
| // lie, keep telling there is more data | |
| return true; | |
| }, | |
| }); | |
| readable.pipe(res); | |
| await render(readable, h(App)); | |
| // end the response | |
| readable.push(null); | |
| }); | |
| app.listen(port, () => { | |
| console.log(`Example app listening on port ${port}`); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment