UNPKG

@redwoodjs/sdk

Version:

A full-stack webapp toolkit designed for TypeScript, Vite, and React Server Components

112 lines (111 loc) 6.18 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { transformRscToHtmlStream } from "./render/transformRscToHtmlStream"; import { injectRSCPayload } from "./render/injectRSCPayload"; import { renderToRscStream } from "./render/renderToRscStream"; import { ssrWebpackRequire } from "./imports/worker"; import { rscActionHandler } from "./register/worker"; import { ErrorResponse } from "./error"; import { defineRoutes, } from "./lib/router"; import { generateNonce } from "./lib/utils"; import { IS_DEV } from "./constants"; export const defineApp = (routes) => { return { fetch: async (request, env, cf) => { globalThis.__webpack_require__ = ssrWebpackRequire; const router = defineRoutes(routes); // context(justinvdm, 5 Feb 2025): Serve assets requests using the assets service binding // todo(justinvdm, 5 Feb 2025): Find a way to avoid this so asset requests are served directly // rather than first needing to go through the worker if (request.url.includes("/assets/")) { const url = new URL(request.url); url.pathname = url.pathname.slice("/assets/".length); return env.ASSETS.fetch(new Request(url.toString(), request)); } else if (IS_DEV && request.url.includes("/__vite_preamble__")) { return new Response('import RefreshRuntime from "/@react-refresh"; RefreshRuntime.injectIntoGlobalHook(window); window.$RefreshReg$ = () => {}; window.$RefreshSig$ = () => (type) => type; window.__vite_plugin_react_preamble_installed__ = true;', { headers: { "content-type": "text/javascript", }, }); } try { const url = new URL(request.url); const isRSCRequest = url.searchParams.has("__rsc"); const handleAction = async (opts) => { const isRSCActionHandler = url.searchParams.has("__rsc_action_id"); if (isRSCActionHandler) { return await rscActionHandler(request, opts); // maybe we should include params and appContext in the action handler? } }; const renderPage = async ({ Page, props: fullPageProps, actionResult, Document, }) => { let props = fullPageProps; let documentProps = fullPageProps; // context(justinvdm, 25 Feb 2025): If the page is a client reference, we need to avoid passing // down props the client shouldn't get (e.g. env). For safety, we pick the allowed props explicitly. if (Object.prototype.hasOwnProperty.call(Page, "$$isClientReference")) { const { appContext, params } = fullPageProps; props = { appContext, params }; } if (Object.prototype.hasOwnProperty.call(Document, "$$isClientReference")) { const { appContext, params } = fullPageProps; documentProps = { appContext, params }; } const nonce = fullPageProps.rw.nonce; const rscPayloadStream = renderToRscStream({ node: _jsx(Page, { ...props }), actionResult: actionResult instanceof Response ? null : actionResult, }); if (isRSCRequest) { return new Response(rscPayloadStream, { headers: { "content-type": "text/x-component; charset=utf-8", }, }); } const [rscPayloadStream1, rscPayloadStream2] = rscPayloadStream.tee(); const htmlStream = await transformRscToHtmlStream({ stream: rscPayloadStream1, Parent: ({ children }) => (_jsx(Document, { ...documentProps, children: children })), }); const html = htmlStream.pipeThrough(injectRSCPayload(rscPayloadStream2, { nonce })); return new Response(html, { headers: { "content-type": "text/html; charset=utf-8", }, }); }; const userHeaders = new Headers(); const response = await router.handle({ cf, request, headers: userHeaders, appContext: {}, env, rw: { Document: DefaultDocument, handleAction, renderPage, nonce: generateNonce(), }, }); // context(justinvdm, 18 Mar 2025): In some cases, such as a .fetch() call to a durable object instance, or Response.redirect(), // we need to return a mutable response object. const mutableResponse = new Response(response.body, response); for (const [key, value] of userHeaders.entries()) { if (!response.headers.has(key)) { mutableResponse.headers.set(key, value); } } return mutableResponse; } catch (e) { if (e instanceof ErrorResponse) { return new Response(e.message, { status: e.code }); } console.error("Unhandled error", e); throw e; } }, }; }; export const DefaultDocument = ({ children, }) => (_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" })] }), _jsx("body", { children: _jsx("div", { id: "root", children: children }) })] }));