@redwoodjs/sdk
Version:
A full-stack webapp toolkit designed for TypeScript, Vite, and React Server Components
112 lines (111 loc) • 6.18 kB
JavaScript
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 }) })] }));