diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index 18fc2834e1d..742655486dd 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -112,6 +112,7 @@ function escapeStringValue(value: string): string { export function processReply( root: ReactServerValue, + formFieldPrefix: string, resolve: (string | FormData) => void, reject: (error: mixed) => void, ): void { @@ -171,7 +172,7 @@ export function processReply( // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above. const data: FormData = formData; // eslint-disable-next-line react-internal/safe-string-coercion - data.append('' + promiseId, partJSON); + data.append(formFieldPrefix + promiseId, partJSON); pendingParts--; if (pendingParts === 0) { resolve(data); @@ -268,7 +269,7 @@ export function processReply( // The reference to this function came from the same client so we can pass it back. const refId = nextPartId++; // eslint-disable-next-line react-internal/safe-string-coercion - formData.set('' + refId, metaDataJSON); + formData.set(formFieldPrefix + refId, metaDataJSON); return serializeServerReferenceID(refId); } throw new Error( @@ -308,7 +309,7 @@ export function processReply( resolve(json); } else { // Otherwise, we use FormData to let us stream in the result. - formData.set('0', json); + formData.set(formFieldPrefix + '0', json); if (pendingParts === 0) { // $FlowFixMe[incompatible-call] this has already been refined. resolve(formData); diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js index f847a636e6c..c835d0b81d7 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js @@ -124,7 +124,7 @@ function encodeReply( string | URLSearchParams | FormData, > /* We don't use URLSearchParams yet but maybe */ { return new Promise((resolve, reject) => { - processReply(value, resolve, reject); + processReply(value, '', resolve, reject); }); } diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js index d549c10693c..777e4271e6e 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js @@ -22,8 +22,6 @@ import { import { createResponse, close, - resolveField, - resolveFile, getRoot, } from 'react-server/src/ReactFlightReplyServer'; @@ -79,20 +77,12 @@ function decodeReply( body: string | FormData, webpackMap: ServerManifest, ): Thenable { - const response = createResponse(webpackMap); if (typeof body === 'string') { - resolveField(response, 0, body); - } else { - // $FlowFixMe[prop-missing] Flow doesn't know that forEach exists. - body.forEach((value: string | File, key: string) => { - const id = +key; - if (typeof value === 'string') { - resolveField(response, id, value); - } else { - resolveFile(response, id, value); - } - }); + const form = new FormData(); + form.append('0', body); + body = form; } + const response = createResponse(webpackMap, '', body); close(response); return getRoot(response); } diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js index d549c10693c..777e4271e6e 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js @@ -22,8 +22,6 @@ import { import { createResponse, close, - resolveField, - resolveFile, getRoot, } from 'react-server/src/ReactFlightReplyServer'; @@ -79,20 +77,12 @@ function decodeReply( body: string | FormData, webpackMap: ServerManifest, ): Thenable { - const response = createResponse(webpackMap); if (typeof body === 'string') { - resolveField(response, 0, body); - } else { - // $FlowFixMe[prop-missing] Flow doesn't know that forEach exists. - body.forEach((value: string | File, key: string) => { - const id = +key; - if (typeof value === 'string') { - resolveField(response, id, value); - } else { - resolveFile(response, id, value); - } - }); + const form = new FormData(); + form.append('0', body); + body = form; } + const response = createResponse(webpackMap, '', body); close(response); return getRoot(response); } diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js index 98d4291de98..dc78f9a28eb 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js @@ -30,7 +30,6 @@ import { reportGlobalError, close, resolveField, - resolveFile, resolveFileInfo, resolveFileChunk, resolveFileComplete, @@ -88,10 +87,9 @@ function decodeReplyFromBusboy( busboyStream: Busboy, webpackMap: ServerManifest, ): Thenable { - const response = createResponse(webpackMap); + const response = createResponse(webpackMap, ''); busboyStream.on('field', (name, value) => { - const id = +name; - resolveField(response, id, value); + resolveField(response, name, value); }); busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => { if (encoding.toLowerCase() === 'base64') { @@ -101,8 +99,7 @@ function decodeReplyFromBusboy( 'the wrong assumption, we can easily fix it.', ); } - const id = +name; - const file = resolveFileInfo(response, id, filename, mimeType); + const file = resolveFileInfo(response, name, filename, mimeType); value.on('data', chunk => { resolveFileChunk(response, file, chunk); }); @@ -123,20 +120,12 @@ function decodeReply( body: string | FormData, webpackMap: ServerManifest, ): Thenable { - const response = createResponse(webpackMap); if (typeof body === 'string') { - resolveField(response, 0, body); - } else { - // $FlowFixMe[prop-missing] Flow doesn't know that forEach exists. - body.forEach((value: string | File, key: string) => { - const id = +key; - if (typeof value === 'string') { - resolveField(response, id, value); - } else { - resolveFile(response, id, value); - } - }); + const form = new FormData(); + form.append('0', body); + body = form; } + const response = createResponse(webpackMap, '', body); close(response); return getRoot(response); } diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js index a39ccb91bae..f74d1c06769 100644 --- a/packages/react-server/src/ReactFlightReplyServer.js +++ b/packages/react-server/src/ReactFlightReplyServer.js @@ -131,6 +131,8 @@ Chunk.prototype.then = function ( export type Response = { _bundlerConfig: ServerManifest, + _prefix: string, + _formData: FormData, _chunks: Map>, _fromJSON: (key: string, value: JSONValue) => any, }; @@ -309,7 +311,17 @@ function getChunk(response: Response, id: number): SomeChunk { const chunks = response._chunks; let chunk = chunks.get(id); if (!chunk) { - chunk = createPendingChunk(response); + const prefix = response._prefix; + const key = prefix + id; + // Check if we have this field in the backing store already. + const backingEntry = response._formData.get(key); + if (backingEntry != null) { + // We assume that this is a string entry for now. + chunk = createResolvedModelChunk(response, (backingEntry: any)); + } else { + // We're still waiting on this entry to stream in. + chunk = createPendingChunk(response); + } chunks.set(id, chunk); } return chunk; @@ -452,10 +464,16 @@ function parseModelString( return value; } -export function createResponse(bundlerConfig: ServerManifest): Response { +export function createResponse( + bundlerConfig: ServerManifest, + formFieldPrefix: string, + backingFormData?: FormData = new FormData(), +): Response { const chunks: Map> = new Map(); const response: Response = { _bundlerConfig: bundlerConfig, + _prefix: formFieldPrefix, + _formData: backingFormData, _chunks: chunks, _fromJSON: function (this: any, key: string, value: JSONValue) { if (typeof value === 'string') { @@ -470,19 +488,24 @@ export function createResponse(bundlerConfig: ServerManifest): Response { export function resolveField( response: Response, - id: number, - model: string, + key: string, + value: string, ): void { - const chunks = response._chunks; - const chunk = chunks.get(id); - if (!chunk) { - chunks.set(id, createResolvedModelChunk(response, model)); - } else { - resolveModelChunk(chunk, model); + // Add this field to the backing store. + response._formData.append(key, value); + const prefix = response._prefix; + if (key.startsWith(prefix)) { + const chunks = response._chunks; + const id = +key.substr(prefix.length); + const chunk = chunks.get(id); + if (chunk) { + // We were waiting on this key so now we can resolve it. + resolveModelChunk(chunk, value); + } } } -export function resolveFile(response: Response, id: number, file: File): void { +export function resolveFile(response: Response, key: string, file: File): void { throw new Error('Not implemented.'); } @@ -490,7 +513,7 @@ export opaque type FileHandle = {}; export function resolveFileInfo( response: Response, - id: number, + key: string, filename: string, mime: string, ): FileHandle {