Skip to content

Commit bbed0b0

Browse files
sebmarkbageeps1lon
authored andcommitted
Bring ReactFlightClient fixes to FlightReplyServer
We did a bunch of refactors to ReactFlightClient in PRs like #29823 and #33664. This brings a bunch of those related refactors to the equivalent FlightReplyServer. Such as deep resolution of cycles and deferred error handling.
1 parent f3e3f6f commit bbed0b0

File tree

7 files changed

+679
-262
lines changed

7 files changed

+679
-262
lines changed

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -222,31 +222,42 @@ function decodeReplyFromBusboy<T>(
222222
// we queue any fields we receive until the previous file is done.
223223
queuedFields.push(name, value);
224224
} else {
225-
resolveField(response, name, value);
225+
try {
226+
resolveField(response, name, value);
227+
} catch (error) {
228+
busboyStream.destroy(error);
229+
}
226230
}
227231
});
228232
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
229233
if (encoding.toLowerCase() === 'base64') {
230-
throw new Error(
231-
"React doesn't accept base64 encoded file uploads because we don't expect " +
232-
"form data passed from a browser to ever encode data that way. If that's " +
233-
'the wrong assumption, we can easily fix it.',
234+
busboyStream.destroy(
235+
new Error(
236+
"React doesn't accept base64 encoded file uploads because we don't expect " +
237+
"form data passed from a browser to ever encode data that way. If that's " +
238+
'the wrong assumption, we can easily fix it.',
239+
),
234240
);
241+
return;
235242
}
236243
pendingFiles++;
237244
const file = resolveFileInfo(response, name, filename, mimeType);
238245
value.on('data', chunk => {
239246
resolveFileChunk(response, file, chunk);
240247
});
241248
value.on('end', () => {
242-
resolveFileComplete(response, name, file);
243-
pendingFiles--;
244-
if (pendingFiles === 0) {
245-
// Release any queued fields
246-
for (let i = 0; i < queuedFields.length; i += 2) {
247-
resolveField(response, queuedFields[i], queuedFields[i + 1]);
249+
try {
250+
resolveFileComplete(response, name, file);
251+
pendingFiles--;
252+
if (pendingFiles === 0) {
253+
// Release any queued fields
254+
for (let i = 0; i < queuedFields.length; i += 2) {
255+
resolveField(response, queuedFields[i], queuedFields[i + 1]);
256+
}
257+
queuedFields.length = 0;
248258
}
249-
queuedFields.length = 0;
259+
} catch (error) {
260+
busboyStream.destroy(error);
250261
}
251262
});
252263
});

packages/react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientCo
3030

3131
import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
3232

33+
import hasOwnProperty from 'shared/hasOwnProperty';
34+
3335
export type ServerConsumerModuleMap = null | {
3436
[clientId: string]: {
3537
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -228,5 +230,8 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
228230
// default property of this if it was an ESM interop module.
229231
return moduleExports.__esModule ? moduleExports.default : moduleExports;
230232
}
231-
return moduleExports[metadata[NAME]];
233+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
234+
return moduleExports[metadata[NAME]];
235+
}
236+
return (undefined: any);
232237
}

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -224,31 +224,42 @@ function decodeReplyFromBusboy<T>(
224224
// we queue any fields we receive until the previous file is done.
225225
queuedFields.push(name, value);
226226
} else {
227-
resolveField(response, name, value);
227+
try {
228+
resolveField(response, name, value);
229+
} catch (error) {
230+
busboyStream.destroy(error);
231+
}
228232
}
229233
});
230234
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
231235
if (encoding.toLowerCase() === 'base64') {
232-
throw new Error(
233-
"React doesn't accept base64 encoded file uploads because we don't expect " +
234-
"form data passed from a browser to ever encode data that way. If that's " +
235-
'the wrong assumption, we can easily fix it.',
236+
busboyStream.destroy(
237+
new Error(
238+
"React doesn't accept base64 encoded file uploads because we don't expect " +
239+
"form data passed from a browser to ever encode data that way. If that's " +
240+
'the wrong assumption, we can easily fix it.',
241+
),
236242
);
243+
return;
237244
}
238245
pendingFiles++;
239246
const file = resolveFileInfo(response, name, filename, mimeType);
240247
value.on('data', chunk => {
241248
resolveFileChunk(response, file, chunk);
242249
});
243250
value.on('end', () => {
244-
resolveFileComplete(response, name, file);
245-
pendingFiles--;
246-
if (pendingFiles === 0) {
247-
// Release any queued fields
248-
for (let i = 0; i < queuedFields.length; i += 2) {
249-
resolveField(response, queuedFields[i], queuedFields[i + 1]);
251+
try {
252+
resolveFileComplete(response, name, file);
253+
pendingFiles--;
254+
if (pendingFiles === 0) {
255+
// Release any queued fields
256+
for (let i = 0; i < queuedFields.length; i += 2) {
257+
resolveField(response, queuedFields[i], queuedFields[i + 1]);
258+
}
259+
queuedFields.length = 0;
250260
}
251-
queuedFields.length = 0;
261+
} catch (error) {
262+
busboyStream.destroy(error);
252263
}
253264
});
254265
});

packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
} from '../shared/ReactFlightImportMetadata';
2525
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
2626

27+
import hasOwnProperty from 'shared/hasOwnProperty';
28+
2729
export type ServerConsumerModuleMap = {
2830
[clientId: string]: {
2931
[clientExportName: string]: ClientReference<any>,
@@ -158,5 +160,8 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
158160
// default property of this if it was an ESM interop module.
159161
return moduleExports.default;
160162
}
161-
return moduleExports[metadata.name];
163+
if (hasOwnProperty.call(moduleExports, metadata.name)) {
164+
return moduleExports[metadata.name];
165+
}
166+
return (undefined: any);
162167
}

packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientCo
3030

3131
import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
3232

33+
import hasOwnProperty from 'shared/hasOwnProperty';
34+
3335
export type ServerConsumerModuleMap = null | {
3436
[clientId: string]: {
3537
[clientExportName: string]: ClientReferenceManifestEntry,
@@ -249,5 +251,8 @@ export function requireModule<T>(metadata: ClientReference<T>): T {
249251
// default property of this if it was an ESM interop module.
250252
return moduleExports.__esModule ? moduleExports.default : moduleExports;
251253
}
252-
return moduleExports[metadata[NAME]];
254+
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
255+
return moduleExports[metadata[NAME]];
256+
}
257+
return (undefined: any);
253258
}

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -224,31 +224,42 @@ function decodeReplyFromBusboy<T>(
224224
// we queue any fields we receive until the previous file is done.
225225
queuedFields.push(name, value);
226226
} else {
227-
resolveField(response, name, value);
227+
try {
228+
resolveField(response, name, value);
229+
} catch (error) {
230+
busboyStream.destroy(error);
231+
}
228232
}
229233
});
230234
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
231235
if (encoding.toLowerCase() === 'base64') {
232-
throw new Error(
233-
"React doesn't accept base64 encoded file uploads because we don't expect " +
234-
"form data passed from a browser to ever encode data that way. If that's " +
235-
'the wrong assumption, we can easily fix it.',
236+
busboyStream.destroy(
237+
new Error(
238+
"React doesn't accept base64 encoded file uploads because we don't expect " +
239+
"form data passed from a browser to ever encode data that way. If that's " +
240+
'the wrong assumption, we can easily fix it.',
241+
),
236242
);
243+
return;
237244
}
238245
pendingFiles++;
239246
const file = resolveFileInfo(response, name, filename, mimeType);
240247
value.on('data', chunk => {
241248
resolveFileChunk(response, file, chunk);
242249
});
243250
value.on('end', () => {
244-
resolveFileComplete(response, name, file);
245-
pendingFiles--;
246-
if (pendingFiles === 0) {
247-
// Release any queued fields
248-
for (let i = 0; i < queuedFields.length; i += 2) {
249-
resolveField(response, queuedFields[i], queuedFields[i + 1]);
251+
try {
252+
resolveFileComplete(response, name, file);
253+
pendingFiles--;
254+
if (pendingFiles === 0) {
255+
// Release any queued fields
256+
for (let i = 0; i < queuedFields.length; i += 2) {
257+
resolveField(response, queuedFields[i], queuedFields[i + 1]);
258+
}
259+
queuedFields.length = 0;
250260
}
251-
queuedFields.length = 0;
261+
} catch (error) {
262+
busboyStream.destroy(error);
252263
}
253264
});
254265
});

0 commit comments

Comments
 (0)