| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- // 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) => {
- let cancelled = false;
- 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
- );
- if (!cancelled) {
- 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++;
- if (!cancelled) {
- 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);
- }
- if (!cancelled) {
- controller.close();
- }
- },
- cancel() {
- cancelled = true;
- }
- });
- return reader;
- };
- export {
- StreamingContext,
- Suspense,
- renderToReadableStream
- };
|