From fd0511c728e186905b7f0e71f072b4b247d6f29f Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Wed, 29 Mar 2023 18:23:43 +0200 Subject: [PATCH] [Flight] Add support BigInt support (#26479) ## Summary Adds support for sending `BigInt` to Flight and Flight Reply ## How did you test this change? - added tests --- .eslintrc.js | 3 +++ .../react-client/src/ReactFlightClient.js | 4 ++++ .../src/ReactFlightReplyClient.js | 8 +++++--- .../src/__tests__/ReactFlight-test.js | 19 +++++++++++++++++++ .../src/__tests__/ReactFlightDOMReply-test.js | 7 +++++++ .../src/ReactFlightReplyServer.js | 4 ++++ .../react-server/src/ReactFlightServer.js | 9 +++++---- scripts/rollup/validate/eslintrc.esm.js | 1 + scripts/rollup/validate/eslintrc.fb.js | 1 + scripts/rollup/validate/eslintrc.rn.js | 1 + scripts/rollup/validate/eslintrc.umd.js | 1 + 11 files changed, 51 insertions(+), 7 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index aff4fa6ce48bc..bcab1b2756c1c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -449,6 +449,9 @@ module.exports = { $ReadOnlyArray: 'readonly', $Shape: 'readonly', AnimationFrameID: 'readonly', + // For Flow type annotation. Only `BigInt` is valid at runtime. + bigint: 'readonly', + BigInt: 'readonly', Class: 'readonly', ClientRect: 'readonly', CopyInspectedElementPath: 'readonly', diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 2657064dd8af1..32fb4f1c907db 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -564,6 +564,10 @@ export function parseModelString( // Special encoding for `undefined` which can't be serialized as JSON otherwise. return undefined; } + case 'n': { + // BigInt + return BigInt(value.substring(2)); + } default: { // We assume that anything else is a reference ID. const id = parseInt(value.substring(1), 16); diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index d08af17f374d5..2e85d050e9589 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -75,6 +75,10 @@ function serializeUndefined(): string { return '$undefined'; } +function serializeBigInt(n: bigint): string { + return '$n' + n.toString(10); +} + function escapeStringValue(value: string): string { if (value[0] === '$') { // We need to escape $ prefixed strings since we use those to encode @@ -264,9 +268,7 @@ export function processReply( } if (typeof value === 'bigint') { - throw new Error( - `BigInt (${value}) is not yet supported as an argument to a Server Function.`, - ); + return serializeBigInt(value); } throw new Error( diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index be92bb2d9eb01..24fb9657be86f 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -229,6 +229,25 @@ describe('ReactFlight', () => { expect(ReactNoop).toMatchRenderedOutput(null); }); + it('can transport BigInt', async () => { + function ComponentClient({prop}) { + return `prop: ${prop} (${typeof prop})`; + } + const Component = clientReference(ComponentClient); + + const model = ; + + const transport = ReactNoopFlightServer.render(model); + + await act(async () => { + ReactNoop.render(await ReactNoopFlightClient.read(transport)); + }); + + expect(ReactNoop).toMatchRenderedOutput( + 'prop: 90071992547409910000 (bigint)', + ); + }); + it('can render a lazy component as a shared component on the server', async () => { function SharedComponent({text}) { return ( diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js index c53f8b6f5fc53..e09addaf5e842 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js @@ -75,4 +75,11 @@ describe('ReactFlightDOMReply', () => { } expect(items).toEqual(['A', 'B', 'C']); }); + + it('can pass a BigInt as a reply', async () => { + const body = await ReactServerDOMClient.encodeReply(90071992547409910000n); + const n = await ReactServerDOMServer.decodeReply(body, webpackServerMap); + + expect(n).toEqual(90071992547409910000n); + }); }); diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js index a8782d8da5d18..83318c418df28 100644 --- a/packages/react-server/src/ReactFlightReplyServer.js +++ b/packages/react-server/src/ReactFlightReplyServer.js @@ -402,6 +402,10 @@ function parseModelString( // Special encoding for `undefined` which can't be serialized as JSON otherwise. return undefined; } + case 'n': { + // BigInt + return BigInt(value.substring(2)); + } default: { // We assume that anything else is a reference ID. const id = parseInt(value.substring(1), 16); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 56018af087739..1c127c886b7be 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -553,6 +553,10 @@ function serializeUndefined(): string { return '$undefined'; } +function serializeBigInt(n: bigint): string { + return '$n' + n.toString(10); +} + function serializeClientReference( request: Request, parent: @@ -931,10 +935,7 @@ export function resolveModelToJSON( } if (typeof value === 'bigint') { - throw new Error( - `BigInt (${value}) is not yet supported in Client Component props.` + - describeObjectForErrorMessage(parent, key), - ); + return serializeBigInt(value); } throw new Error( diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index ef762a404f396..a9bf9ab5a0bcc 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -7,6 +7,7 @@ module.exports = { }, globals: { // ES 6 + BigInt: 'readonly', Map: 'readonly', Set: 'readonly', Proxy: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 01e5c5e96e246..122e7e3fd4c4d 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -7,6 +7,7 @@ module.exports = { }, globals: { // ES6 + BigInt: 'readonly', Map: 'readonly', Set: 'readonly', Symbol: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index 023f0d652fd92..000c1ae92a7a3 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -7,6 +7,7 @@ module.exports = { }, globals: { // ES6 + BigInt: 'readonly', Map: 'readonly', Set: 'readonly', Symbol: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index d2e1bb721815b..17eae00040551 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -6,6 +6,7 @@ module.exports = { }, globals: { // ES6 + BigInt: 'readonly', Map: 'readonly', Set: 'readonly', Symbol: 'readonly',