rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
103 lines (102 loc) • 4.32 kB
JavaScript
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]);
});
}
};