7
7
8
8
// Modified from https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js
9
9
10
- const MODULE_REFERENCE = Symbol . for ( 'react.module .reference' )
10
+ const CLIENT_REFERENCE = Symbol . for ( 'react.client .reference' )
11
11
const PROMISE_PROTOTYPE = Promise . prototype
12
12
13
- const proxyHandlers : ProxyHandler < object > = {
14
- get : function ( target : any , name : string , _receiver : any ) {
13
+ const deepProxyHandlers = {
14
+ get : function ( target : any , name : string , _receiver : ProxyHandler < any > ) {
15
15
switch ( name ) {
16
16
// These names are read by the Flight runtime if you end up using the exports object.
17
17
case '$$typeof' :
@@ -28,61 +28,167 @@ const proxyHandlers: ProxyHandler<object> = {
28
28
// reference.
29
29
case 'defaultProps' :
30
30
return undefined
31
+ // Avoid this attempting to be serialized.
32
+ case 'toJSON' :
33
+ return undefined
34
+ case Symbol . toPrimitive . toString ( ) :
35
+ // @ts -ignore
36
+ return Object . prototype [ Symbol . toPrimitive ]
37
+ case 'Provider' :
38
+ throw new Error (
39
+ `Cannot render a Client Context Provider on the Server. ` +
40
+ `Instead, you can export a Client Component wrapper ` +
41
+ `that itself renders a Client Context Provider.`
42
+ )
43
+ default :
44
+ break
45
+ }
46
+ let expression
47
+ switch ( target . name ) {
48
+ case '' :
49
+ expression = String ( name )
50
+ break
51
+ case '*' :
52
+ expression = String ( name )
53
+ break
54
+ default :
55
+ expression = String ( target . name ) + '.' + String ( name )
56
+ }
57
+ throw new Error (
58
+ `Cannot access ${ expression } on the server. ` +
59
+ 'You cannot dot into a client module from a server component. ' +
60
+ 'You can only pass the imported name through.'
61
+ )
62
+ } ,
63
+ set : function ( ) {
64
+ throw new Error ( 'Cannot assign to a client module from a server module.' )
65
+ } ,
66
+ }
67
+
68
+ const proxyHandlers = {
69
+ get : function ( target : any , name : string , _receiver : ProxyHandler < any > ) {
70
+ switch ( name ) {
71
+ // These names are read by the Flight runtime if you end up using the exports object.
72
+ case '$$typeof' :
73
+ // These names are a little too common. We should probably have a way to
74
+ // have the Flight runtime extract the inner target instead.
75
+ return target . $$typeof
76
+ case 'filepath' :
77
+ return target . filepath
78
+ case 'name' :
79
+ return target . name
80
+ case 'async' :
81
+ return target . async
82
+ // We need to special case this because createElement reads it if we pass this
83
+ // reference.
84
+ case 'defaultProps' :
85
+ return undefined
86
+ // Avoid this attempting to be serialized.
87
+ case 'toJSON' :
88
+ return undefined
89
+ case Symbol . toPrimitive . toString ( ) :
90
+ // @ts -ignore
91
+ return Object . prototype [ Symbol . toPrimitive ]
31
92
case '__esModule' :
32
93
// Something is conditionally checking which export to use. We'll pretend to be
33
94
// an ESM compat module but then we'll check again on the client.
34
- target . default = {
35
- $$typeof : MODULE_REFERENCE ,
36
- filepath : target . filepath ,
37
- // This a placeholder value that tells the client to conditionally use the
38
- // whole object or just the default export.
39
- name : '' ,
40
- async : target . async ,
41
- }
95
+ const moduleId = target . filepath
96
+ target . default = Object . defineProperties (
97
+ function ( ) {
98
+ throw new Error (
99
+ `Attempted to call the default export of ${ moduleId } from the server ` +
100
+ `but it's on the client. It's not possible to invoke a client function from ` +
101
+ `the server, it can only be rendered as a Component or passed to props of a ` +
102
+ `Client Component.`
103
+ )
104
+ } ,
105
+ {
106
+ // This a placeholder value that tells the client to conditionally use the
107
+ // whole object or just the default export.
108
+ name : { value : '' } ,
109
+ $$typeof : { value : CLIENT_REFERENCE } ,
110
+ filepath : { value : target . filepath } ,
111
+ async : { value : target . async } ,
112
+ }
113
+ )
42
114
return true
43
115
case 'then' :
116
+ if ( target . then ) {
117
+ // Use a cached value
118
+ return target . then
119
+ }
44
120
if ( ! target . async ) {
45
121
// If this module is expected to return a Promise (such as an AsyncModule) then
46
122
// we should resolve that with a client reference that unwraps the Promise on
47
123
// the client.
48
- const then = function then (
49
- resolve : ( res : any ) => void ,
50
- _reject : ( err : any ) => void
51
- ) {
52
- const moduleReference : Record < string , any > = {
53
- $$typeof : MODULE_REFERENCE ,
54
- filepath : target . filepath ,
55
- name : '*' , // Represents the whole object instead of a particular import.
56
- async : true ,
124
+
125
+ const clientReference = Object . defineProperties (
126
+ { } ,
127
+ {
128
+ // Represents the whole Module object instead of a particular import.
129
+ name : { value : '*' } ,
130
+ $$typeof : { value : CLIENT_REFERENCE } ,
131
+ filepath : { value : target . filepath } ,
132
+ async : { value : true } ,
57
133
}
58
- return Promise . resolve (
59
- resolve ( new Proxy ( moduleReference , proxyHandlers ) )
60
- )
61
- }
62
- // If this is not used as a Promise but is treated as a reference to a `.then`
63
- // export then we should treat it as a reference to that name.
64
- then . $$typeof = MODULE_REFERENCE
65
- then . filepath = target . filepath
66
- // then.name is conveniently already "then" which is the export name we need.
67
- // This will break if it's minified though.
134
+ )
135
+ const proxy = new Proxy ( clientReference , proxyHandlers )
136
+
137
+ // Treat this as a resolved Promise for React's use()
138
+ target . status = 'fulfilled'
139
+ target . value = proxy
140
+
141
+ const then = ( target . then = Object . defineProperties (
142
+ function then ( resolve : any , _reject : any ) {
143
+ // Expose to React.
144
+ return Promise . resolve (
145
+ // $FlowFixMe[incompatible-call] found when upgrading Flow
146
+ resolve ( proxy )
147
+ )
148
+ } ,
149
+ // If this is not used as a Promise but is treated as a reference to a `.then`
150
+ // export then we should treat it as a reference to that name.
151
+ {
152
+ name : { value : 'then' } ,
153
+ $$typeof : { value : CLIENT_REFERENCE } ,
154
+ filepath : { value : target . filepath } ,
155
+ async : { value : false } ,
156
+ }
157
+ ) )
68
158
return then
159
+ } else {
160
+ // Since typeof .then === 'function' is a feature test we'd continue recursing
161
+ // indefinitely if we return a function. Instead, we return an object reference
162
+ // if we check further.
163
+ return undefined
69
164
}
70
- break
71
165
default :
72
166
break
73
167
}
74
168
let cachedReference = target [ name ]
75
169
if ( ! cachedReference ) {
76
- cachedReference = target [ name ] = {
77
- $$typeof : MODULE_REFERENCE ,
78
- filepath : target . filepath ,
79
- name : name ,
80
- async : target . async ,
81
- }
170
+ const reference = Object . defineProperties (
171
+ function ( ) {
172
+ throw new Error (
173
+ `Attempted to call ${ String ( name ) } () from the server but ${ String (
174
+ name
175
+ ) } is on the client. ` +
176
+ `It's not possible to invoke a client function from the server, it can ` +
177
+ `only be rendered as a Component or passed to props of a Client Component.`
178
+ )
179
+ } ,
180
+ {
181
+ name : { value : name } ,
182
+ $$typeof : { value : CLIENT_REFERENCE } ,
183
+ filepath : { value : target . filepath } ,
184
+ async : { value : target . async } ,
185
+ }
186
+ )
187
+ cachedReference = target [ name ] = new Proxy ( reference , deepProxyHandlers )
82
188
}
83
189
return cachedReference
84
190
} ,
85
- getPrototypeOf ( _target : object ) {
191
+ getPrototypeOf ( _target : any ) : object {
86
192
// Pretend to be a Promise in case anyone asks.
87
193
return PROMISE_PROTOTYPE
88
194
} ,
@@ -92,11 +198,15 @@ const proxyHandlers: ProxyHandler<object> = {
92
198
}
93
199
94
200
export function createProxy ( moduleId : string ) {
95
- const moduleReference = {
96
- $$typeof : MODULE_REFERENCE ,
97
- filepath : moduleId ,
98
- name : '*' , // Represents the whole object instead of a particular import.
99
- async : false ,
100
- }
101
- return new Proxy ( moduleReference , proxyHandlers )
201
+ const clientReference = Object . defineProperties (
202
+ { } ,
203
+ {
204
+ // Represents the whole object instead of a particular import.
205
+ name : { value : '*' } ,
206
+ $$typeof : { value : CLIENT_REFERENCE } ,
207
+ filepath : { value : moduleId } ,
208
+ async : { value : false } ,
209
+ }
210
+ )
211
+ return new Proxy ( clientReference , proxyHandlers )
102
212
}
0 commit comments