Skip to content

Commit e2659d9

Browse files
authored
[Flight] Tailor the warning for binary data with a toJSON (Node Buffer) (react#36873)
A Node.js `Buffer` carries a `toJSON` method, so Flight serializes it through that method instead of as binary, and it is deserialized as a plain `{type: 'Buffer', data: [...]}` object rather than a `Buffer` or `Uint8Array`. The warning React emits in this case reads "Uint8Array objects are not supported", which is misleading on two counts: the value is actually a `Buffer`, reported as `Uint8Array` only because that is its `Object.prototype.toString` tag, and a real `Uint8Array` is in fact supported, since it has no `toJSON` and serializes as binary. This makes the message especially confusing when binary data such as a font is passed as a serialized value. The first commit adds a test that pins down the current behavior, capturing both the misleading warning and the deserialization to a plain `{type: 'Buffer', data: [...]}` object. The second commit replaces the warning, for values that are `ArrayBuffer.isView`, with one that names the actual cause and the fix: the data is serialized through `toJSON` instead of as binary, so a `Uint8Array` or `ArrayBuffer` should be passed to send binary data. The new branch only fires for a typed array that also carries a `toJSON`, which in practice is a Node `Buffer`; a plain `Uint8Array` or `ArrayBuffer` has no `toJSON`, never reaches this branch, and continues to serialize as binary.
1 parent 39c2c1d commit e2659d9

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,4 +2394,31 @@ describe('ReactFlightDOMNode', () => {
23942394
// the next 1024-byte window.
23952395
expect(result.payload).toEqual(binaryData);
23962396
});
2397+
2398+
// A Node.js Buffer carries a `toJSON` method, so Flight serializes it through
2399+
// that method instead of as binary, and warns. It is therefore deserialized
2400+
// as a plain `{type: 'Buffer', data: [...]}` object rather than a
2401+
// Buffer/Uint8Array.
2402+
it('serializes a Node Buffer through its toJSON and warns', async () => {
2403+
const buffer = Buffer.from([1, 2, 3, 4]);
2404+
const stream = await serverAct(() =>
2405+
ReactServerDOMServer.renderToPipeableStream({font: buffer}),
2406+
);
2407+
assertConsoleErrorDev([
2408+
'Binary data with a toJSON method, such as a Node.js Buffer, is ' +
2409+
'serialized through toJSON instead of as binary. Pass a ' +
2410+
'Uint8Array or ArrayBuffer to send binary data.\n' +
2411+
' {font: Uint8Array}\n' +
2412+
' ^^^^^^^^^^',
2413+
]);
2414+
const readable = new Stream.PassThrough(streamOptions);
2415+
const promise = ReactServerDOMClient.createFromNodeStream(readable, {
2416+
moduleMap: {},
2417+
moduleLoading: webpackModuleLoading,
2418+
});
2419+
stream.pipe(readable);
2420+
const result = await promise;
2421+
expect(Buffer.isBuffer(result.font)).toBe(false);
2422+
expect(result.font).toEqual({type: 'Buffer', data: [1, 2, 3, 4]});
2423+
});
23972424
});

packages/react-server/src/ReactFlightServer.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2822,7 +2822,18 @@ function resolveModel(
28222822
// Call with the server component as the currently rendering component
28232823
// for context.
28242824
callWithDebugContextInDEV(request, task, () => {
2825-
if (objectName(originalValue) !== 'Object') {
2825+
if (ArrayBuffer.isView(originalValue)) {
2826+
// Binary data such as a Node.js Buffer carries a toJSON method, so it
2827+
// is serialized through that method rather than as binary. A plain
2828+
// Uint8Array or ArrayBuffer has no toJSON and is serialized as
2829+
// binary.
2830+
console.error(
2831+
'Binary data with a toJSON method, such as a Node.js Buffer, is ' +
2832+
'serialized through toJSON instead of as binary. Pass a ' +
2833+
'Uint8Array or ArrayBuffer to send binary data.%s',
2834+
describeObjectForErrorMessage(parent, parentPropertyName),
2835+
);
2836+
} else if (objectName(originalValue) !== 'Object') {
28262837
const jsxParentType = jsxChildrenParents.get(parent);
28272838
if (typeof jsxParentType === 'string') {
28282839
console.error(

0 commit comments

Comments
 (0)