UNPKG

next

Version:

The React Framework

873 lines (872 loc) • 41.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { ValidationPrefetchKind: null, collectStagedSegmentData: null, createCombinedPayloadAtDepth: null, createCombinedPayloadStream: null, discoverValidationDepths: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { ValidationPrefetchKind: function() { return ValidationPrefetchKind; }, collectStagedSegmentData: function() { return collectStagedSegmentData; }, createCombinedPayloadAtDepth: function() { return createCombinedPayloadAtDepth; }, createCombinedPayloadStream: function() { return createCombinedPayloadStream; }, discoverValidationDepths: function() { return discoverValidationDepths; } }); const _jsxruntime = require("react/jsx-runtime"); const _invarianterror = require("../../../shared/lib/invariant-error"); const _rsctransport = require("../../../shared/lib/rsc-transport"); const _stagedrendering = require("../staged-rendering"); const _manifestssingleton = require("../manifests-singleton"); const _apprenderrenderutils = require("../app-render-render-utils"); const _workasyncstorageexternal = require("../work-async-storage.external"); const _prospectiverenderutils = require("../prospective-render-utils"); const _createerrorhandler = require("../create-error-handler"); const _boundary = require("../../../client/components/instant-validation/boundary"); const _boundaryconstants = require("./boundary-constants"); const _appdirmodule = require("../../lib/app-dir-module"); const _parseloadertree = require("../../../shared/lib/router/utils/parse-loader-tree"); const _nodestream = require("node:stream"); const _streamutils = require("./stream-utils"); const _client = require("react-server-dom-webpack/client"); const _segment = require("../../../shared/lib/segment"); const _instantconfig = require("./instant-config"); const filterStackFrame = process.env.NODE_ENV !== 'production' ? require('../../lib/source-maps').filterStackFrameDEV : undefined; const findSourceMapURL = process.env.NODE_ENV !== 'production' ? require('../../lib/source-maps').findSourceMapURLDEV : undefined; const debug = process.env.NEXT_PRIVATE_DEBUG_VALIDATION === '1' ? console.log : undefined; function traverseRootSeedDataSegments(initialRSCPayload, processSegment) { const rootNode = initialRSCPayload.t.t; const rootPath = stringifySegment((0, _rsctransport.transportSegmentToSegment)(rootNode.s)); return traverseTransportNodeSegments(rootPath, rootNode, processSegment); } function traverseTransportNodeSegments(path, node, processSegment) { processSegment(path, node.d); const children = node.c; if (children === undefined) { return; } for (const [parallelRouteKey, childNode] of children){ const childPath = createChildSegmentPath(path, parallelRouteKey, (0, _rsctransport.transportSegmentToSegment)(childNode.s)); traverseTransportNodeSegments(childPath, childNode, processSegment); } } function createChildSegmentPath(parentPath, parallelRouteKey, segment) { const parallelRoutePrefix = parallelRouteKey === 'children' ? '' : `@${encodeURIComponent(parallelRouteKey)}/`; return `${parentPath}/${parallelRoutePrefix}${stringifySegment(segment)}`; } function stringifySegment(segment) { return typeof segment === 'string' ? encodeURIComponent(segment) : encodeURIComponent(segment[0]) + '|' + segment[1] + '|' + segment[2]; } async function collectStagedSegmentData(prefetchKind, ComponentMod, renderFlightStream, fullPageChunks, fullPageDebugChunks, startTime, stageEndTimes, clientReferenceManifest, createDebugChannel) { const cache = createSegmentCache(); let partialStages; switch(prefetchKind){ case 2: { partialStages = [ _stagedrendering.RenderStage.ShellStatic, _stagedrendering.RenderStage.PrefetchStatic_prefetchApi, _stagedrendering.RenderStage.PrefetchStatic, _stagedrendering.RenderStage.NavigationStatic, _stagedrendering.RenderStage.PrefetchRuntime ]; break; } case 1: { partialStages = [ _stagedrendering.RenderStage.ShellRuntime, _stagedrendering.RenderStage.PrefetchRuntime_prefetchApi, _stagedrendering.RenderStage.PrefetchRuntime, _stagedrendering.RenderStage.NavigationRuntime ]; break; } case 3: { partialStages = [ _stagedrendering.RenderStage.Static, _stagedrendering.RenderStage.PrefetchRuntime ]; break; } } const doStage = async (targetStage)=>{ const endTime = targetStage !== _stagedrendering.RenderStage.Dynamic ? stageEndTimes[targetStage] : undefined; const firstStage = partialStages[0]; return await collectSegmentDataForStage(ComponentMod, renderFlightStream, fullPageChunks, fullPageDebugChunks, clientReferenceManifest, createDebugChannel, cache, firstStage, targetStage, startTime, endTime); }; for (const targetStage of partialStages){ await doStage(targetStage); } const payload = await doStage(_stagedrendering.RenderStage.Dynamic); return { cache, payload }; } async function collectSegmentDataForStage(ComponentMod, renderFlightStream, fullPageChunks, fullPageDebugChunks, clientReferenceManifest, createDebugChannel, cache, firstStage, targetStage, startTime, endTime) { const debugChannelAbortController = new AbortController(); const debugStream = fullPageDebugChunks ? (0, _streamutils.createNodeStreamFromChunks)(fullPageDebugChunks, debugChannelAbortController.signal) : null; const { stream, controller } = createStagedStreamFromChunks(fullPageChunks); stream.on('end', ()=>{ // When the stream finishes, we have to close the debug stream too, // but delay it to avoid "Connection closed." errors. setImmediate(()=>debugChannelAbortController.abort()); }); // Technically we're just re-encoding, so nothing new should be emitted, // but we add an environment name just in case. const environmentName = ()=>{ const currentStage = controller.currentStage; switch(currentStage){ case _stagedrendering.RenderStage.Before: case _stagedrendering.RenderStage.ShellStatic: case _stagedrendering.RenderStage.PrefetchStatic_prefetchApi: case _stagedrendering.RenderStage.PrefetchStatic: case _stagedrendering.RenderStage.Static: case _stagedrendering.RenderStage.NavigationStatic: return 'Prerender'; case _stagedrendering.RenderStage.ShellRuntime: case _stagedrendering.RenderStage.PrefetchRuntime_prefetchApi: case _stagedrendering.RenderStage.PrefetchRuntime: case _stagedrendering.RenderStage.NavigationRuntime: return 'Prefetch'; case _stagedrendering.RenderStage.Dynamic: return 'Server'; default: currentStage; throw new _invarianterror.InvariantError(`Invalid render stage: ${currentStage}`); } }; // Deserialize the payload partially so that we can traverse the structure. const serverConsumerManifest = { moduleLoading: null, moduleMap: clientReferenceManifest.rscModuleMapping, serverModuleMap: (0, _manifestssingleton.getServerModuleMap)() }; const payloadPromise = (0, _client.createFromNodeStream)(stream, serverConsumerManifest, { findSourceMapURL, debugChannel: debugStream ?? undefined, startTime, // Each stage is decoded with an `endTime` corresponding to // when it finished rendering, so that we can avoid pointing to // IO that finished after the stage is done. // This needs to happen during the first deserialization, because // when we serialize the segments afterwards, React will clamp the // timestamps of IO to the current time, and we will lose the ability // to omit info about IO that hasn't resolved in this stage. endTime }); // We expect the outer structure of the payload to be readable in any stage, // even if the segments are incomplete. // NOTE: This must be done after the `createFromNodeStream` call but *before* // the result is awaited, otherwise we'll deadlock. controller.advanceStage(firstStage); const payload = await payloadPromise; // Deconstruct the payload into separate streams per segment. // We have to preserve the stage information for each of them, // so that we can later render each segment in any stage we need. const head = payload.t.h.r; const segments = new Map(); traverseRootSeedDataSegments(payload, (segmentPath, seedData)=>{ segments.set(segmentPath, createSegmentData(seedData)); }); const pendingTasks = []; const renderIntoStageEntry = async (data, stageEntry)=>{ const segmentDebugChannel = stageEntry.debugChunks ? createDebugChannel() : undefined; const itemStream = renderFlightStream(ComponentMod, data, clientReferenceManifest.clientModules, { filterStackFrame, debugChannel: segmentDebugChannel == null ? void 0 : segmentDebugChannel.serverSide, environmentName, startTime, onError: onFlightRenderError }); await Promise.all([ // accumulate Flight chunks (async ()=>{ for await (const chunk of itemStream){ if (controller.currentStage === _stagedrendering.RenderStage.Before) { throw new _invarianterror.InvariantError('Unexpected chunk emitted in Before stage'); } writeChunk(stageEntry, controller.currentStage, targetStage, chunk); } })(), // accumulate Debug chunks segmentDebugChannel && (async ()=>{ for await (const chunk of segmentDebugChannel.clientSide.readable){ stageEntry.debugChunks.push(chunk); } })() ]); }; // Each stage is rendered in a separate pass (passed in as targetStage), // so we only need two stages: // 1. the target stage // 2. the dynamic stage (for late-release debug info) await (0, _apprenderrenderutils.runInSequentialTasks)(()=>{ if (targetStage !== _stagedrendering.RenderStage.Dynamic) { controller.advanceStage(targetStage); } const withDebugChunks = !!fullPageDebugChunks; { let headCacheItem = cache.head; if (!headCacheItem) { headCacheItem = createSegmentCacheItem(); cache.head = headCacheItem; } const stageEntry = getOrCreateStageEntry(headCacheItem, targetStage, withDebugChunks); pendingTasks.push(renderIntoStageEntry(head, stageEntry)); } for (const [segmentPath, segmentData] of segments){ let segmentCacheItem = cache.segments.get(segmentPath); if (!segmentCacheItem) { segmentCacheItem = createSegmentCacheItem(); cache.segments.set(segmentPath, segmentCacheItem); } const stageEntry = getOrCreateStageEntry(segmentCacheItem, targetStage, withDebugChunks); pendingTasks.push(renderIntoStageEntry(segmentData, stageEntry)); } }, ()=>{ controller.advanceStage(_stagedrendering.RenderStage.Dynamic); }); await Promise.all(pendingTasks); return payload; } function onFlightRenderError(error) { const digest = (0, _createerrorhandler.getDigestForWellKnownError)(error); if (digest) { return digest; } // Forward existing digests if (error && typeof error === 'object' && 'digest' in error && typeof error.digest === 'string') { return error.digest; } // We don't need to log the errors because we would have already done that // when generating the original Flight stream for the whole page. if (process.env.NEXT_DEBUG_BUILD || process.env.__NEXT_VERBOSE_LOGGING) { const workStore = _workasyncstorageexternal.workAsyncStorage.getStore(); (0, _prospectiverenderutils.printDebugThrownValueForProspectiveRender)(error, (workStore == null ? void 0 : workStore.route) ?? 'unknown route', _prospectiverenderutils.Phase.InstantValidation); } } /** * Turns accumulated stage chunks into a stream. * The stream starts out in Static stage, and can be advanced further * using the returned controller object. * Conceptually, this is similar to how we unblock more content * by advancing stages in a regular staged render. * */ function createStagedStreamFromChunks(stageChunks) { // The successive stages are supersets of one another, // so we can index into the dynamic chunks everywhere // and just look at the lengths of the partial stage arrays const allChunks = stageChunks[_stagedrendering.RenderStage.Dynamic]; let chunkIx = 0; let currentStage = _stagedrendering.RenderStage.Before; let closed = false; function emitNewChunks(chunks) { for(; chunkIx < chunks.length; chunkIx++){ stream.push(allChunks[chunkIx]); } } function close() { closed = true; stream.push(null); } const stream = new _nodestream.Readable({ read () {} }); function advanceStage(stage) { if (closed) return true; // NOTE: we don't special handling for skipping stages, // emitNewChunks will emit anything that hasn't been emitted before. currentStage = stage; emitNewChunks(stageChunks[stage]); // If there's no more chunks after this stage, finish the stream. if (chunkIx >= allChunks.length) { close(); return true; } else { return false; } } return { stream, controller: { get currentStage () { return currentStage; }, advanceStage } }; } function writeChunk(stageData, currentStage, targetStage, chunk) { if (currentStage <= targetStage) { stageData.chunks.push(chunk); } stageData.allChunks.push(chunk); } async function createCombinedPayloadStream(ComponentMod, renderFlightStream, payload, extraChunksAbortController, renderSignal, clientReferenceManifest, startTime, isDebugChannelEnabled, createDebugChannel) { // Collect all the chunks so that we're not dependent on timing of the render. let isRenderable = true; const renderableChunks = []; const allChunks = []; const debugChunks = isDebugChannelEnabled ? [] : null; const debugChannel = isDebugChannelEnabled ? createDebugChannel() : null; let streamFinished; await (0, _apprenderrenderutils.runInSequentialTasks)(()=>{ const stream = renderFlightStream(ComponentMod, payload, clientReferenceManifest.clientModules, { filterStackFrame, debugChannel: debugChannel == null ? void 0 : debugChannel.serverSide, startTime, onError (error) { const digest = (0, _createerrorhandler.getDigestForWellKnownError)(error); if (digest) { return digest; } // Forward existing digests if (error && typeof error === 'object' && 'digest' in error && typeof error.digest === 'string') { return error.digest; } // We don't need to log the errors because we would have already done that // when generating the original Flight stream for the whole page. if (process.env.NEXT_DEBUG_BUILD || process.env.__NEXT_VERBOSE_LOGGING) { const workStore = _workasyncstorageexternal.workAsyncStorage.getStore(); (0, _prospectiverenderutils.printDebugThrownValueForProspectiveRender)(error, (workStore == null ? void 0 : workStore.route) ?? 'unknown route', _prospectiverenderutils.Phase.InstantValidation); } } }); streamFinished = Promise.all([ // Accumulate Flight chunks (async ()=>{ for await (const chunk of stream){ allChunks.push(chunk); if (isRenderable) { renderableChunks.push(chunk); } } })(), // Accumulate debug chunks debugChannel && (async ()=>{ for await (const chunk of debugChannel.clientSide.readable){ debugChunks.push(chunk); } })() ]); }, ()=>{ isRenderable = false; extraChunksAbortController.abort(); }); await streamFinished; return { stream: (0, _streamutils.createNodeStreamWithLateRelease)(renderableChunks, allChunks, renderSignal), debugStream: debugChunks ? (0, _streamutils.createNodeStreamFromChunks)(debugChunks, renderSignal) : null }; } async function createValidationHead(cache, releaseSignal, clientReferenceManifest, stage) { const segmentCacheItem = cache.head; if (!segmentCacheItem) { throw new _invarianterror.InvariantError(`Missing segment data: <head>`); } const stageEntry = getStageEntry(segmentCacheItem, stage); return await deserializeFromChunks(stageEntry.chunks, stageEntry.allChunks, stageEntry.debugChunks, releaseSignal, clientReferenceManifest, // NOTE: We're not passing an endTime, because the debug info has already been // truncated to the appropriate `endTime` when the segment cache was filled. { startTime: undefined, endTime: undefined }); } /** * Deserializes a (partial possibly partial) RSC stream, given as a chunk-array. * If the stream is partial, we'll wait for `releaseSignal` to fire * and then complete the deserialization using `allChunks`. * * This is used to obtain a partially-complete model (that might contain unresolved holes) * and then release any late debug info from chunks that came later before we abort the render. * */ function deserializeFromChunks(partialChunks, allChunks, debugChunks, releaseSignal, clientReferenceManifest, timings) { const debugChannelAbortController = new AbortController(); const debugStream = debugChunks ? (0, _streamutils.createNodeStreamFromChunks)(debugChunks, debugChannelAbortController.signal) : null; const serverConsumerManifest = { moduleLoading: null, moduleMap: clientReferenceManifest.rscModuleMapping, serverModuleMap: (0, _manifestssingleton.getServerModuleMap)() }; const segmentStream = partialChunks.length < allChunks.length ? (0, _streamutils.createNodeStreamWithLateRelease)(partialChunks, allChunks, releaseSignal) : (0, _streamutils.createNodeStreamFromChunks)(partialChunks); segmentStream.on('end', ()=>{ // When the stream finishes, we have to close the debug stream too, // but delay it to avoid "Connection closed." errors. setImmediate(()=>debugChannelAbortController.abort()); }); return (0, _client.createFromNodeStream)(segmentStream, serverConsumerManifest, { findSourceMapURL, debugChannel: debugStream ?? undefined, startTime: timings == null ? void 0 : timings.startTime, endTime: timings == null ? void 0 : timings.endTime }); } function createSegmentData(data) { return { node: data.r, // Page payloads always carry the boolean form of `p`; the promise // encoding only appears in per-segment prefetch responses. Treat it // conservatively as partial if it ever shows up here. isPartial: typeof data.p === 'boolean' ? data.p : true, varyParams: data.v }; } /** * Builds a node of the rebuilt transport tree: structure (identity and * hints) comes from the original payload's node, render output from the * validation segment cache. */ function createTransportNodeFromSegment(original, data, children) { const node = { s: original.s, d: { r: data.node, p: data.isPartial, v: data.varyParams } }; if (original.h !== undefined) { node.h = original.h; } if (children !== null) { node.c = children; } return node; } /** * The rebuild walks the loader tree and the original payload's transport * tree in parallel; the two mirror each other because the payload was * rendered from the same loader tree. */ function getOriginalChildNode(original, parallelRouteKey) { var _original_c; const childNode = (_original_c = original.c) == null ? void 0 : _original_c.get(parallelRouteKey); if (childNode === undefined) { throw new _invarianterror.InvariantError(`The payload's transport tree is missing a slot that exists in the ` + `loader tree: ${parallelRouteKey}`); } return childNode; } function createSegmentCache() { return { head: null, segments: new Map() }; } function createSegmentCacheItem() { const result = {}; for (const stage of _stagedrendering.RENDER_STAGE_ADVANCE_ORDER){ result[stage] = null; } return result; } function createSegmentCacheItemStageEntry(withDebugChunks) { return { allChunks: [], chunks: [], debugChunks: withDebugChunks ? [] : null }; } function getOrCreateStageEntry(segmentCacheItem, stage, withDebugChunks) { let data = segmentCacheItem[stage]; if (!data) { data = createSegmentCacheItemStageEntry(withDebugChunks); segmentCacheItem[stage] = data; } return data; } function getStageEntry(segmentCacheItem, stage) { const data = segmentCacheItem[stage]; if (!data) { // Indicates that we didn't fill the cache at this stage. throw new _invarianterror.InvariantError(`Expected segment cache to have data for stage '${_stagedrendering.RenderStage[stage]}'`); } return data; } /** * Whether this segment consumes a URL depth level. Each URL depth * represents a potential navigation boundary. * * The root segment ('') consumes depth 0. Regular segments like * 'dashboard' consume the next depth — whether or not they have a * layout. Route groups, __PAGE__, __DEFAULT__, and /_not-found don't * consume a depth — they share the boundary of their parent. */ function segmentConsumesURLDepth(segment) { // Dynamic segments (tuples) always consume a URL depth. if (typeof segment !== 'string') return true; // Route groups, pages, defaults, and not-found don't consume a depth. if (segment === _segment.PAGE_SEGMENT_KEY || (0, _segment.isGroupSegment)(segment) || segment === _segment.DEFAULT_SEGMENT_KEY || segment === _segment.NOT_FOUND_SEGMENT_KEY) { return false; } // Everything else consumes a depth, including the root segment ''. return true; } function discoverValidationDepths(loaderTree) { const groupDepthsByUrlDepth = []; function recordGroupDepth(urlDepth, groupDepth) { while(groupDepthsByUrlDepth.length <= urlDepth){ groupDepthsByUrlDepth.push(0); } if (groupDepth > groupDepthsByUrlDepth[urlDepth]) { groupDepthsByUrlDepth[urlDepth] = groupDepth; } } // urlDepth tracks the index of the current URL-consuming segment. // Groups accumulate at the same index. When the next URL segment // is reached, it increments the index and resets the group counter. // We start at -1 so the root segment '' increments to 0. function walk(tree, urlDepth, groupDepth) { const segment = tree[0]; const { parallelRoutes } = (0, _parseloadertree.parseLoaderTree)(tree); const consumesDepth = segmentConsumesURLDepth(segment); let nextUrlDepth = urlDepth; let nextGroupDepth = groupDepth; if (consumesDepth) { nextUrlDepth = urlDepth + 1; nextGroupDepth = 0; recordGroupDepth(nextUrlDepth, 0); } else if (typeof segment === 'string' && (0, _segment.isGroupSegment)(segment) && segment !== '(__SLOT__)') { // Count real route groups but not the synthetic '(__SLOT__)' segment // that Next.js inserts for parallel slots. The synthetic group // can't be a real navigation boundary. nextGroupDepth++; recordGroupDepth(urlDepth, nextGroupDepth); } for(const key in parallelRoutes){ walk(parallelRoutes[key], nextUrlDepth, nextGroupDepth); } } walk(loaderTree, -1, 0); return groupDepthsByUrlDepth; } var ValidationPrefetchKind = /*#__PURE__*/ function(ValidationPrefetchKind) { /** App Shells, for `<Link>` without `prefetch={true}`, including session data */ ValidationPrefetchKind[ValidationPrefetchKind["RuntimeAppShell"] = 1] = "RuntimeAppShell"; /** App Shells, for `<Link>` without `prefetch={true}`, only static data */ ValidationPrefetchKind[ValidationPrefetchKind["StaticAppShell"] = 2] = "StaticAppShell"; /** Behavior when Partial Prefetching is not enabled. */ ValidationPrefetchKind[ValidationPrefetchKind["LegacySpeculative"] = 3] = "LegacySpeculative"; return ValidationPrefetchKind; }({}); async function createCombinedPayloadAtDepth(prefetchKind, initialRSCPayload, cache, initialLoaderTree, getDynamicParamFromSegment, depth, groupDepth, releaseSignal, boundaryState, clientReferenceManifest, overrideStageForPartialSegments) { const workStore = _workasyncstorageexternal.workAsyncStorage.getStore(); if (!workStore) { throw new _invarianterror.InvariantError('createCombinedPayloadAtDepth must run inside a WorkStore'); } const { validationLevel, route } = workStore; // Index 0 is reserved for the root config. Slot markers start at 1. const slotStacks = [ null ]; /** * When a segment has multiple parallel routes (a fork), wrap each * slot's render output with a slot marker component. The marker's index * in the component stack maps to `slotStacks` for per-slot error * attribution. Slot markers start at index 1 (index 0 is root). */ function wrapSlotsWithMarkers(slots, results) { if (slots === null || slots.size <= 1) return; for (const [key, slotNode] of slots){ const result = results.get(key); const markerIndex = slotStacks.length; slotStacks.push((result == null ? void 0 : result.createInstantStack) ?? null); const markerName = `${_boundaryconstants.INSTANT_SLOT_MARKER_PREFIX}${markerIndex - 1}${_boundaryconstants.INSTANT_SLOT_MARKER_SUFFIX}`; slotNode.d.r = // eslint-disable-next-line @next/internal/no-ambiguous-jsx -- bundled in the server layer /*#__PURE__*/ (0, _jsxruntime.jsx)(_boundary.SlotMarker, { name: markerName, children: slotNode.d.r }, "sm"); } } function getSegment(loaderTree) { const dynamicParam = getDynamicParamFromSegment(loaderTree); if (dynamicParam) { return dynamicParam.treeSegment; } return loaderTree[0]; } async function buildSharedTransportTree(loaderTree, originalNode, parentPath, key, urlDepthConsumed, groupDepthConsumed) { const { parallelRoutes } = (0, _parseloadertree.parseLoaderTree)(loaderTree); const segment = getSegment(loaderTree); const path = parentPath === null ? stringifySegment(segment) : createChildSegmentPath(parentPath, key, segment); debug == null ? void 0 : debug(` ${path || '/'} - Dynamic`); const segmentCacheItem = cache.segments.get(path); if (!segmentCacheItem) { throw new _invarianterror.InvariantError(`Missing segment data: ${path}`); } const stageEntry = getStageEntry(segmentCacheItem, _stagedrendering.RenderStage.Dynamic); const dynamicChunks = stageEntry.chunks; const segmentData = await deserializeFromChunks(dynamicChunks, dynamicChunks, stageEntry.debugChunks, releaseSignal, clientReferenceManifest, null); const consumesUrlDepth = segmentConsumesURLDepth(segment); const isGroup = typeof segment === 'string' && (0, _segment.isGroupSegment)(segment) && segment !== '(__SLOT__)'; // Advance counters for this segment before the boundary check, // mirroring how discoverValidationDepths counts. URL segments // increment urlDepthConsumed, groups increment groupDepthConsumed. // The synthetic '(__SLOT__)' segment is excluded — it can't be a // real navigation boundary. let nextUrlDepth = urlDepthConsumed; let currentGroupDepth = groupDepthConsumed; if (consumesUrlDepth) { nextUrlDepth++; currentGroupDepth = 0; } else if (isGroup) { currentGroupDepth++; } const pastUrlBoundary = nextUrlDepth > depth; const isBoundary = pastUrlBoundary && currentGroupDepth >= groupDepth; if (isBoundary) { debug == null ? void 0 : debug(` ['${path}' is the boundary (url=${nextUrlDepth}, group=${currentGroupDepth})]`); const finalSegmentData = { ...segmentData, node: // eslint-disable-next-line @next/internal/no-ambiguous-jsx -- bundled in the server layer /*#__PURE__*/ (0, _jsxruntime.jsx)(_boundary.PlaceValidationBoundaryBelowThisLevel, { id: path, children: segmentData.node }, "c") }; let slots = null; const slotResults = new Map(); let requiresInstantUI = false; let createInstantStack = null; let bestConfigDepth = -1; // Collect the first mod file path from each slot's subtree. // Don't include the boundary segment's own layout/page — that // file DID render (it wraps the boundary). What didn't render // is the content inside the children slots. const slotModFilePaths = []; let firstModFilePath = null; for(const parallelRouteKey in parallelRoutes){ const result = await buildNewTransportTree(parallelRoutes[parallelRouteKey], getOriginalChildNode(originalNode, parallelRouteKey), path, parallelRouteKey, 0 /* segmentDepth */ ); slotResults.set(parallelRouteKey, result); if (slots === null) { slots = new Map(); } slots.set(parallelRouteKey, result.node); if (result.firstModFilePath !== null) { slotModFilePaths.push(result.firstModFilePath); if (firstModFilePath === null) { firstModFilePath = result.firstModFilePath; } } if (result.requiresInstantUI) { requiresInstantUI = true; if (result.configDepth > bestConfigDepth || result.configDepth === bestConfigDepth && parallelRouteKey === 'children') { bestConfigDepth = result.configDepth; createInstantStack = result.createInstantStack; } } } // Only require this boundary to render if the subtree has an // instant config. Unconfigured slot subtrees are allowed to not // render (e.g. conditionally excluded by a layout). if (requiresInstantUI) { boundaryState.requiredIds.set(path, slotModFilePaths); } wrapSlotsWithMarkers(slots, slotResults); return { node: createTransportNodeFromSegment(originalNode, finalSegmentData, slots), requiresInstantUI, createInstantStack, firstModFilePath, configDepth: bestConfigDepth }; } // Not at the boundary yet — keep walking as shared. let slots = null; const slotResults = new Map(); let requiresInstantUI = false; let createInstantStack = null; let bestConfigDepth = -1; let firstModFilePath = null; for(const parallelRouteKey in parallelRoutes){ const result = await buildSharedTransportTree(parallelRoutes[parallelRouteKey], getOriginalChildNode(originalNode, parallelRouteKey), path, parallelRouteKey, nextUrlDepth, currentGroupDepth); slotResults.set(parallelRouteKey, result); if (slots === null) { slots = new Map(); } slots.set(parallelRouteKey, result.node); if (firstModFilePath === null) { firstModFilePath = result.firstModFilePath; } if (result.requiresInstantUI) { requiresInstantUI = true; if (result.configDepth > bestConfigDepth || result.configDepth === bestConfigDepth && parallelRouteKey === 'children') { bestConfigDepth = result.configDepth; createInstantStack = result.createInstantStack; } } } wrapSlotsWithMarkers(slots, slotResults); return { node: createTransportNodeFromSegment(originalNode, segmentData, slots), requiresInstantUI, createInstantStack, firstModFilePath, configDepth: bestConfigDepth }; } async function buildNewTransportTree(lt, originalNode, parentPath, key, segmentDepth) { const { parallelRoutes } = (0, _parseloadertree.parseLoaderTree)(lt); const { mod: layoutOrPageMod, filePath: layoutOrPageFilePath } = await (0, _appdirmodule.getLayoutOrPageModule)(lt); const localModFilePath = layoutOrPageFilePath ?? null; const segment = getSegment(lt); const path = parentPath === null ? stringifySegment(segment) : createChildSegmentPath(parentPath, key, segment); let instantConfig = null; let localCreateInstantStack = null; if (layoutOrPageMod !== undefined) { instantConfig = layoutOrPageMod.instant ?? null; // When the default validation level is active and this is a page or // default segment without an explicit config, treat it as if // instant = true was exported. Framework-synthesized error // routes are excluded — see isFrameworkErrorRoute. if (instantConfig === null && validationLevel !== 'manual-warning' && validationLevel !== 'experimental-manual-error' && (0, _instantconfig.isImplicitValidationSegment)(segment) && !(0, _instantconfig.isFrameworkErrorRoute)(route)) { instantConfig = true; } if (instantConfig === true || typeof instantConfig === 'object' && instantConfig !== null) { const rawFactory = layoutOrPageMod.__debugCreateInstantConfigStack; localCreateInstantStack = typeof rawFactory === 'function' ? rawFactory : null; } } const segmentCacheItem = cache.segments.get(path); if (!segmentCacheItem) { throw new _invarianterror.InvariantError(`Missing segment data: ${path}`); } let stage; switch(prefetchKind){ case 2: { stage = overrideStageForPartialSegments ?? _stagedrendering.RenderStage.ShellStatic; break; } case 1: { stage = overrideStageForPartialSegments ?? _stagedrendering.RenderStage.ShellRuntime; break; } case 3: { // In legacy speculative prefetches, we always use static. stage = overrideStageForPartialSegments ?? _stagedrendering.RenderStage.Static; break; } } debug == null ? void 0 : debug(` ${path || '/'} - ${_stagedrendering.RenderStage[stage]}`); const stageEntry = getStageEntry(segmentCacheItem, stage); const segmentData = await deserializeFromChunks(stageEntry.chunks, stageEntry.allChunks, stageEntry.debugChunks, releaseSignal, clientReferenceManifest, // NOTE: We're not passing an endTime, because the debug info has already been // truncated to the appropriate `endTime` when the segment cache was filled. { startTime: undefined, endTime: undefined }); // Build children first, then determine requiresInstantUI. let slots = null; const slotResults = new Map(); let childrenRequireInstantUI = false; let childCreateInstantStack = null; let bestChildConfigDepth = -1; let childFirstModFilePath = null; for(const parallelRouteKey in parallelRoutes){ const childSegmentDepth = segmentConsumesURLDepth(segment) ? segmentDepth + 1 : segmentDepth; const result = await buildNewTransportTree(parallelRoutes[parallelRouteKey], getOriginalChildNode(originalNode, parallelRouteKey), path, parallelRouteKey, childSegmentDepth); slotResults.set(parallelRouteKey, result); if (slots === null) { slots = new Map(); } slots.set(parallelRouteKey, result.node); if (childFirstModFilePath === null) { childFirstModFilePath = result.firstModFilePath; } if (result.requiresInstantUI) { childrenRequireInstantUI = true; if (result.configDepth > bestChildConfigDepth || result.configDepth === bestChildConfigDepth && parallelRouteKey === 'children') { bestChildConfigDepth = result.configDepth; childCreateInstantStack = result.createInstantStack; } } } wrapSlotsWithMarkers(slots, slotResults); // Local config takes precedence over children. let requiresInstantUI; let createInstantStack; let configDepth; if (instantConfig === false) { requiresInstantUI = false; createInstantStack = null; configDepth = -1; } else if (instantConfig === true || typeof instantConfig === 'object' && instantConfig !== null) { requiresInstantUI = true; createInstantStack = localCreateInstantStack; configDepth = segmentDepth; } else { requiresInstantUI = childrenRequireInstantUI; createInstantStack = childCreateInstantStack; configDepth = bestChildConfigDepth; } // First mod we find in DFS order: this segment's own layout/page if // any, otherwise the first non-null we got from a child. const firstModFilePath = localModFilePath ?? childFirstModFilePath; return { node: createTransportNodeFromSegment(originalNode, segmentData, slots), requiresInstantUI, createInstantStack, firstModFilePath, configDepth }; } const { node: rebuiltTree, requiresInstantUI, createInstantStack } = await buildSharedTransportTree(initialLoaderTree, initialRSCPayload.t.t, null, null, 0 /* urlDepthConsumed */ , 0 /* groupDepthConsumed */ ); if (!requiresInstantUI) { return null; } // Set the root config at index 0. This is the fallback for errors // that occur above any fork (no slot marker in the component stack). slotStacks[0] = createInstantStack; let headStage; switch(prefetchKind){ case 2: { headStage = overrideStageForPartialSegments ?? _stagedrendering.RenderStage.ShellStatic; break; } case 1: { headStage = overrideStageForPartialSegments ?? _stagedrendering.RenderStage.ShellRuntime; break; } case 3: { headStage = overrideStageForPartialSegments ?? _stagedrendering.RenderStage.Static; break; } } debug == null ? void 0 : debug(` /_head - ${_stagedrendering.RenderStage[headStage]}`); const head = await createValidationHead(cache, releaseSignal, clientReferenceManifest, headStage); const payload = { ...initialRSCPayload, t: { t: rebuiltTree, h: { r: head, p: initialRSCPayload.t.h.p, v: initialRSCPayload.t.h.v } } }; return { payload, slotStacks }; } //# sourceMappingURL=instant-validation.js.map