12
const Module = require('module');
13
14
module.exports = function register() {
15
- const MODULE_REFERENCE = Symbol.for('react.module.reference');
15
+ const CLIENT_REFERENCE = Symbol.for('react.client.reference');
16
const PROMISE_PROTOTYPE = Promise.prototype;
17
18
+ const deepProxyHandlers = {
19
+ get: function(target: Function, name: string, receiver: Proxy<Function>) {
20
+ switch (name) {
21
+ // These names are read by the Flight runtime if you end up using the exports object.
22
+ case '$$typeof':
23
+ // These names are a little too common. We should probably have a way to
24
+ // have the Flight runtime extract the inner target instead.
25
+ return target.$$typeof;
26
+ case 'filepath':
27
+ return target.filepath;
28
+ case 'name':
29
+ return target.name;
30
+ case 'async':
31
+ return target.async;
32
+ // We need to special case this because createElement reads it if we pass this
33
+ // reference.
34
+ case 'defaultProps':
35
+ return undefined;
36
+ case 'getDefaultProps':
37
+ return undefined;
38
+ // Avoid this attempting to be serialized.
39
+ case 'toJSON':
40
+ return undefined;
41
+ case Symbol.toPrimitive:
42
+ // $FlowFixMe[prop-missing]
43
+ return Object.prototype[Symbol.toPrimitive];
44
+ case 'Provider':
45
+ throw new Error(
46
+ `Cannot render a Client Context Provider on the Server. ` +
47
+ `Instead, you can export a Client Component wrapper ` +
48
+ `that itself renders a Client Context Provider.`,
49
+ );
50
+ }
51
+ let expression;
52
+ switch (target.name) {
53
+ case '':
54
+ // eslint-disable-next-line react-internal/safe-string-coercion
55
+ expression = String(name);
56
+ break;
57
+ case '*':
58
+ // eslint-disable-next-line react-internal/safe-string-coercion
59
+ expression = String(name);
60
+ break;
61
+ default:
62
+ // eslint-disable-next-line react-internal/safe-string-coercion
63
+ expression = String(target.name) + '.' + String(name);
64
+ }
65
+ throw new Error(
66
+ `Cannot access ${expression} on the server. ` +
67
+ 'You cannot dot into a client module from a server component. ' +
68
+ 'You can only pass the imported name through.',
69
+ );
70
+ },
71
+ set: function() {
72
+ throw new Error('Cannot assign to a client module from a server module.');
73
+ },
74
+ };
75
+
76
const proxyHandlers = {
19
- get: function(
20
- target: {[string]: $FlowFixMe},
21
- name: string,
22
- receiver: Proxy<{[string]: $FlowFixMe}>,
23
- ) {
77
+ get: function(target: Function, name: string, receiver: Proxy<Function>) {
78
switch (name) {
79
// These names are read by the Flight runtime if you end up using the exports object.
80
case '$$typeof':
91
// reference.
92
case 'defaultProps':
93
return undefined;
94
+ case 'getDefaultProps':
95
+ return undefined;
96
+ // Avoid this attempting to be serialized.
97
+ case 'toJSON':
98
+ return undefined;
99
+ case Symbol.toPrimitive:
100
+ // $FlowFixMe[prop-missing]
101
+ return Object.prototype[Symbol.toPrimitive];
102
case '__esModule':
103
// Something is conditionally checking which export to use. We'll pretend to be
104
// an ESM compat module but then we'll check again on the client.
43
- target.default = {
44
- $$typeof: MODULE_REFERENCE,
45
- filepath: target.filepath,
46
- // This a placeholder value that tells the client to conditionally use the
47
- // whole object or just the default export.
48
- name: '',
49
- async: target.async,
50
- };
105
+ const moduleId = target.filepath;
106
+ target.default = Object.defineProperties(
107
+ (function() {
108
+ throw new Error(
109
+ `Attempted to call the default export of ${moduleId} from the server` +
110
+ `but it's on the client. It's not possible to invoke a client function from ` +
111
+ `the server, it can only be rendered as a Component or passed to props of a` +
112
+ `Client Component.`,
113
+ );
114
+ }: any),
115
+ {
116
+ // This a placeholder value that tells the client to conditionally use the
117
+ // whole object or just the default export.
118
+ name: {value: ''},
119
+ $$typeof: {value: CLIENT_REFERENCE},
120
+ filepath: {value: target.filepath},
121
+ async: {value: target.async},
122
+ },
123
+ );
124
return true;
125
case 'then':
126
+ if (target.then) {
127
+ // Use a cached value
128
+ return target.then;
129
+ }
130
if (!target.async) {
131
// If this module is expected to return a Promise (such as an AsyncModule) then
132
// we should resolve that with a client reference that unwraps the Promise on
133
// the client.
134
+
135
+ const innerModuleId = target.filepath;
136
+ const clientReference: Function = Object.defineProperties(
137
+ (function() {
138
+ throw new Error(
139
+ `Attempted to call the module exports of ${innerModuleId} from the server` +
140
+ `but it's on the client. It's not possible to invoke a client function from ` +
141
+ `the server, it can only be rendered as a Component or passed to props of a` +
142
+ `Client Component.`,
143
+ );
144
+ }: any),
145
+ {
146
+ // Represents the whole object instead of a particular import.
147
+ name: {value: '*'},
148
+ $$typeof: {value: CLIENT_REFERENCE},
149
+ filepath: {value: target.filepath},
150
+ async: {value: true},
151
+ },
152
+ );
153
+ const proxy = new Proxy(clientReference, proxyHandlers);
154
+
155
+ // Treat this as a resolved Promise for React's use()
156
+ target.status = 'fulfilled';
157
+ target.value = proxy;
158
+
159
// $FlowFixMe[missing-local-annot]
58
- const then = function then(resolve, reject: any) {
59
- const moduleReference: {[string]: any, ...} = {
60
- $$typeof: MODULE_REFERENCE,
61
- filepath: target.filepath,
62
- name: '*', // Represents the whole object instead of a particular import.
63
- async: true,
64
- };
65
- return Promise.resolve(
66
- // $FlowFixMe[incompatible-call] found when upgrading Flow
67
- resolve(new Proxy(moduleReference, proxyHandlers)),
68
- );
69
- };
70
- // If this is not used as a Promise but is treated as a reference to a `.then`
71
- // export then we should treat it as a reference to that name.
72
- then.$$typeof = MODULE_REFERENCE;
73
- then.filepath = target.filepath;
74
- // then.name is conveniently already "then" which is the export name we need.
75
- // This will break if it's minified though.
160
+ const then = (target.then = Object.defineProperties(
161
+ (function then(resolve, reject: any) {
162
+ // Expose to React.
163
+ return Promise.resolve(
164
+ // $FlowFixMe[incompatible-call] found when upgrading Flow
165
+ resolve(proxy),
166
+ );
167
+ }: any),
168
+ // If this is not used as a Promise but is treated as a reference to a `.then`
169
+ // export then we should treat it as a reference to that name.
170
+ {
171
+ name: {value: 'then'},
172
+ $$typeof: {value: CLIENT_REFERENCE},
173
+ filepath: {value: target.filepath},
174
+ async: {value: false},
175
+ },
176
+ ));
177
return then;
178
+ } else {
179
+ // Since typeof .then === 'function' is a feature test we'd continue recursing
180
+ // indefinitely if we return a function. Instead, we return an object reference
181
+ // if we check further.
182
+ return undefined;
183
}
184
}
185
let cachedReference = target[name];
186
if (!cachedReference) {
81
- cachedReference = target[name] = {
82
- $$typeof: MODULE_REFERENCE,
83
- filepath: target.filepath,
84
- name: name,
85
- async: target.async,
86
- };
187
+ const reference = Object.defineProperties(
188
+ (function() {
189
+ throw new Error(
190
+ // eslint-disable-next-line react-internal/safe-string-coercion
191
+ `Attempted to call ${String(name)}() from the server but ${String(
192
+ name,
193
+ )} is on the client. ` +
194
+ `It's not possible to invoke a client function from the server, it can ` +
195
+ `only be rendered as a Component or passed to props of a Client Component.`,
196
+ );
197
+ }: any),
198
+ {
199
+ name: {value: name},
200
+ $$typeof: {value: CLIENT_REFERENCE},
201
+ filepath: {value: target.filepath},
202
+ async: {value: target.async},
203
+ },
204
+ );
205
+ cachedReference = target[name] = new Proxy(
206
+ reference,
207
+ deepProxyHandlers,
208
+ );
209
}
210
return cachedReference;
211
},
90
- getPrototypeOf(target: {[string]: $FlowFixMe}) {
212
+ getPrototypeOf(target: Function): Object {
213
// Pretend to be a Promise in case anyone asks.
214
return PROMISE_PROTOTYPE;
215
},
220
221
// $FlowFixMe[prop-missing] found when upgrading Flow
222
Module._extensions['.client.js'] = function(module, path) {
101
- const moduleId = url.pathToFileURL(path).href;
102
- const moduleReference: {[string]: any, ...} = {
103
- $$typeof: MODULE_REFERENCE,
104
- filepath: moduleId,
105
- name: '*', // Represents the whole object instead of a particular import.
106
- async: false,
107
- };
223
+ const moduleId: string = (url.pathToFileURL(path).href: any);
224
+ const clientReference: Function = Object.defineProperties(
225
+ (function() {
226
+ throw new Error(
227
+ `Attempted to call the module exports of ${moduleId} from the server` +
228
+ `but it's on the client. It's not possible to invoke a client function from ` +
229
+ `the server, it can only be rendered as a Component or passed to props of a` +
230
+ `Client Component.`,
231
+ );
232
+ }: any),
233
+ {
234
+ // Represents the whole object instead of a particular import.
235
+ name: {value: '*'},
236
+ $$typeof: {value: CLIENT_REFERENCE},
237
+ filepath: {value: moduleId},
238
+ async: {value: false},
239
+ },
240
+ );
241
// $FlowFixMe[incompatible-call] found when upgrading Flow
109
- module.exports = new Proxy(moduleReference, proxyHandlers);
242
+ module.exports = new Proxy(clientReference, proxyHandlers);
243
};
244
245
// $FlowFixMe[prop-missing] found when upgrading Flow