Skip to content

Commit fdc8c81

Browse files
authored
[Flight] Client and Server Reference Creation into Runtime (#27033)
We already did this for Server References on the Client so this brings us parity with that. This gives us some more flexibility with changing the runtime implementation without having to affect the loaders. We can also do more in the runtime such as adding `.bind()` support to Server References. I also moved the CommonJS Proxy creation into the runtime helper from the register so that it can be handled in one place. This lets us remove the forks from Next.js since the loaders can be simplified there to just use these helpers. This PR doesn't change the protocol or shape of the objects. They're still specific to each bundler but ideally we should probably move this to shared helpers that can be used by multiple bundler implementations.
1 parent eb2c2f7 commit fdc8c81

19 files changed

+455
-282
lines changed

packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ import {
3838

3939
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
4040

41+
export {
42+
registerServerReference,
43+
registerClientReference,
44+
} from './ReactFlightESMReferences';
45+
4146
function createDrainHandler(destination: Destination, request: Request) {
4247
return () => startFlowing(request, destination);
4348
}

packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -178,18 +178,20 @@ function transformServerModule(
178178
continue;
179179
}
180180
}
181-
181+
if (localNames.size === 0) {
182+
return source;
183+
}
182184
let newSrc = source + '\n\n;';
185+
newSrc +=
186+
'import {registerServerReference} from "react-server-dom-esm/server";\n';
183187
localNames.forEach(function (exported, local) {
184188
if (localTypes.get(local) !== 'function') {
185189
// We first check if the export is a function and if so annotate it.
186190
newSrc += 'if (typeof ' + local + ' === "function") ';
187191
}
188-
newSrc += 'Object.defineProperties(' + local + ',{';
189-
newSrc += '$$typeof: {value: Symbol.for("react.server.reference")},';
190-
newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + exported) + '},';
191-
newSrc += '$$bound: { value: null }';
192-
newSrc += '});\n';
192+
newSrc += 'registerServerReference(' + local + ',';
193+
newSrc += JSON.stringify(url) + ',';
194+
newSrc += JSON.stringify(exported) + ');\n';
193195
});
194196
return newSrc;
195197
}
@@ -313,13 +315,17 @@ async function transformClientModule(
313315

314316
await parseExportNamesInto(body, names, url, loader);
315317

318+
if (names.length === 0) {
319+
return '';
320+
}
321+
316322
let newSrc =
317-
"const CLIENT_REFERENCE = Symbol.for('react.client.reference');\n";
323+
'import {registerClientReference} from "react-server-dom-esm/server";\n';
318324
for (let i = 0; i < names.length; i++) {
319325
const name = names[i];
320326
if (name === 'default') {
321327
newSrc += 'export default ';
322-
newSrc += 'Object.defineProperties(function() {';
328+
newSrc += 'registerClientReference(function() {';
323329
newSrc +=
324330
'throw new Error(' +
325331
JSON.stringify(
@@ -331,7 +337,7 @@ async function transformClientModule(
331337
');';
332338
} else {
333339
newSrc += 'export const ' + name + ' = ';
334-
newSrc += 'Object.defineProperties(function() {';
340+
newSrc += 'registerClientReference(function() {';
335341
newSrc +=
336342
'throw new Error(' +
337343
JSON.stringify(
@@ -341,10 +347,9 @@ async function transformClientModule(
341347
) +
342348
');';
343349
}
344-
newSrc += '},{';
345-
newSrc += '$$typeof: {value: CLIENT_REFERENCE},';
346-
newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + name) + '}';
347-
newSrc += '});\n';
350+
newSrc += '},';
351+
newSrc += JSON.stringify(url) + ',';
352+
newSrc += JSON.stringify(name) + ');\n';
348353
}
349354
return newSrc;
350355
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
11+
12+
export type ServerReference<T: Function> = T & {
13+
$$typeof: symbol,
14+
$$id: string,
15+
$$bound: null | Array<ReactClientValue>,
16+
};
17+
18+
// eslint-disable-next-line no-unused-vars
19+
export type ClientReference<T> = {
20+
$$typeof: symbol,
21+
$$id: string,
22+
};
23+
24+
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
25+
const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
26+
27+
export function isClientReference(reference: Object): boolean {
28+
return reference.$$typeof === CLIENT_REFERENCE_TAG;
29+
}
30+
31+
export function isServerReference(reference: Object): boolean {
32+
return reference.$$typeof === SERVER_REFERENCE_TAG;
33+
}
34+
35+
export function registerClientReference<T>(
36+
proxyImplementation: any,
37+
id: string,
38+
exportName: string,
39+
): ClientReference<T> {
40+
return Object.defineProperties(proxyImplementation, {
41+
$$typeof: {value: CLIENT_REFERENCE_TAG},
42+
$$id: {value: id + '#' + exportName},
43+
});
44+
}
45+
46+
// $FlowFixMe[method-unbinding]
47+
const FunctionBind = Function.prototype.bind;
48+
// $FlowFixMe[method-unbinding]
49+
const ArraySlice = Array.prototype.slice;
50+
function bind(this: ServerReference<any>) {
51+
// $FlowFixMe[unsupported-syntax]
52+
const newFn = FunctionBind.apply(this, arguments);
53+
if (this.$$typeof === SERVER_REFERENCE_TAG) {
54+
// $FlowFixMe[method-unbinding]
55+
const args = ArraySlice.call(arguments, 1);
56+
newFn.$$typeof = SERVER_REFERENCE_TAG;
57+
newFn.$$id = this.$$id;
58+
newFn.$$bound = this.$$bound ? this.$$bound.concat(args) : args;
59+
}
60+
return newFn;
61+
}
62+
63+
export function registerServerReference<T>(
64+
reference: ServerReference<T>,
65+
id: string,
66+
exportName: string,
67+
): ServerReference<T> {
68+
return Object.defineProperties((reference: any), {
69+
$$typeof: {value: SERVER_REFERENCE_TAG},
70+
$$id: {value: id + '#' + exportName},
71+
$$bound: {value: null},
72+
bind: {value: bind},
73+
});
74+
}

packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,16 @@
99

1010
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
1111

12-
export type ClientManifest = string; // base URL on the file system
12+
import type {
13+
ClientReference,
14+
ServerReference,
15+
} from './ReactFlightESMReferences';
1316

14-
export type ServerReference<T: Function> = T & {
15-
$$typeof: symbol,
16-
$$id: string,
17-
$$bound: null | Array<ReactClientValue>,
18-
};
17+
export type {ClientReference, ServerReference};
1918

20-
export type ServerReferenceId = string;
19+
export type ClientManifest = string; // base URL on the file system
2120

22-
// eslint-disable-next-line no-unused-vars
23-
export type ClientReference<T> = {
24-
$$typeof: symbol,
25-
$$id: string,
26-
};
21+
export type ServerReferenceId = string;
2722

2823
export type ClientReferenceMetadata = [
2924
string, // module path
@@ -32,23 +27,14 @@ export type ClientReferenceMetadata = [
3227

3328
export type ClientReferenceKey = string;
3429

35-
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
36-
const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
30+
export {isClientReference, isServerReference} from './ReactFlightESMReferences';
3731

3832
export function getClientReferenceKey(
3933
reference: ClientReference<any>,
4034
): ClientReferenceKey {
4135
return reference.$$id;
4236
}
4337

44-
export function isClientReference(reference: Object): boolean {
45-
return reference.$$typeof === CLIENT_REFERENCE_TAG;
46-
}
47-
48-
export function isServerReference(reference: Object): boolean {
49-
return reference.$$typeof === SERVER_REFERENCE_TAG;
50-
}
51-
5238
export function resolveClientReferenceMetadata<T>(
5339
config: ClientManifest,
5440
clientReference: ClientReference<T>,

packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import {
2727

2828
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
2929

30+
export {
31+
registerServerReference,
32+
registerClientReference,
33+
createClientModuleProxy,
34+
} from './ReactFlightWebpackReferences';
35+
3036
type Options = {
3137
identifierPrefix?: string,
3238
signal?: AbortSignal,

packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import {
2727

2828
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
2929

30+
export {
31+
registerServerReference,
32+
registerClientReference,
33+
createClientModuleProxy,
34+
} from './ReactFlightWebpackReferences';
35+
3036
type Options = {
3137
identifierPrefix?: string,
3238
signal?: AbortSignal,

packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ import {
3838

3939
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
4040

41+
export {
42+
registerServerReference,
43+
registerClientReference,
44+
createClientModuleProxy,
45+
} from './ReactFlightWebpackReferences';
46+
4147
function createDrainHandler(destination: Destination, request: Request) {
4248
return () => startFlowing(request, destination);
4349
}

packages/react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler.js

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,19 @@
99

1010
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
1111

12+
import type {
13+
ClientReference,
14+
ServerReference,
15+
} from './ReactFlightWebpackReferences';
16+
17+
export type {ClientReference, ServerReference};
18+
1219
export type ClientManifest = {
1320
[id: string]: ClientReferenceMetadata,
1421
};
1522

16-
export type ServerReference<T: Function> = T & {
17-
$$typeof: symbol,
18-
$$id: string,
19-
$$bound: null | Array<ReactClientValue>,
20-
};
21-
2223
export type ServerReferenceId = string;
2324

24-
// eslint-disable-next-line no-unused-vars
25-
export type ClientReference<T> = {
26-
$$typeof: symbol,
27-
$$id: string,
28-
$$async: boolean,
29-
};
30-
3125
export type ClientReferenceMetadata = {
3226
id: string,
3327
chunks: Array<string>,
@@ -37,23 +31,17 @@ export type ClientReferenceMetadata = {
3731

3832
export type ClientReferenceKey = string;
3933

40-
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
41-
const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
34+
export {
35+
isClientReference,
36+
isServerReference,
37+
} from './ReactFlightWebpackReferences';
4238

4339
export function getClientReferenceKey(
4440
reference: ClientReference<any>,
4541
): ClientReferenceKey {
4642
return reference.$$async ? reference.$$id + '#async' : reference.$$id;
4743
}
4844

49-
export function isClientReference(reference: Object): boolean {
50-
return reference.$$typeof === CLIENT_REFERENCE_TAG;
51-
}
52-
53-
export function isServerReference(reference: Object): boolean {
54-
return reference.$$typeof === SERVER_REFERENCE_TAG;
55-
}
56-
5745
export function resolveClientReferenceMetadata<T>(
5846
config: ClientManifest,
5947
clientReference: ClientReference<T>,

packages/react-server-dom-webpack/src/ReactFlightWebpackNodeLoader.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -178,18 +178,20 @@ function transformServerModule(
178178
continue;
179179
}
180180
}
181-
181+
if (localNames.size === 0) {
182+
return source;
183+
}
182184
let newSrc = source + '\n\n;';
185+
newSrc +=
186+
'import {registerServerReference} from "react-server-dom-webpack/server";\n';
183187
localNames.forEach(function (exported, local) {
184188
if (localTypes.get(local) !== 'function') {
185189
// We first check if the export is a function and if so annotate it.
186190
newSrc += 'if (typeof ' + local + ' === "function") ';
187191
}
188-
newSrc += 'Object.defineProperties(' + local + ',{';
189-
newSrc += '$$typeof: {value: Symbol.for("react.server.reference")},';
190-
newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + exported) + '},';
191-
newSrc += '$$bound: { value: null }';
192-
newSrc += '});\n';
192+
newSrc += 'registerServerReference(' + local + ',';
193+
newSrc += JSON.stringify(url) + ',';
194+
newSrc += JSON.stringify(exported) + ');\n';
193195
});
194196
return newSrc;
195197
}
@@ -313,13 +315,17 @@ async function transformClientModule(
313315

314316
await parseExportNamesInto(body, names, url, loader);
315317

318+
if (names.length === 0) {
319+
return '';
320+
}
321+
316322
let newSrc =
317-
"const CLIENT_REFERENCE = Symbol.for('react.client.reference');\n";
323+
'import {registerClientReference} from "react-server-dom-webpack/server";\n';
318324
for (let i = 0; i < names.length; i++) {
319325
const name = names[i];
320326
if (name === 'default') {
321327
newSrc += 'export default ';
322-
newSrc += 'Object.defineProperties(function() {';
328+
newSrc += 'registerClientReference(function() {';
323329
newSrc +=
324330
'throw new Error(' +
325331
JSON.stringify(
@@ -331,7 +337,7 @@ async function transformClientModule(
331337
');';
332338
} else {
333339
newSrc += 'export const ' + name + ' = ';
334-
newSrc += 'Object.defineProperties(function() {';
340+
newSrc += 'registerClientReference(function() {';
335341
newSrc +=
336342
'throw new Error(' +
337343
JSON.stringify(
@@ -341,10 +347,9 @@ async function transformClientModule(
341347
) +
342348
');';
343349
}
344-
newSrc += '},{';
345-
newSrc += '$$typeof: {value: CLIENT_REFERENCE},';
346-
newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + name) + '}';
347-
newSrc += '});\n';
350+
newSrc += '},';
351+
newSrc += JSON.stringify(url) + ',';
352+
newSrc += JSON.stringify(name) + ');\n';
348353
}
349354
return newSrc;
350355
}

0 commit comments

Comments
 (0)