Buckets:
| // src/jsx/streaming.ts | |
| import { raw } from "../helper/html/index.js"; | |
| import { HtmlEscapedCallbackPhase, resolveCallback } from "../utils/html.js"; | |
| import { JSXNode } from "./base.js"; | |
| import { childrenToString } from "./components.js"; | |
| import { DOM_RENDERER, DOM_STASH } from "./constants.js"; | |
| import { createContext, useContext } from "./context.js"; | |
| import { Suspense as SuspenseDomRenderer } from "./dom/components.js"; | |
| import { buildDataStack } from "./dom/render.js"; | |
| var StreamingContext = createContext(null); | |
| var suspenseCounter = 0; | |
| var Suspense = async ({ | |
| children, | |
| fallback | |
| }) => { | |
| if (!Array.isArray(children)) { | |
| children = [children]; | |
| } | |
| const nonce = useContext(StreamingContext)?.scriptNonce; | |
| let resArray = []; | |
| const stackNode = { [DOM_STASH]: [0, []] }; | |
| const popNodeStack = (value) => { | |
| buildDataStack.pop(); | |
| return value; | |
| }; | |
| try { | |
| stackNode[DOM_STASH][0] = 0; | |
| buildDataStack.push([[], stackNode]); | |
| resArray = children.map( | |
| (c) => c == null || typeof c === "boolean" ? "" : c.toString() | |
| ); | |
| } catch (e) { | |
| if (e instanceof Promise) { | |
| resArray = [ | |
| e.then(() => { | |
| stackNode[DOM_STASH][0] = 0; | |
| buildDataStack.push([[], stackNode]); | |
| return childrenToString(children).then(popNodeStack); | |
| }) | |
| ]; | |
| } else { | |
| throw e; | |
| } | |
| } finally { | |
| popNodeStack(); | |
| } | |
| if (resArray.some((res) => res instanceof Promise)) { | |
| const index = suspenseCounter++; | |
| const fallbackStr = await fallback.toString(); | |
| return raw(`<template id="H:${index}"></template>${fallbackStr}<!--/$-->`, [ | |
| ...fallbackStr.callbacks || [], | |
| ({ phase, buffer, context }) => { | |
| if (phase === HtmlEscapedCallbackPhase.BeforeStream) { | |
| return; | |
| } | |
| return Promise.all(resArray).then(async (htmlArray) => { | |
| htmlArray = htmlArray.flat(); | |
| const content = htmlArray.join(""); | |
| if (buffer) { | |
| buffer[0] = buffer[0].replace( | |
| new RegExp(`<template id="H:${index}"></template>.*?<!--/\\$-->`), | |
| content | |
| ); | |
| } | |
| let html = buffer ? "" : `<template data-hono-target="H:${index}">${content}</template><script${nonce ? ` nonce="${nonce}"` : ""}> | |
| ((d,c,n) => { | |
| c=d.currentScript.previousSibling | |
| d=d.getElementById('H:${index}') | |
| if(!d)return | |
| do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$') | |
| d.replaceWith(c.content) | |
| })(document) | |
| </script>`; | |
| const callbacks = htmlArray.map((html2) => html2.callbacks || []).flat(); | |
| if (!callbacks.length) { | |
| return html; | |
| } | |
| if (phase === HtmlEscapedCallbackPhase.Stream) { | |
| html = await resolveCallback(html, HtmlEscapedCallbackPhase.BeforeStream, true, context); | |
| } | |
| return raw(html, callbacks); | |
| }); | |
| } | |
| ]); | |
| } else { | |
| return raw(resArray.join("")); | |
| } | |
| }; | |
| Suspense[DOM_RENDERER] = SuspenseDomRenderer; | |
| var textEncoder = new TextEncoder(); | |
| var renderToReadableStream = (content, onError = console.trace) => { | |
| const reader = new ReadableStream({ | |
| async start(controller) { | |
| try { | |
| if (content instanceof JSXNode) { | |
| content = content.toString(); | |
| } | |
| const context = typeof content === "object" ? content : {}; | |
| const resolved = await resolveCallback( | |
| content, | |
| HtmlEscapedCallbackPhase.BeforeStream, | |
| true, | |
| context | |
| ); | |
| controller.enqueue(textEncoder.encode(resolved)); | |
| let resolvedCount = 0; | |
| const callbacks = []; | |
| const then = (promise) => { | |
| callbacks.push( | |
| promise.catch((err) => { | |
| console.log(err); | |
| onError(err); | |
| return ""; | |
| }).then(async (res) => { | |
| res = await resolveCallback( | |
| res, | |
| HtmlEscapedCallbackPhase.BeforeStream, | |
| true, | |
| context | |
| ); | |
| res.callbacks?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context })).filter(Boolean).forEach(then); | |
| resolvedCount++; | |
| controller.enqueue(textEncoder.encode(res)); | |
| }) | |
| ); | |
| }; | |
| resolved.callbacks?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context })).filter(Boolean).forEach(then); | |
| while (resolvedCount !== callbacks.length) { | |
| await Promise.all(callbacks); | |
| } | |
| } catch (e) { | |
| onError(e); | |
| } | |
| controller.close(); | |
| } | |
| }); | |
| return reader; | |
| }; | |
| export { | |
| StreamingContext, | |
| Suspense, | |
| renderToReadableStream | |
| }; | |
Xet Storage Details
- Size:
- 4.68 kB
- Xet hash:
- 8a936f45d977ec32715f3ab2154642d185e45909553b5516a9bf58453ccf65f2
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.