[Flight Reply] Add undefined and Iterable Support (#26365)

These were recently added to ReactClientValue and so needs to be
reflected in ReactServerValue too.
This commit is contained in:
Sebastian Markbåge
2023-03-10 12:47:43 -05:00
committed by GitHub
parent ef8bdbecb6
commit be353d2515
3 changed files with 100 additions and 5 deletions
+17 -5
View File
@@ -15,6 +15,7 @@ import {
REACT_ELEMENT_TYPE,
REACT_LAZY_TYPE,
REACT_PROVIDER_TYPE,
getIteratorFn,
} from 'shared/ReactSymbols';
import {
@@ -46,6 +47,7 @@ export type ReactServerValue =
| number
| symbol
| null
| void
| Iterable<ReactServerValue>
| Array<ReactServerValue>
| ReactServerObject
@@ -69,6 +71,10 @@ function serializeSymbolReference(name: string): string {
return '$S' + name;
}
function serializeUndefined(): string {
return '$undefined';
}
function escapeStringValue(value: string): string {
if (value[0] === '$') {
// We need to escape $ prefixed strings since we use those to encode
@@ -154,6 +160,12 @@ export function processReply(
);
return serializePromiseID(promiseId);
}
if (!isArray(value)) {
const iteratorFn = getIteratorFn(value);
if (iteratorFn) {
return Array.from((value: any));
}
}
if (__DEV__) {
if (value !== null && !isArray(value)) {
@@ -208,14 +220,14 @@ export function processReply(
return escapeStringValue(value);
}
if (
typeof value === 'boolean' ||
typeof value === 'number' ||
typeof value === 'undefined'
) {
if (typeof value === 'boolean' || typeof value === 'number') {
return value;
}
if (typeof value === 'undefined') {
return serializeUndefined();
}
if (typeof value === 'function') {
const metaData = knownServerReferences.get(value);
if (metaData !== undefined) {
@@ -0,0 +1,78 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
// Polyfills for test environment
global.ReadableStream =
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
global.TextEncoder = require('util').TextEncoder;
global.TextDecoder = require('util').TextDecoder;
// let serverExports;
let webpackServerMap;
let ReactServerDOMServer;
let ReactServerDOMClient;
describe('ReactFlightDOMReply', () => {
beforeEach(() => {
jest.resetModules();
const WebpackMock = require('./utils/WebpackMock');
// serverExports = WebpackMock.serverExports;
webpackServerMap = WebpackMock.webpackServerMap;
ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
ReactServerDOMClient = require('react-server-dom-webpack/client');
});
it('can pass undefined as a reply', async () => {
const body = await ReactServerDOMClient.encodeReply(undefined);
const missing = await ReactServerDOMServer.decodeReply(
body,
webpackServerMap,
);
expect(missing).toBe(undefined);
const body2 = await ReactServerDOMClient.encodeReply({
array: [undefined, null, undefined],
prop: undefined,
});
const object = await ReactServerDOMServer.decodeReply(
body2,
webpackServerMap,
);
expect(object.array.length).toBe(3);
expect(object.array[0]).toBe(undefined);
expect(object.array[1]).toBe(null);
expect(object.array[3]).toBe(undefined);
expect(object.prop).toBe(undefined);
// These should really be true but our deserialization doesn't currently deal with it.
expect('3' in object.array).toBe(false);
expect('prop' in object).toBe(false);
});
it('can pass an iterable as a reply', async () => {
const body = await ReactServerDOMClient.encodeReply({
[Symbol.iterator]: function* () {
yield 'A';
yield 'B';
yield 'C';
},
});
const iterable = await ReactServerDOMServer.decodeReply(
body,
webpackServerMap,
);
const items = [];
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const item of iterable) {
items.push(item);
}
expect(items).toEqual(['A', 'B', 'C']);
});
});
+5
View File
@@ -397,6 +397,11 @@ function parseModelString(
key,
);
}
case 'u': {
// matches "$undefined"
// Special encoding for `undefined` which can't be serialized as JSON otherwise.
return undefined;
}
default: {
// We assume that anything else is a reference ID.
const id = parseInt(value.substring(1), 16);