rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
199 lines (198 loc) • 10.1 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { normalizeActionResult } from "./render/normalizeActionResult";
import { renderDocumentHtmlStream } from "./render/renderDocumentHtmlStream";
import { renderToRscStream } from "./render/renderToRscStream";
import { injectRSCPayload } from "rsc-html-stream/server";
import { ErrorResponse } from "./error";
import { rscActionHandler } from "./register/worker";
import { getRequestInfo, runWithRequestInfo, runWithRequestInfoOverrides, } from "./requestInfo/worker";
import { ssrWebpackRequire } from "./imports/worker";
import { defineRoutes } from "./lib/router";
import { generateNonce } from "./lib/utils";
export * from "./requestInfo/types";
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 (import.meta.env.VITE_IS_DEV_SERVER &&
new URL(request.url).pathname === "/__worker-run") {
const url = new URL(request.url);
const scriptPath = url.searchParams.get("script");
if (!scriptPath) {
return new Response("Missing 'script' query parameter", {
status: 400,
});
}
try {
const scriptModule = await import(/* @vite-ignore */ scriptPath);
if (scriptModule.default) {
await scriptModule.default(request, env, cf);
}
return new Response("Script executed successfully");
}
catch (e) {
console.error(`Error executing script: ${scriptPath}\n\n${e.stack}`);
return new Response(`Error executing script: ${e.message}`, {
status: 500,
});
}
}
else if (import.meta.env.VITE_IS_DEV_SERVER &&
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") ||
request.headers.get("accept")?.includes("text/x-component");
const isAction = url.searchParams.has("__rsc_action_id");
const rw = {
Document: DefaultDocument,
nonce: generateNonce(),
rscPayload: true,
ssr: true,
databases: new Map(),
scriptsToBeLoaded: new Set(),
entryScripts: new Set(),
inlineScripts: new Set(),
pageRouteResolved: undefined,
};
const userResponseInit = {
status: 200,
headers: new Headers(),
};
const outerRequestInfo = {
request,
cf,
params: {},
ctx: {},
rw,
response: userResponseInit,
isAction,
};
const createPageElement = (requestInfo, Page) => {
let pageElement;
if (isClientReference(Page)) {
const { ctx, params } = requestInfo; // 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.
pageElement = _jsx(Page, { ctx: ctx, params: params });
}
else {
pageElement = _jsx(Page, { ...requestInfo });
}
return pageElement;
};
const renderPage = async (requestInfo, Page, onError) => {
if (isClientReference(requestInfo.rw.Document)) {
if (import.meta.env.DEV) {
console.error("Document cannot be a client component");
}
return new Response(null, {
status: 500,
});
}
const actionResult = normalizeActionResult(requestInfo.rw.actionResult);
const pageElement = createPageElement(requestInfo, Page);
const { rscPayload: shouldInjectRSCPayload } = rw;
let rscPayloadStream = renderToRscStream({
input: {
node: pageElement,
actionResult,
},
onError,
});
if (isRSCRequest) {
const responseHeaders = new Headers(userResponseInit.headers);
responseHeaders.set("content-type", "text/x-component; charset=utf-8");
return new Response(rscPayloadStream, {
status: userResponseInit.status,
statusText: userResponseInit.statusText,
headers: responseHeaders,
});
}
let injectRSCPayloadStream;
if (shouldInjectRSCPayload) {
const [rscPayloadStream1, rscPayloadStream2] = rscPayloadStream.tee();
rscPayloadStream = rscPayloadStream1;
injectRSCPayloadStream = injectRSCPayload(rscPayloadStream2, {
nonce: rw.nonce,
});
}
let html = await renderDocumentHtmlStream({
rscPayloadStream: rscPayloadStream,
Document: rw.Document,
requestInfo: requestInfo,
onError,
shouldSSR: rw.ssr,
});
if (injectRSCPayloadStream) {
html = html.pipeThrough(injectRSCPayloadStream);
}
const responseHeaders = new Headers(userResponseInit.headers);
responseHeaders.set("content-type", "text/html; charset=utf-8");
return new Response(html, {
status: userResponseInit.status,
statusText: userResponseInit.statusText,
headers: responseHeaders,
});
};
const response = await runWithRequestInfo(outerRequestInfo, async () => new Promise(async (resolve, reject) => {
try {
resolve(await router.handle({
request,
renderPage,
getRequestInfo: getRequestInfo,
runWithRequestInfoOverrides,
onError: reject,
rscActionHandler,
}));
}
catch (e) {
reject(e);
}
}));
// 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);
// Merge headers from user response init (these take precedence)
if (userResponseInit.headers) {
const userResponseHeaders = new Headers(userResponseInit.headers);
for (const [key, value] of userResponseHeaders.entries()) {
if (!response.headers.has(key)) {
mutableResponse.headers.set(key, value);
}
}
}
await rw.pageRouteResolved?.promise;
return mutableResponse;
}
catch (e) {
if (e instanceof ErrorResponse) {
return new Response(e.message, { status: e.code });
}
if (e instanceof Response) {
return e;
}
console.error("rwsdk: Received an unhandled error:\n\n%s", 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 }) })] }));
const isClientReference = (Component) => {
return Object.prototype.hasOwnProperty.call(Component, "$$isClientReference");
};