UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

103 lines (102 loc) 4.32 kB
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; // note(justinvdm, 14 Aug 2025): Rendering related imports and logic go here. // See client.tsx for the actual client entrypoint. // context(justinvdm, 14 Aug 2025): `react-server-dom-webpack` uses this global // to load modules, so we need to define it here before importing // "react-server-dom-webpack." // prettier-ignore import "./setWebpackRequire"; import React from "react"; import { hydrateRoot } from "react-dom/client"; import { createFromFetch, createFromReadableStream, encodeReply, } from "react-server-dom-webpack/client.browser"; import { rscStream } from "rsc-html-stream/client"; export { default as React } from "react"; export { ClientOnly } from "./ClientOnly.js"; export { initClientNavigation, navigate } from "./navigation.js"; export const fetchTransport = (transportContext) => { const fetchCallServer = async (id, args) => { const url = new URL(window.location.href); url.searchParams.set("__rsc", ""); if (id != null) { url.searchParams.set("__rsc_action_id", id); } const fetchPromise = fetch(url, { method: "POST", redirect: "manual", body: args != null ? await encodeReply(args) : null, }); // If there's a response handler, check the response first if (transportContext.handleResponse) { const response = await fetchPromise; const shouldContinue = transportContext.handleResponse(response); if (!shouldContinue) { return; } // Continue with the response if handler returned true const streamData = createFromFetch(Promise.resolve(response), { callServer: fetchCallServer, }); transportContext.setRscPayload(streamData); const result = await streamData; return result.actionResult; } // Original behavior when no handler is present const streamData = createFromFetch(fetchPromise, { callServer: fetchCallServer, }); transportContext.setRscPayload(streamData); const result = await streamData; return result.actionResult; }; return fetchCallServer; }; export const initClient = async ({ transport = fetchTransport, hydrateRootOptions, handleResponse, } = {}) => { const transportContext = { setRscPayload: () => { }, handleResponse, }; let transportCallServer = transport(transportContext); const callServer = (id, args) => transportCallServer(id, args); const upgradeToRealtime = async ({ key } = {}) => { const { realtimeTransport } = await import("../lib/realtime/client"); const createRealtimeTransport = realtimeTransport({ key }); transportCallServer = createRealtimeTransport(transportContext); }; globalThis.__rsc_callServer = callServer; globalThis.__rw = { callServer, upgradeToRealtime, }; const rootEl = document.getElementById("hydrate-root"); if (!rootEl) { throw new Error('no element with id "hydrate-root"'); } let rscPayload; // context(justinvdm, 18 Jun 2025): We inject the RSC payload // unless render(Document, [...], { rscPayload: false }) was used. if (globalThis.__FLIGHT_DATA) { rscPayload = createFromReadableStream(rscStream, { callServer, }); } function Content() { const [streamData, setStreamData] = React.useState(rscPayload); const [_isPending, startTransition] = React.useTransition(); transportContext.setRscPayload = (v) => startTransition(() => setStreamData(v)); return (_jsx(_Fragment, { children: streamData ? React.use(streamData).node : null })); } hydrateRoot(rootEl, _jsx(Content, {}), { onUncaughtError: (error, { componentStack }) => { console.error("Uncaught error: %O\n\nComponent stack:%s", error, componentStack); }, ...hydrateRootOptions, }); if (import.meta.hot) { import.meta.hot.on("rsc:update", (e) => { console.log("[rwsdk] hot update", e.file); callServer("__rsc_hot_update", [e.file]); }); } };