77
88// Modified from https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js
99
10- const MODULE_REFERENCE = Symbol . for ( 'react.module .reference' )
10+ const CLIENT_REFERENCE = Symbol . for ( 'react.client .reference' )
1111const PROMISE_PROTOTYPE = Promise . prototype
1212
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 > ) {
1515 switch ( name ) {
1616 // These names are read by the Flight runtime if you end up using the exports object.
1717 case '$$typeof' :
@@ -28,61 +28,167 @@ const proxyHandlers: ProxyHandler<object> = {
2828 // reference.
2929 case 'defaultProps' :
3030 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 ]
3192 case '__esModule' :
3293 // Something is conditionally checking which export to use. We'll pretend to be
3394 // 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+ )
42114 return true
43115 case 'then' :
116+ if ( target . then ) {
117+ // Use a cached value
118+ return target . then
119+ }
44120 if ( ! target . async ) {
45121 // If this module is expected to return a Promise (such as an AsyncModule) then
46122 // we should resolve that with a client reference that unwraps the Promise on
47123 // 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 } ,
57133 }
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+ ) )
68158 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
69164 }
70- break
71165 default :
72166 break
73167 }
74168 let cachedReference = target [ name ]
75169 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 )
82188 }
83189 return cachedReference
84190 } ,
85- getPrototypeOf ( _target : object ) {
191+ getPrototypeOf ( _target : any ) : object {
86192 // Pretend to be a Promise in case anyone asks.
87193 return PROMISE_PROTOTYPE
88194 } ,
@@ -92,11 +198,15 @@ const proxyHandlers: ProxyHandler<object> = {
92198}
93199
94200export 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 )
102212}
0 commit comments