Skip to content

Commit 8172e07

Browse files
docs: add detailed JSDoc comments for RSC functions and utilities to improve code clarity and maintainability
1 parent 5ba9634 commit 8172e07

File tree

12 files changed

+272
-19
lines changed

12 files changed

+272
-19
lines changed

node_package/src/RSCPayloadGenerator.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,20 @@ const mapRailsContextToRSCPayloadStreams = new Map<RailsContext, RSCPayloadStrea
1313

1414
const rscPayloadCallbacks = new Map<RailsContext, Array<RSCPayloadCallback>>();
1515

16-
// The RSC payload callbacks must be executed synchronously to maintain proper hydration timing.
17-
// This ensures that the RSC payload initialization script is injected into the HTML page
18-
// before the corresponding component's HTML markup appears. This timing is critical because:
19-
// 1. Client-side components only hydrate after their HTML is present in the page
20-
// 2. The RSC payload must be available before hydration begins to prevent unnecessary refetching
21-
// 3. Using setTimeout(callback, 0) would break this synchronization and could lead to hydration issues
16+
/**
17+
* Registers a callback to be executed when RSC payloads are generated.
18+
*
19+
* This function:
20+
* 1. Stores the callback function by railsContext
21+
* 2. Immediately executes the callback for any existing streams
22+
*
23+
* This synchronous execution is critical for preventing hydration race conditions.
24+
* It ensures payload array initialization happens before component HTML appears
25+
* in the response stream.
26+
*
27+
* @param railsContext - Context for the current request
28+
* @param callback - Function to call when an RSC payload is generated
29+
*/
2230
export const onRSCPayloadGenerated = (railsContext: RailsContext, callback: RSCPayloadCallback) => {
2331
const callbacks = rscPayloadCallbacks.get(railsContext) || [];
2432
callbacks.push(callback);
@@ -29,6 +37,22 @@ export const onRSCPayloadGenerated = (railsContext: RailsContext, callback: RSCP
2937
existingStreams.forEach((streamInfo) => callback(streamInfo));
3038
};
3139

40+
/**
41+
* Generates and tracks RSC payloads for server components.
42+
*
43+
* getRSCPayloadStream:
44+
* 1. Calls the global generateRSCPayload function
45+
* 2. Tracks streams by railsContext for later injection
46+
* 3. Notifies callbacks immediately to enable early payload embedding
47+
*
48+
* The immediate callback notification is critical for preventing hydration race conditions,
49+
* as it ensures the payload array is initialized in the HTML stream before component rendering.
50+
*
51+
* @param componentName - Name of the server component
52+
* @param props - Props for the server component
53+
* @param railsContext - Context for the current request
54+
* @returns A stream of the RSC payload
55+
*/
3256
export const getRSCPayloadStream = async (
3357
componentName: string,
3458
props: unknown,

node_package/src/RSCProvider.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ type RSCContextType = {
1010

1111
const RSCContext = React.createContext<RSCContextType | undefined>(undefined);
1212

13+
/**
14+
* Creates a provider context for React Server Components.
15+
*
16+
* RSCProvider is a foundational component that:
17+
* 1. Provides caching for server components to prevent redundant requests
18+
* 2. Manages the fetching of server components through getComponent
19+
* 3. Offers environment-agnostic access to server components
20+
*
21+
* This factory function accepts an environment-specific getServerComponent implementation,
22+
* allowing it to work correctly in both client and server environments.
23+
*
24+
* @param railsContext - Context for the current request
25+
* @param getServerComponent - Environment-specific function for fetching server components
26+
* @returns A provider component that wraps children with RSC context
27+
*
28+
* @important This is an internal function. End users should not use this directly.
29+
* Instead, use wrapServerComponentRenderer from 'react-on-rails/wrapServerComponentRenderer/client'
30+
* for client-side rendering or 'react-on-rails/wrapServerComponentRenderer/server' for server-side rendering.
31+
*/
1332
export const createRSCProvider = ({
1433
railsContext,
1534
getServerComponent,
@@ -53,6 +72,25 @@ export const createRSCProvider = ({
5372
};
5473
};
5574

75+
/**
76+
* Hook to access the RSC context within client components.
77+
*
78+
* This hook provides access to:
79+
* - getCachedComponent: For retrieving already rendered server components
80+
* - getComponent: For fetching and rendering server components
81+
*
82+
* It must be used within a component wrapped by RSCProvider (typically done
83+
* automatically by wrapServerComponentRenderer).
84+
*
85+
* @returns The RSC context containing methods for working with server components
86+
* @throws Error if used outside of an RSCProvider
87+
*
88+
* @example
89+
* ```tsx
90+
* const { getComponent } = useRSC();
91+
* const serverComponent = React.use(getComponent('MyServerComponent', props));
92+
* ```
93+
*/
5694
export const useRSC = () => {
5795
const context = React.useContext(RSCContext);
5896
if (!context) {

node_package/src/RSCRoute.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
import * as React from 'react';
22
import { useRSC } from './RSCProvider.tsx';
33

4+
/**
5+
* Renders a React Server Component inside a React Client Component.
6+
*
7+
* RSCRoute provides a bridge between client and server components, allowing server components
8+
* to be directly rendered inside client components. This component:
9+
*
10+
* 1. During initial SSR - Uses the RSC payload to render the server component and embeds the payload in the page
11+
* 2. During hydration - Uses the embedded RSC payload already in the page
12+
* 3. During client navigation - Fetches the RSC payload via HTTP
13+
*
14+
* @example
15+
* ```tsx
16+
* <RSCRoute componentName="MyServerComponent" componentProps={{ user }} />
17+
* ```
18+
*
19+
* @important Only use for server components whose props change rarely. Frequent prop changes
20+
* will cause network requests for each change, impacting performance.
21+
*
22+
* @important This component expects that the component tree that contains it is wrapped using
23+
* wrapServerComponentRenderer from 'react-on-rails/wrapServerComponentRenderer/client' for client-side
24+
* rendering or 'react-on-rails/wrapServerComponentRenderer/server' for server-side rendering.
25+
*/
426
export type RSCRouteProps = {
527
componentName: string;
628
componentProps: unknown;

node_package/src/getReactServerComponent.client.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ const createFromFetch = async (fetchPromise: Promise<Response>) => {
2626
return createFromReadableStream<React.ReactNode>(transformedStream);
2727
};
2828

29+
/**
30+
* Fetches an RSC payload via HTTP request.
31+
*
32+
* This function:
33+
* 1. Serializes the component props
34+
* 2. Makes an HTTP request to the RSC payload generation endpoint
35+
* 3. Processes the response stream into React elements
36+
*
37+
* This is used for client-side navigation or when rendering components
38+
* that weren't part of the initial server render.
39+
*
40+
* @param props - Object containing component name, props, and railsContext
41+
* @returns A Promise resolving to the rendered React element
42+
*/
2943
const fetchRSC = ({ componentName, componentProps, railsContext }: ClientGetReactServerComponentProps) => {
3044
const propsString = JSON.stringify(componentProps);
3145
const { rscPayloadGenerationUrl } = railsContext;
@@ -65,12 +79,48 @@ const createRSCStreamFromArray = (payloads: string[]) => {
6579
return stream;
6680
};
6781

82+
/**
83+
* Creates React elements from preloaded RSC payloads in the page.
84+
*
85+
* This function:
86+
* 1. Creates a ReadableStream from the array of payload chunks
87+
* 2. Transforms the stream to handle console logs and other processing
88+
* 3. Uses React's createFromReadableStream to process the payload
89+
*
90+
* This is used during hydration to avoid making HTTP requests when
91+
* the payload is already embedded in the page.
92+
*
93+
* @param payloads - Array of RSC payload chunks from the global array
94+
* @returns A Promise resolving to the rendered React element
95+
*/
6896
const createFromPreloadedPayloads = (payloads: string[]) => {
6997
const stream = createRSCStreamFromArray(payloads);
7098
const transformedStream = transformRSCStreamAndReplayConsoleLogs(stream);
7199
return createFromReadableStream<React.ReactNode>(transformedStream);
72100
};
73101

102+
/**
103+
* Fetches and renders a server component on the client side.
104+
*
105+
* This function:
106+
* 1. Checks for embedded RSC payloads in window.REACT_ON_RAILS_RSC_PAYLOADS
107+
* 2. If found, uses the embedded payload to avoid an HTTP request
108+
* 3. If not found (during client navigation or dynamic rendering), fetches via HTTP
109+
* 4. Processes the RSC payload into React elements
110+
*
111+
* The embedded payload approach ensures optimal performance during initial page load,
112+
* while the HTTP fallback enables dynamic rendering after navigation.
113+
*
114+
* @param componentName - Name of the server component to render
115+
* @param componentProps - Props to pass to the server component
116+
* @param railsContext - Context for the current request
117+
* @returns A Promise resolving to the rendered React element
118+
*
119+
* @important This is an internal function. End users should not use this directly.
120+
* Instead, use the useRSC hook which provides getComponent and getCachedComponent functions
121+
* for fetching or retrieving cached server components. For rendering server components,
122+
* consider using RSCRoute component which handles the rendering logic automatically.
123+
*/
74124
const getReactServerComponent = ({
75125
componentName,
76126
componentProps,

node_package/src/getReactServerComponent.server.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ const createFromReactOnRailsNodeStream = (
1717
return createFromNodeStream(transformedStream, ssrManifest);
1818
};
1919

20+
/**
21+
* Creates an SSR manifest for React's server components runtime.
22+
*
23+
* This function:
24+
* 1. Loads the server and client component manifests
25+
* 2. Creates a mapping between client and server module IDs
26+
* 3. Builds a moduleMap structure required by React's SSR runtime
27+
*
28+
* The manifest allows React to correctly associate server components
29+
* with their client counterparts during hydration.
30+
*
31+
* @param reactServerManifestFileName - Path to the server manifest file
32+
* @param reactClientManifestFileName - Path to the client manifest file
33+
* @returns A Promise resolving to the SSR manifest object
34+
*/
2035
const createSSRManifest = async (
2136
reactServerManifestFileName: string,
2237
reactClientManifestFileName: string,
@@ -52,6 +67,29 @@ const createSSRManifest = async (
5267
return ssrManifest;
5368
};
5469

70+
/**
71+
* Fetches and renders a server component on the server side.
72+
*
73+
* This function:
74+
* 1. Validates the railsContext for required properties
75+
* 2. Creates an SSR manifest mapping server and client modules
76+
* 3. Gets the RSC payload stream via getRSCPayloadStream
77+
* 4. Processes the stream with React's SSR runtime
78+
*
79+
* During SSR, this function ensures that the RSC payload is both:
80+
* - Used to render the server component
81+
* - Tracked so it can be embedded in the HTML response
82+
*
83+
* @param componentName - Name of the server component to render
84+
* @param componentProps - Props to pass to the server component
85+
* @param railsContext - Context for the current request
86+
* @returns A Promise resolving to the rendered React element
87+
*
88+
* @important This is an internal function. End users should not use this directly.
89+
* Instead, use the useRSC hook which provides getComponent and getCachedComponent functions
90+
* for fetching or retrieving cached server components. For rendering server components,
91+
* consider using RSCRoute component which handles the rendering logic automatically.
92+
*/
5593
const getReactServerComponent = async ({
5694
componentName,
5795
componentProps,

node_package/src/injectRSCPayload.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ function writeChunk(chunk: string, transform: Transform, cacheKey: string) {
3030
writeScript(`(${cacheKeyJSArray(cacheKey)}).push(${chunk})`, transform);
3131
}
3232

33+
/**
34+
* Embeds RSC payloads into the HTML stream for optimal hydration.
35+
*
36+
* This function:
37+
* 1. Creates a result stream for the combined HTML + RSC payloads
38+
* 2. Listens for RSC payload generation via onRSCPayloadGenerated
39+
* 3. Initializes global arrays for each payload BEFORE component HTML
40+
* 4. Writes each payload chunk as a script tag that pushes to the array
41+
* 5. Passes HTML through to the result stream
42+
*
43+
* The timing of array initialization is critical - it must occur before the
44+
* component's HTML to ensure the array exists when client hydration begins.
45+
* This prevents unnecessary HTTP requests during hydration.
46+
*
47+
* @param pipeableHtmlStream - HTML stream from React's renderToPipeableStream
48+
* @param railsContext - Context for the current request
49+
* @returns A combined stream with embedded RSC payloads
50+
*/
3351
export default function injectRSCPayload(
3452
pipeableHtmlStream: NodeJS.ReadableStream | PipeableStream,
3553
railsContext: RailsContext,

node_package/src/registerServerComponent/server.rsc.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import ReactOnRails from '../ReactOnRails.client.ts';
22
import { ReactComponent, RenderFunction } from '../types/index.ts';
33

44
/**
5-
* Registers React Server Components (RSC) with React on Rails for the RSC bundle.
5+
* Registers React Server Components in the RSC bundle.
66
*
7-
* This function handles the registration of components in the RSC bundle context,
8-
* where components are registered directly into the ComponentRegistry without any
9-
* additional wrapping. This is different from the server bundle registration,
10-
* which wraps components with RSCServerRoot.
7+
* Unlike the client and server implementations, the RSC bundle registration
8+
* directly registers components without any wrapping. This is because the
9+
* RSC bundle is responsible for generating the actual RSC payload of server
10+
* components, not for rendering or hydrating client components.
1111
*
1212
* @param components - Object mapping component names to their implementations
1313
*

node_package/src/registerServerComponent/server.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@ import { ReactComponent, RenderFunction } from '../types/index.ts';
55
import wrapServerComponentRenderer from '../wrapServerComponentRenderer/server.tsx';
66

77
/**
8-
* Registers React Server Components (RSC) with React on Rails for the server bundle.
8+
* Registers React Server Components for use in server bundles.
99
*
10-
* This function wraps each component with RSCServerRoot, which handles the server-side
11-
* rendering of React Server Components using pre-generated RSC payloads.
12-
*
13-
* The RSCServerRoot component:
14-
* - Uses pre-generated RSC payloads from the RSC bundle
15-
* - Builds the rendering tree of the server component
16-
* - Handles the integration with React's streaming SSR
10+
* This function:
11+
* 1. Takes server component implementations
12+
* 2. Wraps each component with RSCRoute using WrapServerComponentRenderer
13+
* 3. Registers the wrapped components with ReactOnRails
1714
*
1815
* @param components - Object mapping component names to their implementations
1916
*

node_package/src/transformRSCNodeStream.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
import { Transform } from 'stream';
22

3+
/**
4+
* Transforms an RSC Node.js stream for server-side processing.
5+
*
6+
* This utility:
7+
* 1. Takes a Node.js ReadableStream of RSC payload chunks
8+
* 2. Applies necessary transformations for server-side consumption
9+
* 3. Returns a modified stream that works with React's SSR runtime
10+
*
11+
* This is essential for proper handling of RSC payloads in Node.js
12+
* environment during server-side rendering.
13+
*
14+
* @param stream - The Node.js RSC payload stream
15+
* @returns A transformed stream compatible with React's SSR runtime
16+
*/
317
export default function transformRSCStream(stream: NodeJS.ReadableStream): NodeJS.ReadableStream {
418
const decoder = new TextDecoder();
519
let lastIncompleteChunk = '';

node_package/src/transformRSCStreamAndReplayConsoleLogs.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
import { RSCPayloadChunk } from './types/index.ts';
22

3+
/**
4+
* Transforms an RSC stream and replays console logs on the client.
5+
*
6+
* This utility:
7+
* 1. Takes a ReadableStream of RSC payload chunks
8+
* 2. Processes each chunk to extract and replay embedded console logs
9+
* 3. Passes through the actual RSC payload data
10+
*
11+
* This improves debugging by making server-side console logs appear in
12+
* the client console, maintaining a seamless development experience.
13+
*
14+
* @param stream - The RSC payload stream to transform
15+
* @returns A transformed stream with console logs extracted and replayed
16+
*/
317
export default function transformRSCStreamAndReplayConsoleLogs(stream: ReadableStream<Uint8Array | string>) {
418
return new ReadableStream({
519
async start(controller) {

0 commit comments

Comments
 (0)