Skip to content

Commit c062f27

Browse files
committed
Serialize Map and Set - Client to Server
1 parent 4847e6c commit c062f27

File tree

3 files changed

+79
-9
lines changed

3 files changed

+79
-9
lines changed

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ function serializeBigInt(n: bigint): string {
119119
return '$n' + n.toString(10);
120120
}
121121

122+
function serializeMapID(id: number): string {
123+
return '$Q' + id.toString(16);
124+
}
125+
126+
function serializeSetID(id: number): string {
127+
return '$W' + id.toString(16);
128+
}
129+
122130
function escapeStringValue(value: string): string {
123131
if (value[0] === '$') {
124132
// We need to escape $ prefixed strings since we use those to encode
@@ -229,6 +237,24 @@ export function processReply(
229237
});
230238
return serializeFormDataReference(refId);
231239
}
240+
if (value instanceof Map) {
241+
const partJSON = JSON.stringify(Array.from(value), resolveToJSON);
242+
if (formData === null) {
243+
formData = new FormData();
244+
}
245+
const mapId = nextPartId++;
246+
formData.append(formFieldPrefix + mapId, partJSON);
247+
return serializeMapID(mapId);
248+
}
249+
if (value instanceof Set) {
250+
const partJSON = JSON.stringify(Array.from(value), resolveToJSON);
251+
if (formData === null) {
252+
formData = new FormData();
253+
}
254+
const setId = nextPartId++;
255+
formData.append(formFieldPrefix + setId, partJSON);
256+
return serializeSetID(setId);
257+
}
232258
if (!isArray(value)) {
233259
const iteratorFn = getIteratorFn(value);
234260
if (iteratorFn) {

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,32 @@ describe('ReactFlightDOMReply', () => {
197197
expect(d).toEqual(d2);
198198
expect(d % 1000).toEqual(123); // double-check the milliseconds made it through
199199
});
200+
201+
it('can pass a Map as a reply', async () => {
202+
const objKey = {obj: 'key'};
203+
const m = new Map([
204+
['hi', {greet: 'world'}],
205+
[objKey, 123],
206+
]);
207+
const body = await ReactServerDOMClient.encodeReply(m);
208+
const m2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
209+
210+
expect(m2 instanceof Map).toBe(true);
211+
expect(m2.size).toBe(2);
212+
expect(m2.get('hi').greet).toBe('world');
213+
expect(m2).toEqual(m);
214+
});
215+
216+
it('can pass a Set as a reply', async () => {
217+
const objKey = {obj: 'key'};
218+
const s = new Set(['hi', objKey]);
219+
220+
const body = await ReactServerDOMClient.encodeReply(s);
221+
const s2 = await ReactServerDOMServer.decodeReply(body, webpackServerMap);
222+
223+
expect(s2 instanceof Set).toBe(true);
224+
expect(s2.size).toBe(2);
225+
expect(s2.has('hi')).toBe(true);
226+
expect(s2).toEqual(s);
227+
});
200228
});

packages/react-server/src/ReactFlightReplyServer.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,18 @@ function createModelReject<T>(chunk: SomeChunk<T>): (error: mixed) => void {
364364
return (error: mixed) => triggerErrorOnChunk(chunk, error);
365365
}
366366

367+
function getOutlinedModel(response: Response, id: number): any {
368+
const chunk = getChunk(response, id);
369+
if (chunk.status === RESOLVED_MODEL) {
370+
initializeModelChunk(chunk);
371+
}
372+
if (chunk.status !== INITIALIZED) {
373+
// We know that this is emitted earlier so otherwise it's an error.
374+
throw chunk.reason;
375+
}
376+
return chunk.value;
377+
}
378+
367379
function parseModelString(
368380
response: Response,
369381
parentObject: Object,
@@ -389,17 +401,9 @@ function parseModelString(
389401
case 'F': {
390402
// Server Reference
391403
const id = parseInt(value.slice(2), 16);
392-
const chunk = getChunk(response, id);
393-
if (chunk.status === RESOLVED_MODEL) {
394-
initializeModelChunk(chunk);
395-
}
396-
if (chunk.status !== INITIALIZED) {
397-
// We know that this is emitted earlier so otherwise it's an error.
398-
throw chunk.reason;
399-
}
400404
// TODO: Just encode this in the reference inline instead of as a model.
401405
const metaData: {id: ServerReferenceId, bound: Thenable<Array<any>>} =
402-
chunk.value;
406+
getOutlinedModel(response, id);
403407
return loadServerReference(
404408
response,
405409
metaData.id,
@@ -409,6 +413,18 @@ function parseModelString(
409413
key,
410414
);
411415
}
416+
case 'Q': {
417+
// Map
418+
const id = parseInt(value.slice(2), 16);
419+
const data = getOutlinedModel(response, id);
420+
return new Map(data);
421+
}
422+
case 'W': {
423+
// Set
424+
const id = parseInt(value.slice(2), 16);
425+
const data = getOutlinedModel(response, id);
426+
return new Set(data);
427+
}
412428
case 'K': {
413429
// FormData
414430
const stringId = value.slice(2);

0 commit comments

Comments
 (0)