diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js index b73162847d..935f77fa96 100644 --- a/fixtures/flight/src/App.js +++ b/fixtures/flight/src/App.js @@ -120,10 +120,69 @@ async function ServerComponent({noCache}) { return await fetchThirdParty(noCache); } +let veryDeepObject = [ + { + bar: { + baz: { + a: {}, + }, + }, + }, + { + bar: { + baz: { + a: {}, + }, + }, + }, + { + bar: { + baz: { + a: {}, + }, + }, + }, + { + bar: { + baz: { + a: { + b: { + c: { + d: { + e: { + f: { + g: { + h: { + i: { + j: { + k: { + l: { + m: { + yay: 'You reached the end', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +]; + export default async function App({prerender, noCache}) { const res = await fetch('http://localhost:3001/todos'); const todos = await res.json(); + console.log('Expand me:', veryDeepObject); + const dedupedChild = ; const message = getServerState(); return ( diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index bf02c74571..37e7657115 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -1774,6 +1774,40 @@ function applyConstructor( return undefined; } +function defineLazyGetter( + response: Response, + chunk: SomeChunk, + parentObject: Object, + key: string, +): any { + // We don't immediately initialize it even if it's resolved. + // Instead, we wait for the getter to get accessed. + Object.defineProperty(parentObject, key, { + get: function () { + if (chunk.status === RESOLVED_MODEL) { + // If it was now resolved, then we initialize it. This may then discover + // a new set of lazy references that are then asked for eagerly in case + // we get that deep. + initializeModelChunk(chunk); + } + switch (chunk.status) { + case INITIALIZED: { + return chunk.value; + } + case ERRORED: + throw chunk.reason; + } + // Otherwise, we didn't have enough time to load the object before it was + // accessed or the connection closed. So we just log that it was omitted. + // TODO: We should ideally throw here to indicate a difference. + return OMITTED_PROP_ERROR; + }, + enumerable: true, + configurable: false, + }); + return null; +} + function extractIterator(response: Response, model: Array): Iterator { // $FlowFixMe[incompatible-use]: This uses raw Symbols because we're extracting from a native array. return model[Symbol.iterator](); @@ -2014,8 +2048,19 @@ function parseModelString( if (value.length > 2) { const debugChannel = response._debugChannel; if (debugChannel) { - const ref = value.slice(2); - debugChannel('R:' + ref); // Release this reference immediately + const ref = value.slice(2); // We assume this doesn't have a path just id. + const id = parseInt(ref, 16); + if (!response._chunks.has(id)) { + // We haven't seen this id before. Query the server to start sending it. + debugChannel('Q:' + ref); + } + // Start waiting. This now creates a pending chunk if it doesn't already exist. + const chunk = getChunk(response, id); + if (chunk.status === INITIALIZED) { + // We already loaded this before. We can just use the real value. + return chunk.value; + } + return defineLazyGetter(response, chunk, parentObject, key); } } diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index e6e172729f..750fe609ed 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -4820,10 +4820,15 @@ function emitConsoleChunk( const payload = [methodName, stackTrace, owner, env]; // $FlowFixMe[method-unbinding] payload.push.apply(payload, args); - let json = serializeDebugModel(request, 500, payload); + const objectLimit = request.deferredDebugObjects === null ? 500 : 10; + let json = serializeDebugModel( + request, + objectLimit + stackTrace.length, + payload, + ); if (json[0] !== '[') { // This looks like an error. Try a simpler object. - json = serializeDebugModel(request, 500, [ + json = serializeDebugModel(request, 10 + stackTrace.length, [ methodName, stackTrace, owner, @@ -5760,6 +5765,8 @@ export function resolveDebugMessage(request: Request, message: string): void { if (retainedValue !== undefined) { // If we still have this object, and haven't emitted it before, emit it on the stream. const counter = {objectLimit: 10}; + deferredDebugObjects.retained.delete(id); + deferredDebugObjects.existing.delete(retainedValue); emitOutlinedDebugModelChunk(request, id, counter, retainedValue); enqueueFlush(request); }