From 8f2f5168c80dbe34ab6d91832d55490df98f23db Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 10 Mar 2023 12:22:01 -0500 Subject: [PATCH] Add support for undefined in Replies to match client --- .../src/ReactFlightReplyClient.js | 15 +++-- .../src/__tests__/ReactFlightDOMReply-test.js | 60 +++++++++++++++++++ .../src/ReactFlightReplyServer.js | 5 ++ 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index ec0c54bd35278..55bf82184d1c2 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -46,6 +46,7 @@ export type ReactServerValue = | number | symbol | null + | void | Iterable | Array | ReactServerObject @@ -69,6 +70,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 @@ -208,14 +213,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) { diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js new file mode 100644 index 0000000000000..5df671dc65932 --- /dev/null +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js @@ -0,0 +1,60 @@ +/** + * 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 act; +let ReactServerDOMServer; +let ReactServerDOMClient; + +describe('ReactFlightDOMReply', () => { + beforeEach(() => { + jest.resetModules(); + act = require('internal-test-utils').act; + 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); + }); +}); diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js index b8e7d1817afe2..d9de22ca661c4 100644 --- a/packages/react-server/src/ReactFlightReplyServer.js +++ b/packages/react-server/src/ReactFlightReplyServer.js @@ -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);