143 lines
No EOL
6.3 KiB
JavaScript
Executable file
143 lines
No EOL
6.3 KiB
JavaScript
Executable file
import { getLayoutOrPageModule } from '../../lib/app-dir-module';
|
|
import { parseLoaderTree } from '../../../shared/lib/router/utils/parse-loader-tree';
|
|
import { workAsyncStorage } from '../work-async-storage.external';
|
|
export async function anySegmentHasRuntimePrefetchEnabled(tree) {
|
|
const { mod: layoutOrPageMod } = await getLayoutOrPageModule(tree);
|
|
// TODO(restart-on-cache-miss): Does this work correctly for client page/layout modules?
|
|
const instantConfig = layoutOrPageMod ? layoutOrPageMod.unstable_instant : undefined;
|
|
const hasRuntimePrefetch = instantConfig && typeof instantConfig === 'object' ? instantConfig.prefetch === 'runtime' : false;
|
|
if (hasRuntimePrefetch) {
|
|
return true;
|
|
}
|
|
const { parallelRoutes } = parseLoaderTree(tree);
|
|
for(const parallelRouteKey in parallelRoutes){
|
|
const parallelRoute = parallelRoutes[parallelRouteKey];
|
|
const hasChildRuntimePrefetch = await anySegmentHasRuntimePrefetchEnabled(parallelRoute);
|
|
if (hasChildRuntimePrefetch) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
export async function isPageAllowedToBlock(tree) {
|
|
const { mod: layoutOrPageMod } = await getLayoutOrPageModule(tree);
|
|
// TODO(restart-on-cache-miss): Does this work correctly for client page/layout modules?
|
|
const instantConfig = layoutOrPageMod ? layoutOrPageMod.unstable_instant : undefined;
|
|
// If we encounter a non-false instant config before a instant=false,
|
|
// the page isn't allowed to block. The config expresses a requirement for
|
|
// instant UI, so we should make sure that a static shell exists.
|
|
// (even if it'd use runtime prefetching for client navs)
|
|
if (instantConfig !== undefined) {
|
|
if (typeof instantConfig === 'object') {
|
|
return false;
|
|
} else if (instantConfig === false) {
|
|
return true;
|
|
}
|
|
}
|
|
const { parallelRoutes } = parseLoaderTree(tree);
|
|
for(const parallelRouteKey in parallelRoutes){
|
|
const parallelRoute = parallelRoutes[parallelRouteKey];
|
|
const subtreeIsBlocking = await isPageAllowedToBlock(parallelRoute);
|
|
if (subtreeIsBlocking) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Checks if any segments in the loader tree have `instant` configs that need validating.
|
|
* NOTE: Client navigations call this multiple times, so we cache it.
|
|
* */ // Shared helper (not exported, not cached — called by the cached wrappers)
|
|
async function anySegmentNeedsInstantValidation(rootTree, mode) {
|
|
const segments = await findSegmentsWithInstantConfig(rootTree);
|
|
// Check if there's any configs with `prefetch: 'static'` or `mode: 'instant'`.
|
|
// (If there's only `false`, there's no need to run validation).
|
|
// If any segment has `unstable_disableValidation`, we skip validation for the whole tree.
|
|
let needsValidation = false;
|
|
for (const { config } of segments){
|
|
if (typeof config === 'object') {
|
|
if (config.unstable_disableValidation === true || mode === 'dev' && config.unstable_disableDevValidation === true || mode === 'build' && config.unstable_disableBuildValidation === true) {
|
|
return false;
|
|
}
|
|
// do not short-circuit, some other segment might still have `unstable_disableValidation`
|
|
needsValidation = true;
|
|
}
|
|
}
|
|
return needsValidation;
|
|
}
|
|
export const anySegmentNeedsInstantValidationInDev = cacheScopedToWorkStore(async (rootTree)=>anySegmentNeedsInstantValidation(rootTree, 'dev'));
|
|
export const anySegmentNeedsInstantValidationInBuild = cacheScopedToWorkStore(async (rootTree)=>anySegmentNeedsInstantValidation(rootTree, 'build'));
|
|
export const findSegmentsWithInstantConfig = cacheScopedToWorkStore(async (rootTree)=>{
|
|
const results = [];
|
|
async function visit(tree, path) {
|
|
const { mod: layoutOrPageMod } = await getLayoutOrPageModule(tree);
|
|
// TODO(restart-on-cache-miss): Does this work correctly for client page/layout modules?
|
|
const instantConfig = layoutOrPageMod ? layoutOrPageMod.unstable_instant : undefined;
|
|
if (instantConfig !== undefined) {
|
|
results.push({
|
|
path,
|
|
config: instantConfig
|
|
});
|
|
}
|
|
const { parallelRoutes } = parseLoaderTree(tree);
|
|
for(const parallelRouteKey in parallelRoutes){
|
|
const childTree = parallelRoutes[parallelRouteKey];
|
|
await visit(childTree, [
|
|
...path,
|
|
parallelRouteKey
|
|
]);
|
|
}
|
|
}
|
|
await visit(rootTree, []);
|
|
return results;
|
|
});
|
|
export const resolveInstantConfigSamplesForPage = async (tree)=>{
|
|
const { mod: layoutOrPageMod } = await getLayoutOrPageModule(tree);
|
|
const instantConfig = layoutOrPageMod ? layoutOrPageMod.unstable_instant : undefined;
|
|
let samples = null;
|
|
if (instantConfig !== undefined && typeof instantConfig === 'object' && instantConfig.samples) {
|
|
samples = instantConfig.samples;
|
|
}
|
|
// The samples from inner segments override samples from outer segments,
|
|
// i.e. a page overrides the samples from a layout.
|
|
// We do not perform any merging logic.
|
|
const { parallelRoutes } = parseLoaderTree(tree);
|
|
for(const parallelRouteKey in parallelRoutes){
|
|
if (parallelRouteKey !== 'children') {
|
|
continue;
|
|
}
|
|
const childTree = parallelRoutes[parallelRouteKey];
|
|
const childSamples = await resolveInstantConfigSamplesForPage(childTree);
|
|
if (childSamples !== null) {
|
|
samples = childSamples;
|
|
}
|
|
}
|
|
return samples;
|
|
};
|
|
/**
|
|
* A simple cache wrapper for 1-argument functions.
|
|
* The cache will live as long as the current WorkStore,
|
|
* i.e. it's scoped to a single request.
|
|
*/ function cacheScopedToWorkStore(func) {
|
|
const resultsPerWorkStore = new WeakMap();
|
|
return (arg)=>{
|
|
const workStore = workAsyncStorage.getStore();
|
|
if (!workStore) {
|
|
// No caching.
|
|
return func(arg);
|
|
}
|
|
let results = resultsPerWorkStore.get(workStore);
|
|
if (results && results.has(arg)) {
|
|
return results.get(arg);
|
|
}
|
|
const result = func(arg);
|
|
if (!results) {
|
|
results = new WeakMap();
|
|
resultsPerWorkStore.set(workStore, results);
|
|
}
|
|
results.set(arg, result);
|
|
return result;
|
|
};
|
|
}
|
|
|
|
//# sourceMappingURL=instant-config.js.map
|