Skip to content

Commit 9f540fc

Browse files
authored
[Flight] Support streaming of decodeReply in Edge environments (facebook#31852)
We support streaming `multipart/form-data` in Node.js using Busboy since that's kind of the idiomatic ecosystem way for handling these stream there. There's not really anything idiomatic like that for Edge that's universal yet. This adds a version that's basically just `AsyncIterable.from(formData)`. It could also be a `ReadableStream` of those entries since those are also `AsyncIterable`. I imagine that in the future we might add one from a binary `ReadableStream` that does the parsing built-in.
1 parent 8f92ea4 commit 9f540fc

File tree

13 files changed

+196
-0
lines changed

13 files changed

+196
-0
lines changed

packages/react-server-dom-parcel/npm/server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') {
99

1010
exports.renderToReadableStream = s.renderToReadableStream;
1111
exports.decodeReply = s.decodeReply;
12+
exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
1213
exports.decodeAction = s.decodeAction;
1314
exports.decodeFormState = s.decodeFormState;
1415
exports.createClientReference = s.createClientReference;

packages/react-server-dom-parcel/server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
export {
1111
renderToReadableStream,
1212
decodeReply,
13+
decodeReplyFromAsyncIterable,
1314
decodeAction,
1415
decodeFormState,
1516
createClientReference,

packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
type ServerReferenceId,
1818
} from '../client/ReactFlightClientConfigBundlerParcel';
1919

20+
import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
21+
2022
import {
2123
createRequest,
2224
createPrerenderRequest,
@@ -30,6 +32,9 @@ import {
3032
createResponse,
3133
close,
3234
getRoot,
35+
reportGlobalError,
36+
resolveField,
37+
resolveFile,
3338
} from 'react-server/src/ReactFlightReplyServer';
3439

3540
import {
@@ -189,6 +194,50 @@ export function decodeReply<T>(
189194
return root;
190195
}
191196

197+
export function decodeReplyFromAsyncIterable<T>(
198+
iterable: AsyncIterable<[string, string | File]>,
199+
options?: {temporaryReferences?: TemporaryReferenceSet},
200+
): Thenable<T> {
201+
const iterator: AsyncIterator<[string, string | File]> =
202+
iterable[ASYNC_ITERATOR]();
203+
204+
const response = createResponse(
205+
serverManifest,
206+
'',
207+
options ? options.temporaryReferences : undefined,
208+
);
209+
210+
function progress(
211+
entry:
212+
| {done: false, +value: [string, string | File], ...}
213+
| {done: true, +value: void, ...},
214+
) {
215+
if (entry.done) {
216+
close(response);
217+
} else {
218+
const [name, value] = entry.value;
219+
if (typeof value === 'string') {
220+
resolveField(response, name, value);
221+
} else {
222+
resolveFile(response, name, value);
223+
}
224+
iterator.next().then(progress, error);
225+
}
226+
}
227+
function error(reason: Error) {
228+
reportGlobalError(response, reason);
229+
if (typeof (iterator: any).throw === 'function') {
230+
// The iterator protocol doesn't necessarily include this but a generator do.
231+
// $FlowFixMe should be able to pass mixed
232+
iterator.throw(reason).then(error, error);
233+
}
234+
}
235+
236+
iterator.next().then(progress, error);
237+
238+
return getRoot(response);
239+
}
240+
192241
export function decodeAction<T>(body: FormData): Promise<() => T> | null {
193242
return decodeActionImpl(body, serverManifest);
194243
}

packages/react-server-dom-parcel/src/server/react-flight-dom-server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
renderToReadableStream,
1212
prerender as unstable_prerender,
1313
decodeReply,
14+
decodeReplyFromAsyncIterable,
1415
decodeAction,
1516
decodeFormState,
1617
createClientReference,

packages/react-server-dom-turbopack/npm/server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') {
99

1010
exports.renderToReadableStream = s.renderToReadableStream;
1111
exports.decodeReply = s.decodeReply;
12+
exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
1213
exports.decodeAction = s.decodeAction;
1314
exports.decodeFormState = s.decodeFormState;
1415
exports.registerServerReference = s.registerServerReference;

packages/react-server-dom-turbopack/server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
export {
1111
renderToReadableStream,
1212
decodeReply,
13+
decodeReplyFromAsyncIterable,
1314
decodeAction,
1415
decodeFormState,
1516
registerServerReference,

packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import type {Thenable} from 'shared/ReactTypes';
1212
import type {ClientManifest} from './ReactFlightServerConfigTurbopackBundler';
1313
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
1414

15+
import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
16+
1517
import {
1618
createRequest,
1719
createPrerenderRequest,
@@ -25,6 +27,9 @@ import {
2527
createResponse,
2628
close,
2729
getRoot,
30+
reportGlobalError,
31+
resolveField,
32+
resolveFile,
2833
} from 'react-server/src/ReactFlightReplyServer';
2934

3035
import {
@@ -183,10 +188,56 @@ function decodeReply<T>(
183188
return root;
184189
}
185190

191+
function decodeReplyFromAsyncIterable<T>(
192+
iterable: AsyncIterable<[string, string | File]>,
193+
turbopackMap: ServerManifest,
194+
options?: {temporaryReferences?: TemporaryReferenceSet},
195+
): Thenable<T> {
196+
const iterator: AsyncIterator<[string, string | File]> =
197+
iterable[ASYNC_ITERATOR]();
198+
199+
const response = createResponse(
200+
turbopackMap,
201+
'',
202+
options ? options.temporaryReferences : undefined,
203+
);
204+
205+
function progress(
206+
entry:
207+
| {done: false, +value: [string, string | File], ...}
208+
| {done: true, +value: void, ...},
209+
) {
210+
if (entry.done) {
211+
close(response);
212+
} else {
213+
const [name, value] = entry.value;
214+
if (typeof value === 'string') {
215+
resolveField(response, name, value);
216+
} else {
217+
resolveFile(response, name, value);
218+
}
219+
iterator.next().then(progress, error);
220+
}
221+
}
222+
function error(reason: Error) {
223+
reportGlobalError(response, reason);
224+
if (typeof (iterator: any).throw === 'function') {
225+
// The iterator protocol doesn't necessarily include this but a generator do.
226+
// $FlowFixMe should be able to pass mixed
227+
iterator.throw(reason).then(error, error);
228+
}
229+
}
230+
231+
iterator.next().then(progress, error);
232+
233+
return getRoot(response);
234+
}
235+
186236
export {
187237
renderToReadableStream,
188238
prerender,
189239
decodeReply,
240+
decodeReplyFromAsyncIterable,
190241
decodeAction,
191242
decodeFormState,
192243
};

packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
renderToReadableStream,
1212
prerender as unstable_prerender,
1313
decodeReply,
14+
decodeReplyFromAsyncIterable,
1415
decodeAction,
1516
decodeFormState,
1617
registerServerReference,

packages/react-server-dom-webpack/npm/server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ if (process.env.NODE_ENV === 'production') {
99

1010
exports.renderToReadableStream = s.renderToReadableStream;
1111
exports.decodeReply = s.decodeReply;
12+
exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
1213
exports.decodeAction = s.decodeAction;
1314
exports.decodeFormState = s.decodeFormState;
1415
exports.registerServerReference = s.registerServerReference;

packages/react-server-dom-webpack/server.edge.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
export {
1111
renderToReadableStream,
1212
decodeReply,
13+
decodeReplyFromAsyncIterable,
1314
decodeAction,
1415
decodeFormState,
1516
registerServerReference,

0 commit comments

Comments
 (0)