@tanstack/react-router
Version:
Modern and scalable routing for React applications
106 lines (105 loc) • 4.54 kB
JavaScript
import { createSsrStreamResponse, transformPipeableStreamWithRouter, transformReadableStreamWithRouter } from "@tanstack/router-core/ssr/server";
import ReactDOMServer from "react-dom/server";
import { PassThrough } from "node:stream";
import { isbot } from "isbot";
//#region src/ssr/renderRouterToStream.tsx
var noop = () => {};
async function waitForReadyOrAbort(ready, signal) {
let cleanup = noop;
try {
await Promise.race([ready, new Promise((resolve) => {
const onAbort = () => resolve();
cleanup = () => signal.removeEventListener("abort", onAbort);
signal.addEventListener("abort", onAbort, { once: true });
if (signal.aborted) resolve();
})]);
} finally {
cleanup();
}
}
var isAbortError = (request, error) => request.signal.aborted && error === request.signal.reason || error instanceof Error && error.name === "AbortError";
var renderRouterToStream = async ({ request, router, responseHeaders, children }) => {
if (typeof ReactDOMServer.renderToReadableStream === "function") {
const stream = await ReactDOMServer.renderToReadableStream(children, {
signal: request.signal,
nonce: router.options.ssr?.nonce,
progressiveChunkSize: Number.POSITIVE_INFINITY,
onError: (error, info) => {
if (!isAbortError(request, error)) console.error("Error in renderToReadableStream:", error, info);
}
});
if (isbot(request.headers.get("User-Agent"))) await waitForReadyOrAbort(stream.allReady, request.signal);
const responseStream = transformReadableStreamWithRouter(router, stream, { onAbort: () => stream.cancel().catch(() => {}) });
return createSsrStreamResponse(router, new Response(responseStream, {
status: router.stores.statusCode.get(),
headers: responseHeaders
}));
}
if (typeof ReactDOMServer.renderToPipeableStream === "function") {
const reactAppPassthrough = new PassThrough();
let pipeable;
let responseAttached = false;
let aborted = false;
let endedBeforeAttach = false;
let pendingAbortReason;
const toError = (reason) => reason instanceof Error ? reason : new Error(String(reason ?? "SSR aborted"));
const destroyError = (reason) => reason === void 0 ? void 0 : toError(reason);
const pendingDestroyError = () => pendingAbortReason === void 0 ? toError(pendingAbortReason) : destroyError(pendingAbortReason);
const finishPassThrough = (reason, opts) => {
if (reactAppPassthrough.destroyed) return;
if (responseAttached) reactAppPassthrough.destroy(opts?.defaultError ? toError(reason) : destroyError(reason));
else endedBeforeAttach = true;
};
const abortPipeable = (reason, opts) => {
if (aborted) return;
aborted = true;
pendingAbortReason = reason;
const err = toError(reason);
try {
pipeable?.abort(err);
} catch {}
finishPassThrough(reason, opts);
};
if (request.signal.aborted) abortPipeable(request.signal.reason);
else {
const onRequestAbort = () => abortPipeable(request.signal.reason);
request.signal.addEventListener("abort", onRequestAbort, { once: true });
router.serverSsr?.onCleanup(() => {
request.signal.removeEventListener("abort", onRequestAbort);
});
}
try {
pipeable = ReactDOMServer.renderToPipeableStream(children, {
nonce: router.options.ssr?.nonce,
progressiveChunkSize: Number.POSITIVE_INFINITY,
...isbot(request.headers.get("User-Agent")) ? { onAllReady() {
pipeable.pipe(reactAppPassthrough);
} } : { onShellReady() {
pipeable.pipe(reactAppPassthrough);
} },
onError: (error, info) => {
if (!isAbortError(request, error)) console.error("Error in renderToPipeableStream:", error, info);
abortPipeable(error, { defaultError: true });
}
});
} catch (e) {
console.error("Error in renderToPipeableStream:", e);
router.serverSsr?.cleanup();
throw e;
}
const responseStream = transformPipeableStreamWithRouter(router, reactAppPassthrough, { onAbort: abortPipeable });
responseAttached = true;
if (endedBeforeAttach) reactAppPassthrough.destroy(pendingDestroyError());
if (aborted && pipeable) try {
pipeable.abort(toError(pendingAbortReason));
} catch {}
return createSsrStreamResponse(router, new Response(responseStream, {
status: router.stores.statusCode.get(),
headers: responseHeaders
}));
}
throw new Error("No renderToReadableStream or renderToPipeableStream found in react-dom/server. Ensure you are using a version of react-dom that supports streaming.");
};
//#endregion
export { renderRouterToStream };
//# sourceMappingURL=renderRouterToStream.js.map