[Flight] Serialize weird numbers (#26623)
Sophie Alpert committed
Apr 14, 2023 at 09:28 UTC
ab2385fa388f3d4ebc0b1a9f5a747c82e4aa82ed
6 files changed
+114
-2
packages/react-client/src/ReactFlightClient.js
+16
@@ -559,6 +559,22 @@ export function parseModelString(
559
throw chunk.reason;
560
}
561
}
562
+ case 'I': {
563
+ // $Infinity
564
+ return Infinity;
565
+ }
566
+ case '-': {
567
+ // $-0 or $-Infinity
568
+ if (value[2] === '0') {
569
+ return -0;
570
+ } else {
571
+ return -Infinity;
572
+ }
573
+ }
574
+ case 'N': {
575
+ // $NaN
576
+ return NaN;
577
+ }
578
case 'u': {
579
// matches "$undefined"
580
// Special encoding for `undefined` which can't be serialized as JSON otherwise.
packages/react-client/src/ReactFlightReplyClient.js
+23
-1
@@ -71,6 +71,24 @@ function serializeSymbolReference(name: string): string {
71
return '$S' + name;
72
}
73
74
+function serializeNumber(number: number): string | number {
75
+ if (Number.isFinite(number)) {
76
+ if (number === 0 && 1 / number === -Infinity) {
77
+ return '$-0';
78
+ } else {
79
+ return number;
80
+ }
81
+ } else {
82
+ if (number === Infinity) {
83
+ return '$Infinity';
84
+ } else if (number === -Infinity) {
85
+ return '$-Infinity';
86
+ } else {
87
+ return '$NaN';
88
+ }
89
+ }
90
+}
91
+
92
function serializeUndefined(): string {
93
return '$undefined';
94
}
@@ -224,10 +242,14 @@ export function processReply(
242
return escapeStringValue(value);
243
}
244
227
- if (typeof value === 'boolean' || typeof value === 'number') {
245
+ if (typeof value === 'boolean') {
246
return value;
247
}
248
249
+ if (typeof value === 'number') {
250
+ return serializeNumber(value);
251
+ }
252
+
253
if (typeof value === 'undefined') {
254
return serializeUndefined();
255
}
packages/react-client/src/__tests__/ReactFlight-test.js
+24
@@ -263,6 +263,30 @@ describe('ReactFlight', () => {
263
expect(ReactNoop).toMatchRenderedOutput(null);
264
});
265
266
+ it('can transport weird numbers', async () => {
267
+ const nums = [0, -0, Infinity, -Infinity, NaN];
268
+ function ComponentClient({prop}) {
269
+ expect(prop).not.toBe(nums);
270
+ expect(prop).toEqual(nums);
271
+ expect(prop.every((p, i) => Object.is(p, nums[i]))).toBe(true);
272
+ return `prop: ${prop}`;
273
+ }
274
+ const Component = clientReference(ComponentClient);
275
+
276
+ const model = <Component prop={nums} />;
277
+
278
+ const transport = ReactNoopFlightServer.render(model);
279
+
280
+ await act(async () => {
281
+ ReactNoop.render(await ReactNoopFlightClient.read(transport));
282
+ });
283
+
284
+ expect(ReactNoop).toMatchRenderedOutput(
285
+ // already checked -0 with expects above
286
+ 'prop: 0,0,Infinity,-Infinity,NaN',
287
+ );
288
+ });
289
+
290
it('can transport BigInt', async () => {
291
function ComponentClient({prop}) {
292
return `prop: ${prop} (${typeof prop})`;
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js
+12
@@ -76,6 +76,18 @@ describe('ReactFlightDOMReply', () => {
76
expect(items).toEqual(['A', 'B', 'C']);
77
});
78
79
+ it('can pass weird numbers as a reply', async () => {
80
+ const nums = [0, -0, Infinity, -Infinity, NaN];
81
+ const body = await ReactServerDOMClient.encodeReply(nums);
82
+ const nums2 = await ReactServerDOMServer.decodeReply(
83
+ body,
84
+ webpackServerMap,
85
+ );
86
+
87
+ expect(nums).toEqual(nums2);
88
+ expect(nums.every((n, i) => Object.is(n, nums2[i]))).toBe(true);
89
+ });
90
+
91
it('can pass a BigInt as a reply', async () => {
92
const body = await ReactServerDOMClient.encodeReply(90071992547409910000n);
93
const n = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
packages/react-server/src/ReactFlightReplyServer.js
+16
@@ -397,6 +397,22 @@ function parseModelString(
397
key,
398
);
399
}
400
+ case 'I': {
401
+ // $Infinity
402
+ return Infinity;
403
+ }
404
+ case '-': {
405
+ // $-0 or $-Infinity
406
+ if (value[2] === '0') {
407
+ return -0;
408
+ } else {
409
+ return -Infinity;
410
+ }
411
+ }
412
+ case 'N': {
413
+ // $NaN
414
+ return NaN;
415
+ }
416
case 'u': {
417
// matches "$undefined"
418
// Special encoding for `undefined` which can't be serialized as JSON otherwise.
packages/react-server/src/ReactFlightServer.js
+23
-1
@@ -549,6 +549,24 @@ function serializeProviderReference(name: string): string {
549
return '$P' + name;
550
}
551
552
+function serializeNumber(number: number): string | number {
553
+ if (Number.isFinite(number)) {
554
+ if (number === 0 && 1 / number === -Infinity) {
555
+ return '$-0';
556
+ } else {
557
+ return number;
558
+ }
559
+ } else {
560
+ if (number === Infinity) {
561
+ return '$Infinity';
562
+ } else if (number === -Infinity) {
563
+ return '$-Infinity';
564
+ } else {
565
+ return '$NaN';
566
+ }
567
+ }
568
+}
569
+
570
function serializeUndefined(): string {
571
return '$undefined';
572
}
@@ -877,10 +895,14 @@ export function resolveModelToJSON(
895
return escapeStringValue(value);
896
}
897
880
- if (typeof value === 'boolean' || typeof value === 'number') {
898
+ if (typeof value === 'boolean') {
899
return value;
900
}
901
902
+ if (typeof value === 'number') {
903
+ return serializeNumber(value);
904
+ }
905
+
906
if (typeof value === 'undefined') {
907
return serializeUndefined();
908
}