1
1
"use client" ;
2
2
3
+ /* eslint-disable no-underscore-dangle */
4
+
3
5
import * as React from 'react' ;
4
6
import * as ReactDOMClient from 'react-dom/client' ;
5
7
import { createFromReadableStream } from 'react-on-rails-rsc/client' ;
@@ -13,6 +15,12 @@ if (typeof use !== 'function') {
13
15
throw new Error ( 'React.use is not defined. Please ensure you are using React 19 to use server components.' ) ;
14
16
}
15
17
18
+ declare global {
19
+ interface Window {
20
+ __FLIGHT_DATA : unknown [ ] ;
21
+ }
22
+ }
23
+
16
24
export type RSCClientRootProps = {
17
25
componentName : string ;
18
26
rscPayloadGenerationUrlPath : string ;
@@ -35,6 +43,45 @@ const fetchRSC = ({ componentName, rscPayloadGenerationUrlPath, componentProps }
35
43
return createFromFetch ( fetch ( `/${ strippedUrlPath } /${ componentName } ?props=${ propsString } ` ) ) ;
36
44
}
37
45
46
+ const createRSCStreamFromPage = ( ) => {
47
+ let streamController : ReadableStreamController < unknown > | undefined ;
48
+ const stream = new ReadableStream ( {
49
+ start ( controller ) {
50
+ if ( typeof window === 'undefined' ) {
51
+ return ;
52
+ }
53
+ const handleChunk = ( chunk : unknown ) => {
54
+ controller . enqueue ( chunk ) ;
55
+ } ;
56
+ if ( ! window . __FLIGHT_DATA ) {
57
+ window . __FLIGHT_DATA = [ ] ;
58
+ }
59
+ window . __FLIGHT_DATA . forEach ( handleChunk ) ;
60
+ window . __FLIGHT_DATA . push = ( ...chunks : unknown [ ] ) => {
61
+ chunks . forEach ( handleChunk ) ;
62
+ return chunks . length ;
63
+ } ;
64
+ streamController = controller ;
65
+ }
66
+ } ) ;
67
+
68
+ if ( typeof document !== 'undefined' && document . readyState === 'loading' ) {
69
+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
70
+ streamController ?. close ( ) ;
71
+ } ) ;
72
+ } else {
73
+ streamController ?. close ( ) ;
74
+ }
75
+
76
+ return stream ;
77
+ }
78
+
79
+ const createFromRSCStream = ( ) => {
80
+ const stream = createRSCStreamFromPage ( ) ;
81
+ const transformedStream = transformRSCStreamAndReplayConsoleLogs ( stream ) ;
82
+ return createFromReadableStream < React . ReactNode > ( transformedStream ) ;
83
+ }
84
+
38
85
/**
39
86
* RSCClientRoot is a React component that handles client-side rendering of React Server Components (RSC).
40
87
* It manages the fetching, caching, and rendering of RSC payloads from the server.
@@ -53,7 +100,6 @@ const RSCClientRoot: RenderFunction = async ({
53
100
rscPayloadGenerationUrlPath,
54
101
componentProps,
55
102
} : RSCClientRootProps , _railsContext ?: RailsContext , domNodeId ?: string ) => {
56
- const root = await fetchRSC ( { componentName, rscPayloadGenerationUrlPath, componentProps } )
57
103
if ( ! domNodeId ) {
58
104
throw new Error ( 'RSCClientRoot: No domNodeId provided' ) ;
59
105
}
@@ -62,8 +108,10 @@ const RSCClientRoot: RenderFunction = async ({
62
108
throw new Error ( `RSCClientRoot: No DOM node found for id: ${ domNodeId } ` ) ;
63
109
}
64
110
if ( domNode . innerHTML ) {
111
+ const root = await createFromRSCStream ( ) ;
65
112
ReactDOMClient . hydrateRoot ( domNode , root ) ;
66
113
} else {
114
+ const root = await fetchRSC ( { componentName, rscPayloadGenerationUrlPath, componentProps } )
67
115
ReactDOMClient . createRoot ( domNode ) . render ( root ) ;
68
116
}
69
117
// Added only to satisfy the return type of RenderFunction
0 commit comments