UNPKG

@tanstack/solid-router

Version:

Modern and scalable routing for Solid applications

1 lines 8.81 kB
{"version":3,"file":"renderRouterToStream.cjs","names":["Solid","isbot","createSsrStreamResponse","transformReadableStreamWithRouter","makeSsrSerovalPlugin","JSXElement","ReadableStream","AnyRouter","noop","waitForReadyOrAbort","ready","Promise","signal","AbortSignal","cleanup","race","resolve","onAbort","removeEventListener","addEventListener","once","aborted","renderRouterToStream","request","router","responseHeaders","children","Request","Headers","writable","readable","TransformStream","docType","ssr","serializationAdapters","options","serovalPlugins","map","adapter","plugin","didRun","stream","renderToStream","_$memo","nonce","plugins","innerWriter","getWriter","writerDone","releaseWriter","releaseLock","abortSolidPipe","reason","abort","catch","finally","onRequestAbort","serverSsr","onCleanup","headers","get","solidWritable","WritableStream","write","chunk","close","pipeTo","err","name","code","console","error","responseStream","Response","status","stores","statusCode"],"sources":["../../../src/ssr/renderRouterToStream.tsx"],"sourcesContent":["import * as Solid from 'solid-js/web'\nimport { isbot } from 'isbot'\nimport {\n createSsrStreamResponse,\n transformReadableStreamWithRouter,\n} from '@tanstack/router-core/ssr/server'\nimport { makeSsrSerovalPlugin } from '@tanstack/router-core'\nimport type { JSXElement } from 'solid-js'\nimport type { ReadableStream } from 'node:stream/web'\nimport type { AnyRouter } from '@tanstack/router-core'\n\nconst noop = () => {}\n\n// Bot responses wait for the server renderer before streaming. If the request\n// disconnects during that wait, unblock so the pipe can abort and clean up.\nasync function waitForReadyOrAbort(\n ready: Promise<unknown>,\n signal: AbortSignal,\n) {\n let cleanup = noop\n try {\n await Promise.race([\n ready,\n new Promise<void>((resolve) => {\n const onAbort = () => resolve()\n cleanup = () => signal.removeEventListener('abort', onAbort)\n signal.addEventListener('abort', onAbort, { once: true })\n if (signal.aborted) resolve()\n }),\n ])\n } finally {\n cleanup()\n }\n}\n\nexport const renderRouterToStream = async ({\n request,\n router,\n responseHeaders,\n children,\n}: {\n request: Request\n router: AnyRouter\n responseHeaders: Headers\n children: () => JSXElement\n}) => {\n const { writable, readable } = new TransformStream()\n\n const docType = Solid.ssr('<!DOCTYPE html>')\n\n const serializationAdapters =\n (router.options as any)?.serializationAdapters ||\n (router.options.ssr as any)?.serializationAdapters\n const serovalPlugins = serializationAdapters?.map((adapter: any) => {\n const plugin = makeSsrSerovalPlugin(adapter, { didRun: false })\n return plugin\n })\n\n const stream = Solid.renderToStream(\n () => (\n <>\n {docType}\n {children()}\n </>\n ),\n {\n nonce: router.options.ssr?.nonce,\n plugins: serovalPlugins,\n } as any,\n )\n\n // Solid's `pipeTo(w)` takes a single arg (no signal overload) and locks\n // `w` via `w.getWriter()`. To still own the lifecycle we hand Solid a\n // proxy WritableStream that forwards into an inner writer we control on\n // the real TransformStream writable. Aborting the inner writer errors\n // the underlying readable (which our router transform reads from),\n // surfacing the cancel through the response pipeline.\n //\n // RESIDUAL RISK: solid-js@1.x does NOT expose a disposal hook on\n // `renderToStream`, and its internal write loop swallows writer\n // rejections (`writer.write(...).catch(() => {})` in\n // solid-js/web/dist/server.js). So aborting the inner writer stops\n // outbound bytes but does not terminate Solid's render continuation\n // if a Suspense/resource never resolves — those pending promise\n // continuations remain scheduled and can retain children/context/\n // request references via captured closures until natural completion\n // or process exit. The request-scoped router graph itself is released\n // by ServerSsr.cleanup() through the router stream lifecycle, so the leak\n // is bounded to whatever the user's Suspense/resource closures capture.\n // A hard upstream-abort guarantee would require a disposal API in solid-js.\n const innerWriter = writable.getWriter()\n let writerDone = false\n const releaseWriter = () => {\n try {\n innerWriter.releaseLock()\n } catch {\n // already released / errored\n }\n }\n const abortSolidPipe = (reason?: unknown) => {\n if (writerDone) return\n writerDone = true\n void innerWriter\n .abort(reason)\n .catch(() => {})\n .finally(releaseWriter)\n }\n\n const onRequestAbort = () => {\n abortSolidPipe(request.signal.reason)\n }\n\n // Wire request abort before the bot all-ready wait. Otherwise a disconnect\n // during `await stream` can leave this callback pending forever.\n if (request.signal.aborted) {\n onRequestAbort()\n } else {\n request.signal.addEventListener('abort', onRequestAbort, { once: true })\n router.serverSsr?.onCleanup(() => {\n request.signal.removeEventListener('abort', onRequestAbort)\n })\n }\n\n if (isbot(request.headers.get('User-Agent'))) {\n await waitForReadyOrAbort(\n Promise.resolve(stream as unknown),\n request.signal,\n )\n }\n\n const solidWritable = new WritableStream({\n write(chunk) {\n return innerWriter.write(chunk)\n },\n close() {\n writerDone = true\n return innerWriter.close().finally(releaseWriter)\n },\n abort(reason) {\n writerDone = true\n return innerWriter.abort(reason).finally(releaseWriter)\n },\n })\n\n if (!request.signal.aborted) {\n try {\n void Promise.resolve(stream.pipeTo(solidWritable) as unknown).catch(\n (err: any) => {\n if (\n writerDone ||\n err?.name === 'AbortError' ||\n err?.code === 'ABORT_ERR'\n )\n return\n console.error('Error in Solid render stream:', err)\n abortSolidPipe(err)\n },\n )\n } catch (err: any) {\n if (err?.name !== 'AbortError' && err?.code !== 'ABORT_ERR') {\n console.error('Error in Solid render stream:', err)\n }\n abortSolidPipe(err)\n }\n }\n\n const responseStream = transformReadableStreamWithRouter(\n router,\n readable as unknown as ReadableStream,\n { onAbort: abortSolidPipe },\n )\n return createSsrStreamResponse(\n router,\n new Response(responseStream as any, {\n status: router.stores.statusCode.get(),\n headers: responseHeaders,\n }),\n )\n}\n"],"mappings":"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"}