UNPKG

next

Version:

The React Framework

527 lines (526 loc) • 26.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { assignErrorIfEmpty: null, buildAppStaticPaths: null, filterUniqueParams: null, filterUniqueRootParamsCombinations: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { assignErrorIfEmpty: function() { return assignErrorIfEmpty; }, buildAppStaticPaths: function() { return buildAppStaticPaths; }, filterUniqueParams: function() { return filterUniqueParams; }, filterUniqueRootParamsCombinations: function() { return filterUniqueRootParamsCombinations; } }); const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path")); const _runwithafter = require("../../server/after/run-with-after"); const _workstore = require("../../server/async-storage/work-store"); const _fallback = require("../../lib/fallback"); const _routematcher = require("../../shared/lib/router/utils/route-matcher"); const _routeregex = require("../../shared/lib/router/utils/route-regex"); const _utils = require("./utils"); const _escapepathdelimiters = /*#__PURE__*/ _interop_require_default(require("../../shared/lib/router/utils/escape-path-delimiters")); const _createincrementalcache = require("../../export/helpers/create-incremental-cache"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function filterUniqueParams(routeParamKeys, routeParams) { // A Map is used to store unique parameter combinations. The key of the Map // is a string representation of the parameter combination, and the value // is the actual `Params` object. const unique = new Map(); // Iterate over each parameter object in the input array. for (const params of routeParams){ let key = '' // Initialize an empty string to build the unique key for the current `params` object. ; // Iterate through the `routeParamKeys` (which are assumed to be sorted). // This consistent order is crucial for generating a stable and unique key // for each parameter combination. for (const paramKey of routeParamKeys){ const value = params[paramKey]; // Construct a part of the key using the parameter key and its value. // A type prefix (`A:` for Array, `S:` for String, `U:` for undefined) is added to the value // to prevent collisions. For example, `['a', 'b']` and `'a,b'` would // otherwise generate the same string representation, leading to incorrect // deduplication. This ensures that different types with the same string // representation are treated as distinct. let valuePart; if (Array.isArray(value)) { valuePart = `A:${value.join(',')}`; } else if (value === undefined) { valuePart = `U:undefined`; } else { valuePart = `S:${value}`; } key += `${paramKey}:${valuePart}|`; } // If the generated key is not already in the `unique` Map, it means this // parameter combination is unique so far. Add it to the Map. if (!unique.has(key)) { unique.set(key, params); } } // Convert the Map's values (the unique `Params` objects) back into an array // and return it. return Array.from(unique.values()); } function filterUniqueRootParamsCombinations(rootParamKeys, routeParams) { // A Map is used to store unique combinations of root parameters. // The key of the Map is a string representation of the root parameter // combination, and the value is the `Params` object containing only // the root parameters. const combinations = new Map(); // Iterate over each parameter object in the input array. for (const params of routeParams){ const combination = {} // Initialize an object to hold only the root parameters. ; let key = '' // Initialize an empty string to build the unique key for the current root parameter combination. ; // Iterate through the `rootParamKeys` (which are assumed to be sorted). // This consistent order is crucial for generating a stable and unique key // for each root parameter combination. for (const rootKey of rootParamKeys){ const value = params[rootKey]; combination[rootKey] = value // Add the root parameter and its value to the combination object. ; // Construct a part of the key using the root parameter key and its value. // A type prefix (`A:` for Array, `S:` for String, `U:` for undefined) is added to the value // to prevent collisions. This ensures that different types with the same // string representation are treated as distinct. let valuePart; if (Array.isArray(value)) { valuePart = `A:${value.join(',')}`; } else if (value === undefined) { valuePart = `U:undefined`; } else { valuePart = `S:${value}`; } key += `${rootKey}:${valuePart}|`; } // If the generated key is not already in the `combinations` Map, it means // this root parameter combination is unique so far. Add it to the Map. if (!combinations.has(key)) { combinations.set(key, combination); } } // Convert the Map's values (the unique root parameter `Params` objects) // back into an array and return it. return Array.from(combinations.values()); } /** * Validates the parameters to ensure they're accessible and have the correct * types. * * @param page - The page to validate. * @param regex - The route regex. * @param isRoutePPREnabled - Whether the route has partial prerendering enabled. * @param routeParamKeys - The keys of the parameters. * @param rootParamKeys - The keys of the root params. * @param routeParams - The list of parameters to validate. * @returns The list of validated parameters. */ function validateParams(page, regex, isRoutePPREnabled, routeParamKeys, rootParamKeys, routeParams) { const valid = []; // Validate that if there are any root params, that the user has provided at // least one value for them only if we're using partial prerendering. if (isRoutePPREnabled && rootParamKeys.length > 0) { if (routeParams.length === 0 || rootParamKeys.some((key)=>routeParams.some((params)=>!(key in params)))) { if (rootParamKeys.length === 1) { throw Object.defineProperty(new Error(`A required root parameter (${rootParamKeys[0]}) was not provided in generateStaticParams for ${page}, please provide at least one value.`), "__NEXT_ERROR_CODE", { value: "E622", enumerable: false, configurable: true }); } throw Object.defineProperty(new Error(`Required root params (${rootParamKeys.join(', ')}) were not provided in generateStaticParams for ${page}, please provide at least one value for each.`), "__NEXT_ERROR_CODE", { value: "E621", enumerable: false, configurable: true }); } } for (const params of routeParams){ const item = {}; for (const key of routeParamKeys){ const { repeat, optional } = regex.groups[key]; let paramValue = params[key]; if (optional && params.hasOwnProperty(key) && (paramValue === null || paramValue === undefined || paramValue === false)) { paramValue = []; } // A parameter is missing, so the rest of the params are not accessible. // We only support this when the route has partial prerendering enabled. // This will make it so that the remaining params are marked as missing so // we can generate a fallback route for them. if (!paramValue && isRoutePPREnabled) { break; } // Perform validation for the parameter based on whether it's a repeat // parameter or not. if (repeat) { if (!Array.isArray(paramValue)) { throw Object.defineProperty(new Error(`A required parameter (${key}) was not provided as an array received ${typeof paramValue} in generateStaticParams for ${page}`), "__NEXT_ERROR_CODE", { value: "E618", enumerable: false, configurable: true }); } } else { if (typeof paramValue !== 'string') { throw Object.defineProperty(new Error(`A required parameter (${key}) was not provided as a string received ${typeof paramValue} in generateStaticParams for ${page}`), "__NEXT_ERROR_CODE", { value: "E617", enumerable: false, configurable: true }); } } item[key] = paramValue; } valid.push(item); } return valid; } function assignErrorIfEmpty(prerenderedRoutes, routeParamKeys) { // If there are no routes to process, exit early. if (prerenderedRoutes.length === 0) { return; } // Initialize the root of the Trie. This node represents the starting point // before any parameters have been considered. const root = { children: new Map(), routes: [] }; // Phase 1: Build the Trie. // Iterate over each prerendered route and insert it into the Trie. // Each route's concrete parameter values form a path in the Trie. for (const route of prerenderedRoutes){ let currentNode = root // Start building the path from the root for each route. ; // Iterate through the sorted parameter keys. The order of keys is crucial // for ensuring that routes with the same concrete parameters follow the // same path in the Trie, regardless of the original order of properties // in the `params` object. for (const key of routeParamKeys){ // Check if the current route actually has a concrete value for this parameter. // If a dynamic segment is not filled (i.e., it's a fallback), it won't have // this property, and we stop building the path for this route at this point. if (route.params.hasOwnProperty(key)) { const value = route.params[key]; // Generate a unique key for the parameter's value. This is critical // to prevent collisions between different data types that might have // the same string representation (e.g., `['a', 'b']` vs `'a,b'`). // A type prefix (`A:` for Array, `S:` for String, `U:` for undefined) // is added to the value to prevent collisions. This ensures that // different types with the same string representation are treated as // distinct. let valueKey; if (Array.isArray(value)) { valueKey = `A:${value.join(',')}`; } else if (value === undefined) { valueKey = `U:undefined`; } else { valueKey = `S:${value}`; } // Look for a child node corresponding to this `valueKey` from the `currentNode`. let childNode = currentNode.children.get(valueKey); if (!childNode) { // If the child node doesn't exist, create a new one and add it to // the current node's children. childNode = { children: new Map(), routes: [] }; currentNode.children.set(valueKey, childNode); } // Move deeper into the Trie to the `childNode` for the next parameter. currentNode = childNode; } } // After processing all concrete parameters for the route, add the full // `PrerenderedRoute` object to the `routes` array of the `currentNode`. // This node represents the unique concrete parameter combination for this route. currentNode.routes.push(route); } // Phase 2: Traverse the Trie to assign the `throwOnEmptyStaticShell` property. // This is done using an iterative Depth-First Search (DFS) approach with an // explicit stack to avoid JavaScript's recursion depth limits (stack overflow) // for very deep routing structures. const stack = [ root ] // Initialize the stack with the root node. ; while(stack.length > 0){ const node = stack.pop()// Pop the next node to process from the stack. ; // `hasChildren` indicates if this node has any more specific concrete // parameter combinations branching off from it. If true, it means this // node represents a prefix for other, more specific routes. const hasChildren = node.children.size > 0; // If the current node has routes associated with it (meaning, routes whose // concrete parameters lead to this node's path in the Trie). if (node.routes.length > 0) { // Determine the minimum number of fallback parameters among all routes // that are associated with this current Trie node. This is used to // identify if a route should not throw on empty static shell relative to another route *at the same level* // of concrete parameters, but with fewer fallback parameters. let minFallbacks = Infinity; for (const r of node.routes){ var _r_fallbackRouteParams; // `fallbackRouteParams?.length ?? 0` handles cases where `fallbackRouteParams` // might be `undefined` or `null`, treating them as 0 length. minFallbacks = Math.min(minFallbacks, ((_r_fallbackRouteParams = r.fallbackRouteParams) == null ? void 0 : _r_fallbackRouteParams.length) ?? 0); } // Now, for each `PrerenderedRoute` associated with this node: for (const route of node.routes){ // A route is ok not to throw on an empty static shell (and thus // `throwOnEmptyStaticShell` should be `false`) if either of the // following conditions is met: // 1. `hasChildren` is true: This node has further concrete parameter children. // This means the current route is a parent to more specific routes (e.g., // `/blog/[slug]` should not throw when concrete routes like `/blog/first-post` exist). // OR // 2. `route.fallbackRouteParams.length > minFallbacks`: This route has // more fallback parameters than another route at the same Trie node. // This implies the current route is a more general version that should not throw // compared to a more specific route that has fewer fallback parameters // (e.g., `/1234/[...slug]` should not throw relative to `/[id]/[...slug]`). if (hasChildren || route.fallbackRouteParams && route.fallbackRouteParams.length > minFallbacks) { route.throwOnEmptyStaticShell = false // Should not throw on empty static shell. ; } else { route.throwOnEmptyStaticShell = true // Should throw on empty static shell. ; } } } // Add all children of the current node to the stack. This ensures that // the traversal continues to explore deeper paths in the Trie. for (const child of node.children.values()){ stack.push(child); } } } async function buildAppStaticPaths({ dir, page, distDir, dynamicIO, authInterrupts, segments, isrFlushToDisk, cacheHandler, cacheLifeProfiles, requestHeaders, cacheHandlers, maxMemoryCacheSize, fetchCacheKeyPrefix, nextConfigOutput, ComponentMod, isRoutePPREnabled = false, buildId, rootParamKeys }) { if (segments.some((generate)=>{ var _generate_config; return ((_generate_config = generate.config) == null ? void 0 : _generate_config.dynamicParams) === true; }) && nextConfigOutput === 'export') { throw Object.defineProperty(new Error('"dynamicParams: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports'), "__NEXT_ERROR_CODE", { value: "E393", enumerable: false, configurable: true }); } ComponentMod.patchFetch(); const incrementalCache = await (0, _createincrementalcache.createIncrementalCache)({ dir, distDir, cacheHandler, cacheHandlers, requestHeaders, fetchCacheKeyPrefix, flushToDisk: isrFlushToDisk, cacheMaxMemorySize: maxMemoryCacheSize }); const regex = (0, _routeregex.getRouteRegex)(page); const routeParamKeys = Object.keys((0, _routematcher.getRouteMatcher)(regex)(page) || {}); const afterRunner = new _runwithafter.AfterRunner(); const store = (0, _workstore.createWorkStore)({ page, // We're discovering the parameters here, so we don't have any unknown // ones. fallbackRouteParams: null, renderOpts: { incrementalCache, cacheLifeProfiles, supportsDynamicResponse: true, isRevalidate: false, experimental: { dynamicIO, authInterrupts }, waitUntil: afterRunner.context.waitUntil, onClose: afterRunner.context.onClose, onAfterTaskError: afterRunner.context.onTaskError }, buildId, previouslyRevalidatedTags: [] }); const routeParams = await ComponentMod.workAsyncStorage.run(store, async ()=>{ async function builtRouteParams(parentsParams = [], idx = 0) { // If we don't have any more to process, then we're done. if (idx === segments.length) return parentsParams; const current = segments[idx]; if (typeof current.generateStaticParams !== 'function' && idx < segments.length) { return builtRouteParams(parentsParams, idx + 1); } const params = []; if (current.generateStaticParams) { var _current_config; // fetchCache can be used to inform the fetch() defaults used inside // of generateStaticParams. revalidate and dynamic options don't come into // play within generateStaticParams. if (typeof ((_current_config = current.config) == null ? void 0 : _current_config.fetchCache) !== 'undefined') { store.fetchCache = current.config.fetchCache; } if (parentsParams.length > 0) { for (const parentParams of parentsParams){ const result = await current.generateStaticParams({ params: parentParams }); for (const item of result){ params.push({ ...parentParams, ...item }); } } } else { const result = await current.generateStaticParams({ params: {} }); params.push(...result); } } if (idx < segments.length) { return builtRouteParams(params, idx + 1); } return params; } return builtRouteParams(); }); await afterRunner.executeAfter(); let lastDynamicSegmentHadGenerateStaticParams = false; for (const segment of segments){ var _segment_config; // Check to see if there are any missing params for segments that have // dynamicParams set to false. if (segment.param && segment.isDynamicSegment && ((_segment_config = segment.config) == null ? void 0 : _segment_config.dynamicParams) === false) { for (const params of routeParams){ if (segment.param in params) continue; const relative = segment.filePath ? _nodepath.default.relative(dir, segment.filePath) : undefined; throw Object.defineProperty(new Error(`Segment "${relative}" exports "dynamicParams: false" but the param "${segment.param}" is missing from the generated route params.`), "__NEXT_ERROR_CODE", { value: "E280", enumerable: false, configurable: true }); } } if (segment.isDynamicSegment && typeof segment.generateStaticParams !== 'function') { lastDynamicSegmentHadGenerateStaticParams = false; } else if (typeof segment.generateStaticParams === 'function') { lastDynamicSegmentHadGenerateStaticParams = true; } } // Determine if all the segments have had their parameters provided. const hadAllParamsGenerated = routeParamKeys.length === 0 || routeParams.length > 0 && routeParams.every((params)=>{ for (const key of routeParamKeys){ if (key in params) continue; return false; } return true; }); // TODO: dynamic params should be allowed to be granular per segment but // we need additional information stored/leveraged in the prerender // manifest to allow this behavior. const dynamicParams = segments.every((segment)=>{ var _segment_config; return ((_segment_config = segment.config) == null ? void 0 : _segment_config.dynamicParams) !== false; }); const supportsRoutePreGeneration = hadAllParamsGenerated || process.env.NODE_ENV === 'production'; const fallbackMode = dynamicParams ? supportsRoutePreGeneration ? isRoutePPREnabled ? _fallback.FallbackMode.PRERENDER : _fallback.FallbackMode.BLOCKING_STATIC_RENDER : undefined : _fallback.FallbackMode.NOT_FOUND; const prerenderedRoutesByPathname = new Map(); if (hadAllParamsGenerated || isRoutePPREnabled) { if (isRoutePPREnabled) { // Discover all unique combinations of the rootParams so we can generate // routes that won't throw on empty static shell for each of them if they're available. routeParams.unshift(...filterUniqueRootParamsCombinations(rootParamKeys, routeParams)); prerenderedRoutesByPathname.set(page, { params: {}, pathname: page, encodedPathname: page, fallbackRouteParams: routeParamKeys, fallbackMode: dynamicParams ? // perform a blocking static render. rootParamKeys.length > 0 ? _fallback.FallbackMode.BLOCKING_STATIC_RENDER : fallbackMode : _fallback.FallbackMode.NOT_FOUND, fallbackRootParams: rootParamKeys, // This is set later after all the routes have been processed. throwOnEmptyStaticShell: true }); } filterUniqueParams(routeParamKeys, validateParams(page, regex, isRoutePPREnabled, routeParamKeys, rootParamKeys, routeParams)).forEach((params)=>{ let pathname = page; let encodedPathname = page; let fallbackRouteParams = []; for (const key of routeParamKeys){ if (fallbackRouteParams.length > 0) { // This is a partial route, so we should add the value to the // fallbackRouteParams. fallbackRouteParams.push(key); continue; } let paramValue = params[key]; if (!paramValue) { if (isRoutePPREnabled) { // This is a partial route, so we should add the value to the // fallbackRouteParams. fallbackRouteParams.push(key); continue; } else { // This route is not complete, and we aren't performing a partial // prerender, so we should return, skipping this route. return; } } const { repeat, optional } = regex.groups[key]; let replaced = `[${repeat ? '...' : ''}${key}]`; if (optional) { replaced = `[${replaced}]`; } pathname = pathname.replace(replaced, (0, _utils.encodeParam)(paramValue, (value)=>(0, _escapepathdelimiters.default)(value, true))); encodedPathname = encodedPathname.replace(replaced, (0, _utils.encodeParam)(paramValue, encodeURIComponent)); } const fallbackRootParams = rootParamKeys.filter((param)=>fallbackRouteParams.includes(param)); pathname = (0, _utils.normalizePathname)(pathname); prerenderedRoutesByPathname.set(pathname, { params, pathname, encodedPathname: (0, _utils.normalizePathname)(encodedPathname), fallbackRouteParams, fallbackMode: dynamicParams ? // perform a blocking static render. fallbackRootParams.length > 0 ? _fallback.FallbackMode.BLOCKING_STATIC_RENDER : fallbackMode : _fallback.FallbackMode.NOT_FOUND, fallbackRootParams, // This is set later after all the routes have been processed. throwOnEmptyStaticShell: true }); }); } const prerenderedRoutes = prerenderedRoutesByPathname.size > 0 || lastDynamicSegmentHadGenerateStaticParams ? [ ...prerenderedRoutesByPathname.values() ] : undefined; // Now we have to set the throwOnEmptyStaticShell for each of the routes. if (prerenderedRoutes && dynamicIO) { assignErrorIfEmpty(prerenderedRoutes, routeParamKeys); } return { fallbackMode, prerenderedRoutes }; } //# sourceMappingURL=app.js.map