Skip to content

Commit db9d080

Browse files
Enhance type safety in RSCClientRoot and RSCPayloadContainer by using RSCPayloadChunk type, and improve documentation for chunk handling and transfer resilience.
1 parent 44d3758 commit db9d080

File tree

3 files changed

+43
-8
lines changed

3 files changed

+43
-8
lines changed

node_package/src/RSCClientRoot.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
'use client';
22

3-
/* eslint-disable no-underscore-dangle */
4-
53
import * as React from 'react';
64
import * as ReactDOMClient from 'react-dom/client';
75
import { createFromReadableStream } from 'react-on-rails-rsc/client';
@@ -17,7 +15,7 @@ if (typeof use !== 'function') {
1715

1816
declare global {
1917
interface Window {
20-
REACT_ON_RAILS_RSC_PAYLOAD: unknown[];
18+
REACT_ON_RAILS_RSC_PAYLOAD: RSCPayloadChunk[];
2119
}
2220
}
2321

@@ -50,14 +48,29 @@ const createRSCStreamFromPage = () => {
5048
if (typeof window === 'undefined') {
5149
return;
5250
}
53-
const handleChunk = (chunk: unknown) => {
54-
controller.enqueue(chunk as RSCPayloadChunk);
51+
const handleChunk = (chunk: RSCPayloadChunk) => {
52+
controller.enqueue(chunk);
5553
};
54+
55+
// The RSC payload transfer mechanism works in two possible scenarios:
56+
// 1. RSCClientRoot executes first:
57+
// - Initializes REACT_ON_RAILS_RSC_PAYLOAD as an empty array
58+
// - Overrides the push function to handle incoming chunks
59+
// - When server scripts run later, they use the overridden push function
60+
// 2. Server scripts execute first:
61+
// - Initialize REACT_ON_RAILS_RSC_PAYLOAD as an empty array
62+
// - Buffer RSC payload chunks in the array
63+
// - When RSCClientRoot runs, it reads buffered chunks and overrides push
64+
//
65+
// Key points:
66+
// - The array is never reassigned, ensuring data consistency
67+
// - The push function override ensures all chunks are properly handled
68+
// - Execution order is irrelevant - both scenarios work correctly
5669
if (!window.REACT_ON_RAILS_RSC_PAYLOAD) {
5770
window.REACT_ON_RAILS_RSC_PAYLOAD = [];
5871
}
5972
window.REACT_ON_RAILS_RSC_PAYLOAD.forEach(handleChunk);
60-
window.REACT_ON_RAILS_RSC_PAYLOAD.push = (...chunks: unknown[]) => {
73+
window.REACT_ON_RAILS_RSC_PAYLOAD.push = (...chunks) => {
6174
chunks.forEach(handleChunk);
6275
return chunks.length;
6376
};

node_package/src/RSCPayloadContainer.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ function escapeScript(script: string) {
1818
return script.replace(/<!--/g, '<\\!--').replace(/<\/(script)/gi, '</\\$1');
1919
}
2020

21+
/**
22+
* RSCPayloadContainer is a React component that handles the server-to-client transfer
23+
* of React Server Component (RSC) payloads. It works in conjunction with RSCClientRoot
24+
* to ensure reliable delivery of RSC chunks.
25+
*
26+
* How it works:
27+
* 1. Receives a NodeJS.ReadableStream containing RSC payload chunks from the server
28+
* 2. Creates a series of promises to handle each chunk asynchronously
29+
* 3. For each chunk:
30+
* - Creates a script tag that pushes the chunk to window.REACT_ON_RAILS_RSC_PAYLOAD
31+
* - Uses React.use() to handle the async chunk loading
32+
* - Recursively renders the next chunk using Suspense
33+
*
34+
* The component ensures that:
35+
* - Chunks are processed in order
36+
* - Server-side console logs are preserved
37+
* - The transfer is resilient to network conditions
38+
* - The client receives all chunks reliably
39+
*/
2140
const RSCPayloadContainer = ({
2241
chunkIndex,
2342
getChunkPromise,
@@ -27,6 +46,9 @@ const RSCPayloadContainer = ({
2746

2847
const scriptElement = React.createElement('script', {
2948
dangerouslySetInnerHTML: {
49+
// Ensure the array is never reassigned.
50+
// Even at the RSCClientRoot component, the array is assigned
51+
// only if it's not already assigned by this script.
3052
__html: escapeScript(`(self.REACT_ON_RAILS_RSC_PAYLOAD||=[]).push(${chunk.chunk})`),
3153
},
3254
key: `script-${chunkIndex}`,

node_package/src/RSCServerRoot.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,14 @@ const RSCServerRoot: RenderFunction = async (
7979
'serverClientManifestFileName and reactServerClientManifestFileName are required. ' +
8080
'Please ensure that React Server Component webpack configurations are properly set ' +
8181
'as stated in the React Server Component tutorial. ' +
82-
'Ensure to use "stream_react_component" instead of "react_component" to SSR a server component.',
82+
'Make sure to use "stream_react_component" instead of "react_component" to SSR a server component.',
8383
);
8484
}
8585

8686
if (typeof generateRSCPayload !== 'function') {
8787
throw new Error(
8888
'generateRSCPayload is not defined. Please ensure that you are using at least version 4.0.0 of ' +
89-
'React on Rails Pro and the node renderer, and that ReactOnRailsPro.configuration.enable_rsc_support ' +
89+
'React on Rails Pro and the Node renderer, and that ReactOnRailsPro.configuration.enable_rsc_support ' +
9090
'is set to true.',
9191
);
9292
}

0 commit comments

Comments
 (0)