UNPKG

next

Version:

The React Framework

216 lines (215 loc) 9.93 kB
import { getLayoutOrPageModule } from '../../lib/app-dir-module'; import { parseLoaderTree } from '../../../shared/lib/router/utils/parse-loader-tree'; import { PAGE_SEGMENT_KEY, DEFAULT_SEGMENT_KEY } from '../../../shared/lib/segment'; import { UNDERSCORE_GLOBAL_ERROR_ROUTE, UNDERSCORE_NOT_FOUND_ROUTE } from '../../../shared/lib/entry-constants'; import { workAsyncStorage } from '../work-async-storage.external'; import { InvariantError } from '../../../shared/lib/invariant-error'; /** * True when an unconfigured segment should be treated as implicitly * validated under a non-manual default validation level. Only page and * default segments qualify — layouts do not validate on their own. */ export function isImplicitValidationSegment(segment) { const key = typeof segment === 'string' ? segment : segment[0]; return key === PAGE_SEGMENT_KEY || key.startsWith(PAGE_SEGMENT_KEY) || key === DEFAULT_SEGMENT_KEY; } /** * Routes for the framework-synthesized error and not-found entries. They * have no user-configurable escape hatch (the framework supplies the page * when the user hasn't), so they're excluded from implicit validation under * a non-manual default validation level. Even when the user provides their * own `global-error` or root `not-found`, these pages are special-purpose * error UI — opting them into validation is something the user can do * explicitly via `instant`. */ export function isFrameworkErrorRoute(route) { return route === UNDERSCORE_GLOBAL_ERROR_ROUTE || route === UNDERSCORE_NOT_FOUND_ROUTE; } /** * Matches any `prefetch` config that enables Partial Prefetching for the * segment: 'partial' or 'unstable_eager'. A route with Partial Prefetching * enabled also runtime-caches its navigations, so this gates the runtime * prefetch spawn. */ export async function anySegmentHasPartialPrefetchingEnabled(tree) { const { mod: layoutOrPageMod } = await getLayoutOrPageModule(tree); // TODO(restart-on-cache-miss): Does this work correctly for client page/layout modules? const prefetchConfig = layoutOrPageMod ? layoutOrPageMod.prefetch : undefined; if (prefetchConfig === 'partial' || prefetchConfig === 'unstable_eager') { return true; } const { parallelRoutes } = parseLoaderTree(tree); for(const parallelRouteKey in parallelRoutes){ const parallelRoute = parallelRoutes[parallelRouteKey]; const hasChildPartialPrefetching = await anySegmentHasPartialPrefetchingEnabled(parallelRoute); if (hasChildPartialPrefetching) { 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.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 (instantConfig === false) { return true; } else { return false; } } const { parallelRoutes } = parseLoaderTree(tree); for(const parallelRouteKey in parallelRoutes){ const parallelRoute = parallelRoutes[parallelRouteKey]; const subtreeIsBlocking = await isPageAllowedToBlock(parallelRoute); if (subtreeIsBlocking) { return true; } } return false; } var VALIDATION_LEVEL = /*#__PURE__*/ function(VALIDATION_LEVEL) { VALIDATION_LEVEL[VALIDATION_LEVEL["WARNING"] = 0] = "WARNING"; VALIDATION_LEVEL[VALIDATION_LEVEL["ERROR"] = 1] = "ERROR"; return VALIDATION_LEVEL; }(VALIDATION_LEVEL || {}); /** * Walks the loader tree and checks if any segment has an `instant` config * that needs validating for the given mode. * * - Explicit `instant` exports are checked against mode. * - Page and default segments without an explicit config get implicit * validation when the default validation level applies to this mode. * - `unstable_disableValidation` on any segment kills validation for * the whole tree. */ async function anySegmentNeedsInstantValidation(rootTree, level) { const workStore = workAsyncStorage.getStore(); if (!workStore) { throw Object.defineProperty(new InvariantError('anySegmentNeedsInstantValidation must run inside a WorkStore'), "__NEXT_ERROR_CODE", { value: "E1185", enumerable: false, configurable: true }); } const { validationLevel } = workStore; let baseValidationLevel = 0; let manualValidation = false; switch(validationLevel){ case 'manual-warning': manualValidation = true; // intentional fallthrough case 'warning': baseValidationLevel = 0; break; case 'experimental-manual-error': manualValidation = true; // intentional fallthrough case 'experimental-error': baseValidationLevel = 1; break; default: validationLevel; } const applyDefaultValidation = // We need to be validating the right level level <= baseValidationLevel && // We need to not be in manual validation mode !manualValidation && // We don't validate framework internal routes by default !isFrameworkErrorRoute(workStore.route); let needsValidation = false; let disabled = false; async function visit(tree) { if (disabled) return; const { mod: layoutOrPageMod } = await getLayoutOrPageModule(tree); const instantConfig = layoutOrPageMod ? layoutOrPageMod.instant : undefined; if (instantConfig === false) { // Explicit opt-out. Doesn't itself trigger validation. } else if (instantConfig === true) { // Explicit opt-in using the default level. if (level <= baseValidationLevel) { needsValidation = true; } } else if (typeof instantConfig === 'object' && instantConfig !== null) { if (instantConfig.unstable_disableValidation === true || level === 0 && instantConfig.unstable_disableDevValidation === true || level === 1 && instantConfig.unstable_disableBuildValidation === true) { disabled = true; return; } if (instantConfig.level !== undefined) { const configuredLevel = instantConfig.level === 'experimental-error' ? 1 : 0; if (level <= configuredLevel) { needsValidation = true; } } else if (level <= baseValidationLevel) { needsValidation = true; } } else if (applyDefaultValidation && isImplicitValidationSegment(tree[0])) { // No explicit config. Implicit validation applies to page/default // segments when the default level is active for this mode. needsValidation = true; } const { parallelRoutes } = parseLoaderTree(tree); for(const parallelRouteKey in parallelRoutes){ await visit(parallelRoutes[parallelRouteKey]); if (disabled) return; } } await visit(rootTree); if (disabled) { return false; } return needsValidation; } export const anySegmentNeedsInstantValidationInDev = cacheScopedToWorkStore(async (rootTree)=>anySegmentNeedsInstantValidation(rootTree, 0)); export const anySegmentNeedsInstantValidationInBuild = cacheScopedToWorkStore(async (rootTree)=>anySegmentNeedsInstantValidation(rootTree, 1)); export const resolveInstantConfigSamplesForPage = async (tree)=>{ const { mod: layoutOrPageMod } = await getLayoutOrPageModule(tree); const instantConfig = layoutOrPageMod ? layoutOrPageMod.instant : undefined; let samples = null; if (instantConfig !== undefined && typeof instantConfig === 'object' && instantConfig.unstable_samples) { samples = instantConfig.unstable_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) { throw Object.defineProperty(new InvariantError(`${func.name || 'cacheScopedToWorkStore callee'} must run inside a WorkStore`), "__NEXT_ERROR_CODE", { value: "E1186", enumerable: false, configurable: true }); } 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