.
This commit is contained in:
Generated
Vendored
+217
@@ -0,0 +1,217 @@
|
||||
import { matchSegment } from '../../client/components/match-segments';
|
||||
import { getLinkAndScriptTags } from './get-css-inlined-link-tags';
|
||||
import { getPreloadableFonts } from './get-preloadable-fonts';
|
||||
import { createFlightRouterStateFromLoaderTree, createRouteTreePrefetch } from './create-flight-router-state-from-loader-tree';
|
||||
import { hasLoadingComponentInTree } from './has-loading-component-in-tree';
|
||||
import { addSearchParamsIfPageSegment } from '../../shared/lib/segment';
|
||||
import { createComponentTree } from './create-component-tree';
|
||||
import { getSegmentParam } from '../../shared/lib/router/utils/get-segment-param';
|
||||
/**
|
||||
* Use router state to decide at what common layout to render the page.
|
||||
* This can either be the common layout between two pages or a specific place to start rendering from using the "refetch" marker in the tree.
|
||||
*/ export async function walkTreeWithFlightRouterState({ loaderTreeToFilter, parentParams, flightRouterState, parentIsInsideSharedLayout, rscHead, injectedCSS, injectedJS, injectedFontPreloadTags, rootLayoutIncluded, ctx, preloadCallbacks, MetadataOutlet, hintTree }) {
|
||||
const { renderOpts: { nextFontManifest, experimental }, query, isPrefetch, getDynamicParamFromSegment, parsedRequestHeaders } = ctx;
|
||||
const [segment, parallelRoutes, modules] = loaderTreeToFilter;
|
||||
const parallelRoutesKeys = Object.keys(parallelRoutes);
|
||||
const { layout } = modules;
|
||||
const isLayout = typeof layout !== 'undefined';
|
||||
/**
|
||||
* Checks if the current segment is a root layout.
|
||||
*/ const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded;
|
||||
/**
|
||||
* Checks if the current segment or any level above it has a root layout.
|
||||
*/ const rootLayoutIncludedAtThisLevelOrAbove = rootLayoutIncluded || rootLayoutAtThisLevel;
|
||||
// Because this function walks to a deeper point in the tree to start rendering we have to track the dynamic parameters up to the point where rendering starts
|
||||
const segmentParam = getDynamicParamFromSegment(loaderTreeToFilter);
|
||||
const currentParams = // Handle null case where dynamic param is optional
|
||||
segmentParam && segmentParam.value !== null ? {
|
||||
...parentParams,
|
||||
[segmentParam.param]: segmentParam.value
|
||||
} : parentParams;
|
||||
const actualSegment = addSearchParamsIfPageSegment(segmentParam ? segmentParam.treeSegment : segment, query);
|
||||
/**
|
||||
* Decide if the current segment is where rendering has to start.
|
||||
*/ const renderComponentsOnThisLevel = // No further router state available
|
||||
!flightRouterState || // Segment in router state does not match current segment
|
||||
!matchSegment(actualSegment, flightRouterState[0]) || // Explicit refresh
|
||||
flightRouterState[3] === 'refetch';
|
||||
// Pre-PPR, the `loading` component signals to the router how deep to render the component tree
|
||||
// to ensure prefetches are quick and inexpensive. If there's no `loading` component anywhere in the tree being rendered,
|
||||
// the prefetch will be short-circuited to avoid requesting a potentially very expensive subtree. If there's a `loading`
|
||||
// somewhere in the tree, we'll recursively render the component tree up until we encounter that loading component, and then stop.
|
||||
// Check if we're inside the "new" part of the navigation — inside the
|
||||
// shared layout. In the case of a prefetch, this can be true even if the
|
||||
// segment matches, because the client might send a matching segment to
|
||||
// indicate that it already has the data in its cache. But in order to find
|
||||
// the correct loading boundary, we still need to track where the shared
|
||||
// layout begins.
|
||||
//
|
||||
// TODO: We should rethink the protocol for dynamic requests. It might not
|
||||
// make sense for the client to send a FlightRouterState, since that type is
|
||||
// overloaded with other concerns.
|
||||
const isInsideSharedLayout = renderComponentsOnThisLevel || parentIsInsideSharedLayout || flightRouterState[3] === 'inside-shared-layout';
|
||||
if (isInsideSharedLayout && !experimental.isRoutePPREnabled && // If PPR is disabled, and this is a request for the route tree, then we
|
||||
// never render any components. Only send the router state.
|
||||
(parsedRequestHeaders.isRouteTreePrefetchRequest || // Otherwise, check for the presence of a `loading` component.
|
||||
isPrefetch && !Boolean(modules.loading) && !hasLoadingComponentInTree(loaderTreeToFilter))) {
|
||||
// Send only the router state.
|
||||
// TODO: Even for a dynamic route, we should cache these responses,
|
||||
// because they do not contain any render data (neither segment data nor
|
||||
// the head). They can be made even more cacheable once we move the route
|
||||
// params into a separate data structure.
|
||||
const overriddenSegment = flightRouterState && // TODO: Why does canSegmentBeOverridden exist? Why don't we always just
|
||||
// use `actualSegment`? Is it to avoid overwriting some state that's
|
||||
// tracked by the client? Dig deeper to see if we can simplify this.
|
||||
canSegmentBeOverridden(actualSegment, flightRouterState[0]) ? flightRouterState[0] : actualSegment;
|
||||
const routerState = parsedRequestHeaders.isRouteTreePrefetchRequest ? await createRouteTreePrefetch(loaderTreeToFilter, hintTree, getDynamicParamFromSegment) : await createFlightRouterStateFromLoaderTree(loaderTreeToFilter, hintTree, getDynamicParamFromSegment, query);
|
||||
return [
|
||||
[
|
||||
overriddenSegment,
|
||||
routerState,
|
||||
null,
|
||||
[
|
||||
null,
|
||||
null
|
||||
],
|
||||
true
|
||||
]
|
||||
];
|
||||
}
|
||||
// Similar to the previous branch. This flag is sent by the client to request
|
||||
// only the metadata for a page. No segment data.
|
||||
if (flightRouterState && flightRouterState[3] === 'metadata-only') {
|
||||
const overriddenSegment = flightRouterState && canSegmentBeOverridden(actualSegment, flightRouterState[0]) ? flightRouterState[0] : actualSegment;
|
||||
const routerState = parsedRequestHeaders.isRouteTreePrefetchRequest ? await createRouteTreePrefetch(loaderTreeToFilter, hintTree, getDynamicParamFromSegment) : await createFlightRouterStateFromLoaderTree(loaderTreeToFilter, hintTree, getDynamicParamFromSegment, query);
|
||||
return [
|
||||
[
|
||||
overriddenSegment,
|
||||
routerState,
|
||||
null,
|
||||
rscHead,
|
||||
false
|
||||
]
|
||||
];
|
||||
}
|
||||
if (renderComponentsOnThisLevel) {
|
||||
const overriddenSegment = flightRouterState && // TODO: Why does canSegmentBeOverridden exist? Why don't we always just
|
||||
// use `actualSegment`? Is it to avoid overwriting some state that's
|
||||
// tracked by the client? Dig deeper to see if we can simplify this.
|
||||
canSegmentBeOverridden(actualSegment, flightRouterState[0]) ? flightRouterState[0] : actualSegment;
|
||||
const routerState = await createFlightRouterStateFromLoaderTree(// Create router state using the slice of the loaderTree
|
||||
loaderTreeToFilter, hintTree, getDynamicParamFromSegment, query);
|
||||
// Create component tree using the slice of the loaderTree
|
||||
const seedData = await createComponentTree(// This ensures flightRouterPath is valid and filters down the tree
|
||||
{
|
||||
ctx,
|
||||
loaderTree: loaderTreeToFilter,
|
||||
parentParams: currentParams,
|
||||
parentOptionalCatchAllParamName: null,
|
||||
parentRuntimePrefetchable: false,
|
||||
injectedCSS,
|
||||
injectedJS,
|
||||
injectedFontPreloadTags,
|
||||
// This is intentionally not "rootLayoutIncludedAtThisLevelOrAbove" as createComponentTree starts at the current level and does a check for "rootLayoutAtThisLevel" too.
|
||||
rootLayoutIncluded,
|
||||
preloadCallbacks,
|
||||
authInterrupts: experimental.authInterrupts,
|
||||
MetadataOutlet
|
||||
});
|
||||
return [
|
||||
[
|
||||
overriddenSegment,
|
||||
routerState,
|
||||
seedData,
|
||||
rscHead,
|
||||
false
|
||||
]
|
||||
];
|
||||
}
|
||||
// If we are not rendering on this level we need to check if the current
|
||||
// segment has a layout. If so, we need to track all the used CSS to make
|
||||
// the result consistent.
|
||||
const layoutPath = layout == null ? void 0 : layout[1];
|
||||
const injectedCSSWithCurrentLayout = new Set(injectedCSS);
|
||||
const injectedJSWithCurrentLayout = new Set(injectedJS);
|
||||
const injectedFontPreloadTagsWithCurrentLayout = new Set(injectedFontPreloadTags);
|
||||
if (layoutPath) {
|
||||
getLinkAndScriptTags(layoutPath, injectedCSSWithCurrentLayout, injectedJSWithCurrentLayout, true);
|
||||
getPreloadableFonts(nextFontManifest, layoutPath, injectedFontPreloadTagsWithCurrentLayout);
|
||||
}
|
||||
const paths = [];
|
||||
// Walk through all parallel routes.
|
||||
for (const parallelRouteKey of parallelRoutesKeys){
|
||||
var _hintTree_slots;
|
||||
const parallelRoute = parallelRoutes[parallelRouteKey];
|
||||
const subPaths = await walkTreeWithFlightRouterState({
|
||||
ctx,
|
||||
loaderTreeToFilter: parallelRoute,
|
||||
parentParams: currentParams,
|
||||
flightRouterState: flightRouterState && flightRouterState[1][parallelRouteKey],
|
||||
parentIsInsideSharedLayout: isInsideSharedLayout,
|
||||
rscHead,
|
||||
injectedCSS: injectedCSSWithCurrentLayout,
|
||||
injectedJS: injectedJSWithCurrentLayout,
|
||||
injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout,
|
||||
rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove,
|
||||
preloadCallbacks,
|
||||
MetadataOutlet,
|
||||
hintTree: (hintTree == null ? void 0 : (_hintTree_slots = hintTree.slots) == null ? void 0 : _hintTree_slots[parallelRouteKey]) ?? null
|
||||
});
|
||||
for (const subPath of subPaths){
|
||||
paths.push([
|
||||
actualSegment,
|
||||
parallelRouteKey,
|
||||
...subPath
|
||||
]);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
/**
|
||||
* A simplified version of `walkTreeWithFlightRouterState` that doesn't skip any layouts
|
||||
* but returns a result of the same shape.
|
||||
* Intended to be used for instant validation, where we need the complete tree.
|
||||
*/ export async function createFullTreeFlightDataForNavigation({ loaderTree, rscHead, injectedCSS, injectedJS, injectedFontPreloadTags, ctx, preloadCallbacks, MetadataOutlet }) {
|
||||
var _ctx_renderOpts_prefetchHints;
|
||||
const { renderOpts: { experimental }, query, getDynamicParamFromSegment, pagePath } = ctx;
|
||||
const hintTreeForInitialRender = ((_ctx_renderOpts_prefetchHints = ctx.renderOpts.prefetchHints) == null ? void 0 : _ctx_renderOpts_prefetchHints[pagePath]) ?? null;
|
||||
const routerState = await createFlightRouterStateFromLoaderTree(loaderTree, hintTreeForInitialRender, getDynamicParamFromSegment, query);
|
||||
const rootSegment = routerState[0];
|
||||
const seedData = await createComponentTree({
|
||||
ctx,
|
||||
loaderTree,
|
||||
parentParams: {},
|
||||
parentOptionalCatchAllParamName: null,
|
||||
parentRuntimePrefetchable: false,
|
||||
injectedCSS,
|
||||
injectedJS,
|
||||
injectedFontPreloadTags,
|
||||
rootLayoutIncluded: false,
|
||||
preloadCallbacks,
|
||||
authInterrupts: experimental.authInterrupts,
|
||||
MetadataOutlet
|
||||
});
|
||||
return [
|
||||
[
|
||||
// TODO: app-render slices this Segment off.
|
||||
// why is that valid, and why are we including it in the first place?
|
||||
rootSegment,
|
||||
routerState,
|
||||
seedData,
|
||||
rscHead,
|
||||
false
|
||||
]
|
||||
];
|
||||
}
|
||||
/*
|
||||
* This function is used to determine if an existing segment can be overridden
|
||||
* by the incoming segment.
|
||||
*/ const canSegmentBeOverridden = (existingSegment, segment)=>{
|
||||
var _getSegmentParam;
|
||||
if (Array.isArray(existingSegment) || !Array.isArray(segment)) {
|
||||
return false;
|
||||
}
|
||||
return ((_getSegmentParam = getSegmentParam(existingSegment)) == null ? void 0 : _getSegmentParam.paramName) === segment[0];
|
||||
};
|
||||
|
||||
//# sourceMappingURL=walk-tree-with-flight-router-state.js.map
|
||||
Reference in New Issue
Block a user