Skip to content

Commit 5f5f445

Browse files
committed
Use an explicit temporary reference set on the server
This ensures that objects can only be passed by reference if they're part of the same pair of renders. This also ensures that you can avoid this short cut when it's not possible such as in SSR passes.
1 parent f123dd2 commit 5f5f445

13 files changed

Lines changed: 305 additions & 61 deletions

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,30 @@ export {
4747
registerClientReference,
4848
} from './ReactFlightESMReferences';
4949

50+
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
51+
52+
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
53+
54+
export type {TemporaryReferenceSet};
55+
5056
function createDrainHandler(destination: Destination, request: Request) {
5157
return () => startFlowing(request, destination);
5258
}
5359

60+
function createCancelHandler(request: Request, reason: string) {
61+
return () => {
62+
stopFlowing(request);
63+
// eslint-disable-next-line react-internal/prod-error-codes
64+
abort(request, new Error(reason));
65+
};
66+
}
67+
5468
type Options = {
5569
environmentName?: string,
5670
onError?: (error: mixed) => void,
5771
onPostpone?: (reason: string) => void,
5872
identifierPrefix?: string,
73+
temporaryReferences?: TemporaryReferenceSet,
5974
};
6075

6176
type PipeableStream = {
@@ -75,6 +90,7 @@ function renderToPipeableStream(
7590
options ? options.identifierPrefix : undefined,
7691
options ? options.onPostpone : undefined,
7792
options ? options.environmentName : undefined,
93+
options ? options.temporaryReferences : undefined,
7894
);
7995
let hasStartedFlowing = false;
8096
startWork(request);
@@ -88,10 +104,20 @@ function renderToPipeableStream(
88104
hasStartedFlowing = true;
89105
startFlowing(request, destination);
90106
destination.on('drain', createDrainHandler(destination, request));
107+
destination.on(
108+
'error',
109+
createCancelHandler(
110+
request,
111+
'The destination stream errored while writing data.',
112+
),
113+
);
114+
destination.on(
115+
'close',
116+
createCancelHandler(request, 'The destination stream closed early.'),
117+
);
91118
return destination;
92119
},
93120
abort(reason: mixed) {
94-
stopFlowing(request);
95121
abort(request, reason);
96122
},
97123
};
@@ -155,13 +181,19 @@ function decodeReplyFromBusboy<T>(
155181
function decodeReply<T>(
156182
body: string | FormData,
157183
moduleBasePath: ServerManifest,
184+
options?: {temporaryReferences?: TemporaryReferenceSet},
158185
): Thenable<T> {
159186
if (typeof body === 'string') {
160187
const form = new FormData();
161188
form.append('0', body);
162189
body = form;
163190
}
164-
const response = createResponse(moduleBasePath, '', body);
191+
const response = createResponse(
192+
moduleBasePath,
193+
'',
194+
options ? options.temporaryReferences : undefined,
195+
body,
196+
);
165197
const root = getRoot<T>(response);
166198
close(response);
167199
return root;

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
createRequest,
1717
startWork,
1818
startFlowing,
19+
stopFlowing,
1920
abort,
2021
} from 'react-server/src/ReactFlightServer';
2122

@@ -25,18 +26,28 @@ import {
2526
getRoot,
2627
} from 'react-server/src/ReactFlightReplyServer';
2728

28-
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
29+
import {
30+
decodeAction,
31+
decodeFormState,
32+
} from 'react-server/src/ReactFlightActionServer';
2933

3034
export {
3135
registerServerReference,
3236
registerClientReference,
3337
createClientModuleProxy,
3438
} from './ReactFlightTurbopackReferences';
3539

40+
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
41+
42+
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
43+
44+
export type {TemporaryReferenceSet};
45+
3646
type Options = {
3747
environmentName?: string,
3848
identifierPrefix?: string,
3949
signal?: AbortSignal,
50+
temporaryReferences?: TemporaryReferenceSet,
4051
onError?: (error: mixed) => void,
4152
onPostpone?: (reason: string) => void,
4253
};
@@ -53,6 +64,7 @@ function renderToReadableStream(
5364
options ? options.identifierPrefix : undefined,
5465
options ? options.onPostpone : undefined,
5566
options ? options.environmentName : undefined,
67+
options ? options.temporaryReferences : undefined,
5668
);
5769
if (options && options.signal) {
5870
const signal = options.signal;
@@ -75,7 +87,10 @@ function renderToReadableStream(
7587
pull: (controller): ?Promise<void> => {
7688
startFlowing(request, controller);
7789
},
78-
cancel: (reason): ?Promise<void> => {},
90+
cancel: (reason): ?Promise<void> => {
91+
stopFlowing(request);
92+
abort(request, reason);
93+
},
7994
},
8095
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
8196
{highWaterMark: 0},
@@ -86,16 +101,22 @@ function renderToReadableStream(
86101
function decodeReply<T>(
87102
body: string | FormData,
88103
turbopackMap: ServerManifest,
104+
options?: {temporaryReferences?: TemporaryReferenceSet},
89105
): Thenable<T> {
90106
if (typeof body === 'string') {
91107
const form = new FormData();
92108
form.append('0', body);
93109
body = form;
94110
}
95-
const response = createResponse(turbopackMap, '', body);
111+
const response = createResponse(
112+
turbopackMap,
113+
'',
114+
options ? options.temporaryReferences : undefined,
115+
body,
116+
);
96117
const root = getRoot<T>(response);
97118
close(response);
98119
return root;
99120
}
100121

101-
export {renderToReadableStream, decodeReply, decodeAction};
122+
export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
createRequest,
1717
startWork,
1818
startFlowing,
19+
stopFlowing,
1920
abort,
2021
} from 'react-server/src/ReactFlightServer';
2122

@@ -25,18 +26,28 @@ import {
2526
getRoot,
2627
} from 'react-server/src/ReactFlightReplyServer';
2728

28-
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
29+
import {
30+
decodeAction,
31+
decodeFormState,
32+
} from 'react-server/src/ReactFlightActionServer';
2933

3034
export {
3135
registerServerReference,
3236
registerClientReference,
3337
createClientModuleProxy,
3438
} from './ReactFlightTurbopackReferences';
3539

40+
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
41+
42+
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
43+
44+
export type {TemporaryReferenceSet};
45+
3646
type Options = {
3747
environmentName?: string,
3848
identifierPrefix?: string,
3949
signal?: AbortSignal,
50+
temporaryReferences?: TemporaryReferenceSet,
4051
onError?: (error: mixed) => void,
4152
onPostpone?: (reason: string) => void,
4253
};
@@ -53,6 +64,7 @@ function renderToReadableStream(
5364
options ? options.identifierPrefix : undefined,
5465
options ? options.onPostpone : undefined,
5566
options ? options.environmentName : undefined,
67+
options ? options.temporaryReferences : undefined,
5668
);
5769
if (options && options.signal) {
5870
const signal = options.signal;
@@ -75,7 +87,10 @@ function renderToReadableStream(
7587
pull: (controller): ?Promise<void> => {
7688
startFlowing(request, controller);
7789
},
78-
cancel: (reason): ?Promise<void> => {},
90+
cancel: (reason): ?Promise<void> => {
91+
stopFlowing(request);
92+
abort(request, reason);
93+
},
7994
},
8095
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
8196
{highWaterMark: 0},
@@ -86,16 +101,22 @@ function renderToReadableStream(
86101
function decodeReply<T>(
87102
body: string | FormData,
88103
turbopackMap: ServerManifest,
104+
options?: {temporaryReferences?: TemporaryReferenceSet},
89105
): Thenable<T> {
90106
if (typeof body === 'string') {
91107
const form = new FormData();
92108
form.append('0', body);
93109
body = form;
94110
}
95-
const response = createResponse(turbopackMap, '', body);
111+
const response = createResponse(
112+
turbopackMap,
113+
'',
114+
options ? options.temporaryReferences : undefined,
115+
body,
116+
);
96117
const root = getRoot<T>(response);
97118
close(response);
98119
return root;
99120
}
100121

101-
export {renderToReadableStream, decodeReply, decodeAction};
122+
export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
createRequest,
2323
startWork,
2424
startFlowing,
25+
stopFlowing,
2526
abort,
2627
} from 'react-server/src/ReactFlightServer';
2728

@@ -36,23 +37,41 @@ import {
3637
getRoot,
3738
} from 'react-server/src/ReactFlightReplyServer';
3839

39-
import {decodeAction} from 'react-server/src/ReactFlightActionServer';
40+
import {
41+
decodeAction,
42+
decodeFormState,
43+
} from 'react-server/src/ReactFlightActionServer';
4044

4145
export {
4246
registerServerReference,
4347
registerClientReference,
4448
createClientModuleProxy,
4549
} from './ReactFlightTurbopackReferences';
4650

51+
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
52+
53+
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
54+
55+
export type {TemporaryReferenceSet};
56+
4757
function createDrainHandler(destination: Destination, request: Request) {
4858
return () => startFlowing(request, destination);
4959
}
5060

61+
function createCancelHandler(request: Request, reason: string) {
62+
return () => {
63+
stopFlowing(request);
64+
// eslint-disable-next-line react-internal/prod-error-codes
65+
abort(request, new Error(reason));
66+
};
67+
}
68+
5169
type Options = {
5270
environmentName?: string,
5371
onError?: (error: mixed) => void,
5472
onPostpone?: (reason: string) => void,
5573
identifierPrefix?: string,
74+
temporaryReferences?: TemporaryReferenceSet,
5675
};
5776

5877
type PipeableStream = {
@@ -72,6 +91,7 @@ function renderToPipeableStream(
7291
options ? options.identifierPrefix : undefined,
7392
options ? options.onPostpone : undefined,
7493
options ? options.environmentName : undefined,
94+
options ? options.temporaryReferences : undefined,
7595
);
7696
let hasStartedFlowing = false;
7797
startWork(request);
@@ -85,6 +105,17 @@ function renderToPipeableStream(
85105
hasStartedFlowing = true;
86106
startFlowing(request, destination);
87107
destination.on('drain', createDrainHandler(destination, request));
108+
destination.on(
109+
'error',
110+
createCancelHandler(
111+
request,
112+
'The destination stream errored while writing data.',
113+
),
114+
);
115+
destination.on(
116+
'close',
117+
createCancelHandler(request, 'The destination stream closed early.'),
118+
);
88119
return destination;
89120
},
90121
abort(reason: mixed) {
@@ -151,13 +182,19 @@ function decodeReplyFromBusboy<T>(
151182
function decodeReply<T>(
152183
body: string | FormData,
153184
turbopackMap: ServerManifest,
185+
options?: {temporaryReferences?: TemporaryReferenceSet},
154186
): Thenable<T> {
155187
if (typeof body === 'string') {
156188
const form = new FormData();
157189
form.append('0', body);
158190
body = form;
159191
}
160-
const response = createResponse(turbopackMap, '', body);
192+
const response = createResponse(
193+
turbopackMap,
194+
'',
195+
options ? options.temporaryReferences : undefined,
196+
body,
197+
);
161198
const root = getRoot<T>(response);
162199
close(response);
163200
return root;
@@ -168,4 +205,5 @@ export {
168205
decodeReplyFromBusboy,
169206
decodeReply,
170207
decodeAction,
208+
decodeFormState,
171209
};

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,17 @@ export {
3737
createClientModuleProxy,
3838
} from './ReactFlightWebpackReferences';
3939

40+
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
41+
42+
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
43+
44+
export type {TemporaryReferenceSet};
45+
4046
type Options = {
4147
environmentName?: string,
4248
identifierPrefix?: string,
4349
signal?: AbortSignal,
50+
temporaryReferences?: TemporaryReferenceSet,
4451
onError?: (error: mixed) => void,
4552
onPostpone?: (reason: string) => void,
4653
};
@@ -57,6 +64,7 @@ function renderToReadableStream(
5764
options ? options.identifierPrefix : undefined,
5865
options ? options.onPostpone : undefined,
5966
options ? options.environmentName : undefined,
67+
options ? options.temporaryReferences : undefined,
6068
);
6169
if (options && options.signal) {
6270
const signal = options.signal;
@@ -93,13 +101,19 @@ function renderToReadableStream(
93101
function decodeReply<T>(
94102
body: string | FormData,
95103
webpackMap: ServerManifest,
104+
options?: {temporaryReferences?: TemporaryReferenceSet},
96105
): Thenable<T> {
97106
if (typeof body === 'string') {
98107
const form = new FormData();
99108
form.append('0', body);
100109
body = form;
101110
}
102-
const response = createResponse(webpackMap, '', body);
111+
const response = createResponse(
112+
webpackMap,
113+
'',
114+
options ? options.temporaryReferences : undefined,
115+
body,
116+
);
103117
const root = getRoot<T>(response);
104118
close(response);
105119
return root;

0 commit comments

Comments
 (0)