UNPKG

next

Version:

The React Framework

172 lines (171 loc) 7.29 kB
import { InvariantError } from '../../invariant-error'; import { parseLoaderTree } from './parse-loader-tree'; import { parseAppRoute, parseAppRouteSegment } from '../routes/app'; import { resolveParamValue } from './resolve-param-value'; /** * Gets the value of a param from the params object. This correctly handles the * case where the param is a fallback route param and encodes the resulting * value. * * @param interpolatedParams - The params object. * @param segmentKey - The key of the segment. * @param fallbackRouteParams - The fallback route params. * @returns The value of the param. */ function getParamValue(interpolatedParams, segmentKey, fallbackRouteParams) { let value = interpolatedParams[segmentKey]; if (fallbackRouteParams?.has(segmentKey)) { // We know that the fallback route params has the segment key because we // checked that above. const [searchValue] = fallbackRouteParams.get(segmentKey); value = searchValue; } else if (Array.isArray(value)) { value = value.map((i)=>encodeURIComponent(i)); } else if (typeof value === 'string') { value = encodeURIComponent(value); } return value; } export function interpolateParallelRouteParams(loaderTree, params, pagePath, fallbackRouteParams) { const interpolated = structuredClone(params); // Stack-based traversal with depth tracking const stack = [ { tree: loaderTree, depth: 0 } ]; // Parse the route from the provided page path. const route = parseAppRoute(pagePath, true); while(stack.length > 0){ const { tree, depth } = stack.pop(); const { segment, parallelRoutes } = parseLoaderTree(tree); const appSegment = parseAppRouteSegment(segment); if (appSegment?.type === 'dynamic' && !interpolated.hasOwnProperty(appSegment.param.paramName) && // If the param is in the fallback route params, we don't need to // interpolate it because it's already marked as being unknown. !fallbackRouteParams?.has(appSegment.param.paramName)) { const { paramName, paramType } = appSegment.param; const paramValue = resolveParamValue(paramName, paramType, depth, route, interpolated); if (paramValue !== undefined) { interpolated[paramName] = paramValue; } else if (paramType !== 'optional-catchall') { throw Object.defineProperty(new InvariantError(`Could not resolve param value for segment: ${paramName}`), "__NEXT_ERROR_CODE", { value: "E932", enumerable: false, configurable: true }); } } // Calculate next depth - increment if this is not a route group and not empty let nextDepth = depth; if (appSegment && appSegment.type !== 'route-group' && appSegment.type !== 'parallel-route') { nextDepth++; } // Add all parallel routes to the stack for processing for (const parallelRoute of Object.values(parallelRoutes)){ stack.push({ tree: parallelRoute, depth: nextDepth }); } } return interpolated; } /** * * Shared logic on client and server for creating a dynamic param value. * * This code needs to be shared with the client so it can extract dynamic route * params from the URL without a server request. * * Because everything in this module is sent to the client, we should aim to * keep this code as simple as possible. The special case handling for catchall * and optional is, alas, unfortunate. */ export function getDynamicParam(interpolatedParams, segmentKey, dynamicParamType, fallbackRouteParams) { let value = getParamValue(interpolatedParams, segmentKey, fallbackRouteParams); // handle the case where an optional catchall does not have a value, // e.g. `/dashboard/[[...slug]]` when requesting `/dashboard` if (!value || value.length === 0) { if (dynamicParamType === 'oc') { return { param: segmentKey, value: null, type: dynamicParamType, treeSegment: [ segmentKey, '', dynamicParamType ] }; } throw Object.defineProperty(new InvariantError(`Missing value for segment key: "${segmentKey}" with dynamic param type: ${dynamicParamType}`), "__NEXT_ERROR_CODE", { value: "E864", enumerable: false, configurable: true }); } return { param: segmentKey, // The value that is passed to user code. value, // The value that is rendered in the router tree. treeSegment: [ segmentKey, Array.isArray(value) ? value.join('/') : value, dynamicParamType ], type: dynamicParamType }; } /** * Regular expression pattern used to match route parameters. * Matches both single parameters and parameter groups. * Examples: * - `[[...slug]]` matches parameter group with key 'slug', repeat: true, optional: true * - `[...slug]` matches parameter group with key 'slug', repeat: true, optional: false * - `[[foo]]` matches parameter with key 'foo', repeat: false, optional: true * - `[bar]` matches parameter with key 'bar', repeat: false, optional: false */ export const PARAMETER_PATTERN = /^([^[]*)\[((?:\[[^\]]*\])|[^\]]+)\](.*)$/; /** * Parses a given parameter from a route to a data structure that can be used * to generate the parametrized route. * Examples: * - `[[...slug]]` -> `{ key: 'slug', repeat: true, optional: true }` * - `[...slug]` -> `{ key: 'slug', repeat: true, optional: false }` * - `[[foo]]` -> `{ key: 'foo', repeat: false, optional: true }` * - `[bar]` -> `{ key: 'bar', repeat: false, optional: false }` * - `fizz` -> `{ key: 'fizz', repeat: false, optional: false }` * @param param - The parameter to parse. * @returns The parsed parameter as a data structure. */ export function parseParameter(param) { const match = param.match(PARAMETER_PATTERN); if (!match) { return parseMatchedParameter(param); } return parseMatchedParameter(match[2]); } /** * Parses a matched parameter from the PARAMETER_PATTERN regex to a data structure that can be used * to generate the parametrized route. * Examples: * - `[...slug]` -> `{ key: 'slug', repeat: true, optional: true }` * - `...slug` -> `{ key: 'slug', repeat: true, optional: false }` * - `[foo]` -> `{ key: 'foo', repeat: false, optional: true }` * - `bar` -> `{ key: 'bar', repeat: false, optional: false }` * @param param - The matched parameter to parse. * @returns The parsed parameter as a data structure. */ export function parseMatchedParameter(param) { const optional = param.startsWith('[') && param.endsWith(']'); if (optional) { param = param.slice(1, -1); } const repeat = param.startsWith('...'); if (repeat) { param = param.slice(3); } return { key: param, repeat, optional }; } //# sourceMappingURL=get-dynamic-param.js.map