Skip to content

Commit 643257c

Browse files
authored
[Flight] Serialize functions by reference (#33539)
On pages that have a high number of server components (e.g. common when doing syntax highlighting), the debug outlining can produce extremely large RSC payloads. For example a documentation page I was working on had a 13.8 MB payload. I noticed that a majority of this was the source code for the same function components repeated over and over again (over 4000 times) within `$E()` eval commands. This PR deduplicates the same functions by serializing by reference, similar to what is already done for objects. Doing this reduced the payload size of my page from 13.8 MB to 4.6 MB, and resulted in only 31 evals instead of over 4000. As a result it reduced development page load and hydration time from 4 seconds to 1.5 seconds. It also means the deserialized functions will have reference equality just as they did on the server.
1 parent 06e8995 commit 643257c

File tree

2 files changed

+20
-2
lines changed

2 files changed

+20
-2
lines changed

packages/react-client/src/__tests__/ReactFlight-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3276,6 +3276,7 @@ describe('ReactFlight', () => {
32763276
expect(typeof loggedFn2).toBe('function');
32773277
expect(loggedFn2).not.toBe(foo);
32783278
expect(loggedFn2.toString()).toBe(foo.toString());
3279+
expect(loggedFn2).toBe(loggedFn);
32793280

32803281
const promise = mockConsoleLog.mock.calls[0][1].promise;
32813282
expect(promise).toBeInstanceOf(Promise);

packages/react-server/src/ReactFlightServer.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4105,8 +4105,25 @@ function renderConsoleValue(
41054105
}
41064106

41074107
// Serialize the body of the function as an eval so it can be printed.
4108-
// $FlowFixMe[method-unbinding]
4109-
return serializeEval('(' + Function.prototype.toString.call(value) + ')');
4108+
const writtenObjects = request.writtenObjects;
4109+
const existingReference = writtenObjects.get(value);
4110+
if (existingReference !== undefined) {
4111+
// We've already emitted this function, so we can
4112+
// just refer to that by its existing reference.
4113+
return existingReference;
4114+
}
4115+
4116+
const serializedValue = serializeEval(
4117+
// $FlowFixMe[method-unbinding]
4118+
'(' + Function.prototype.toString.call(value) + ')',
4119+
);
4120+
request.pendingChunks++;
4121+
const id = request.nextChunkId++;
4122+
const processedChunk = encodeReferenceChunk(request, id, serializedValue);
4123+
request.completedRegularChunks.push(processedChunk);
4124+
const reference = serializeByValueID(id);
4125+
writtenObjects.set(value, reference);
4126+
return reference;
41104127
}
41114128

41124129
if (typeof value === 'symbol') {

0 commit comments

Comments
 (0)