UNPKG

@tanstack/router-core

Version:

Modern and scalable routing for React applications

1 lines 7.57 kB
{"version":3,"file":"createRequestHandler.cjs","names":[],"sources":["../../../src/ssr/createRequestHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport { _getRenderedMatches } from '../load-client'\nimport { mergeHeaders } from './headers'\nimport {\n attachRouterServerSsrUtils,\n getNormalizedURL,\n getOrigin,\n} from './ssr-server'\nimport {\n bindSsrResponseToRequest,\n disposeSsrResponseDetached,\n} from './handlerCallback'\nimport type { HandlerCallback } from './handlerCallback'\nimport type { AnyHeaders } from './headers'\nimport type { AnyRouter } from '../router'\nimport type { ServerManifest } from '../manifest'\n\nexport type RequestHandler<TRouter extends AnyRouter> = (\n cb: HandlerCallback<TRouter>,\n) => Promise<Response>\n\ntype RequestWaiter = ((reason: unknown) => void) | undefined\n\nconst requestWaiters = new WeakMap<AbortSignal, Array<RequestWaiter>>()\n\nfunction removeRequestWaiter(\n waiters: Array<RequestWaiter>,\n index: number,\n reject: (reason: unknown) => void,\n) {\n if (waiters[index] !== reject) {\n return\n }\n if (index !== waiters.length - 1) {\n waiters[index] = undefined\n return\n }\n\n waiters.pop()\n while (waiters.length && waiters[waiters.length - 1] === undefined) {\n waiters.pop()\n }\n}\n\nexport function waitForRequest<T>(\n value: T | PromiseLike<T>,\n signal: AbortSignal,\n onLate?: (value: T) => void,\n): Promise<T> {\n const promise = Promise.resolve(value)\n if (signal.aborted) {\n void promise.then(onLate, () => {})\n return Promise.reject(signal.reason)\n }\n\n return new Promise<T>((resolve, reject) => {\n let waiters = requestWaiters.get(signal)\n let index: number\n if (waiters) {\n index = waiters.push(reject) - 1\n } else {\n const newWaiters: Array<RequestWaiter> = [reject]\n waiters = newWaiters\n index = 0\n requestWaiters.set(signal, newWaiters)\n signal.addEventListener(\n 'abort',\n () => {\n requestWaiters.delete(signal)\n for (const rejectWaiter of newWaiters) {\n rejectWaiter?.(signal.reason)\n }\n newWaiters.length = 0\n },\n { once: true },\n )\n }\n void promise.then(\n (result) => {\n removeRequestWaiter(waiters, index, reject)\n if (signal.aborted) {\n onLate?.(result)\n } else {\n resolve(result)\n }\n },\n (error) => {\n removeRequestWaiter(waiters, index, reject)\n reject(error)\n },\n )\n })\n}\n\nexport function createRequestHandler<TRouter extends AnyRouter>({\n createRouter,\n request,\n getRouterManifest,\n}: {\n createRouter: () => TRouter\n request: Request\n getRouterManifest?: () => ServerManifest | Promise<ServerManifest>\n}): RequestHandler<TRouter> {\n return async (cb) => {\n request.signal.throwIfAborted()\n const router = createRouter()\n let responseOwnsCleanup = false\n\n try {\n attachRouterServerSsrUtils({\n router,\n manifest: await waitForRequest(getRouterManifest?.(), request.signal),\n })\n\n // normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR.\n const { url } = getNormalizedURL(request.url, 'http://localhost')\n const origin = getOrigin(request)\n const href = url.href.replace(url.origin, '')\n\n // Create a history for the router\n const history = createMemoryHistory({\n initialEntries: [href],\n })\n\n // Update the router with the history and context\n router.update({\n history,\n origin: router.options.origin ?? origin,\n })\n\n await router.load({\n _signal: request.signal,\n })\n request.signal.throwIfAborted()\n\n const result = router._serverResult\n if (result?.type === 'redirect') {\n return result.redirect\n }\n\n await waitForRequest(router.serverSsr?.dehydrate(), request.signal)\n request.signal.throwIfAborted()\n\n const responseHeaders = getRequestHeaders({\n router,\n })\n\n request.signal.throwIfAborted()\n const response = await waitForRequest(\n cb({\n request,\n router,\n responseHeaders,\n }),\n request.signal,\n (late) => {\n disposeSsrResponseDetached(late, request.signal.reason)\n },\n )\n const ssrResponse = bindSsrResponseToRequest(\n router,\n response,\n request.signal,\n )\n request.signal.throwIfAborted()\n responseOwnsCleanup = ssrResponse.serverSsrCleanup === 'stream'\n return ssrResponse.response\n } finally {\n if (!responseOwnsCleanup) {\n // Clean up router SSR state if the callback won't handle it\n // (e.g., if an error occurred before the callback was invoked).\n // Transformed streaming response bodies clean up when consumed/cancelled.\n router.serverSsr?.cleanup()\n }\n }\n }\n}\n\nfunction getRequestHeaders(opts: { router: AnyRouter }): Headers {\n const matchHeaders: Array<AnyHeaders> = []\n for (const match of _getRenderedMatches(opts.router.stores.matches.get())) {\n matchHeaders.push(match.headers)\n }\n\n return mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=UTF-8',\n },\n ...matchHeaders,\n )\n}\n"],"mappings":"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"}