.
This commit is contained in:
+110
@@ -0,0 +1,110 @@
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { PathnameContext } from '../hooks-client-context.shared-runtime';
|
||||
import { isDynamicRoute } from './utils';
|
||||
import { asPathToSearchParams } from './utils/as-path-to-search-params';
|
||||
import { getRouteRegex } from './utils/route-regex';
|
||||
/** It adapts a Pages Router (`NextRouter`) to the App Router Instance. */ export function adaptForAppRouterInstance(pagesRouter) {
|
||||
return {
|
||||
back () {
|
||||
pagesRouter.back();
|
||||
},
|
||||
forward () {
|
||||
pagesRouter.forward();
|
||||
},
|
||||
refresh () {
|
||||
pagesRouter.reload();
|
||||
},
|
||||
hmrRefresh () {},
|
||||
push (href, { scroll } = {}) {
|
||||
void pagesRouter.push(href, undefined, {
|
||||
scroll
|
||||
});
|
||||
},
|
||||
replace (href, { scroll } = {}) {
|
||||
void pagesRouter.replace(href, undefined, {
|
||||
scroll
|
||||
});
|
||||
},
|
||||
prefetch (href) {
|
||||
void pagesRouter.prefetch(href);
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* adaptForSearchParams transforms the ParsedURLQuery into URLSearchParams.
|
||||
*
|
||||
* @param router the router that contains the query.
|
||||
* @returns the search params in the URLSearchParams format
|
||||
*/ export function adaptForSearchParams(router) {
|
||||
if (!router.isReady || !router.query) {
|
||||
return new URLSearchParams();
|
||||
}
|
||||
return asPathToSearchParams(router.asPath);
|
||||
}
|
||||
export function adaptForPathParams(router) {
|
||||
if (!router.isReady || !router.query) {
|
||||
return null;
|
||||
}
|
||||
const pathParams = {};
|
||||
const routeRegex = getRouteRegex(router.pathname);
|
||||
const keys = Object.keys(routeRegex.groups);
|
||||
for (const key of keys){
|
||||
pathParams[key] = router.query[key];
|
||||
}
|
||||
return pathParams;
|
||||
}
|
||||
export function PathnameContextProviderAdapter({ children, router, ...props }) {
|
||||
const ref = useRef(props.isAutoExport);
|
||||
const value = useMemo(()=>{
|
||||
// isAutoExport is only ever `true` on the first render from the server,
|
||||
// so reset it to `false` after we read it for the first time as `true`. If
|
||||
// we don't use the value, then we don't need it.
|
||||
const isAutoExport = ref.current;
|
||||
if (isAutoExport) {
|
||||
ref.current = false;
|
||||
}
|
||||
// When the route is a dynamic route, we need to do more processing to
|
||||
// determine if we need to stop showing the pathname.
|
||||
if (isDynamicRoute(router.pathname)) {
|
||||
// When the router is rendering the fallback page, it can't possibly know
|
||||
// the path, so return `null` here. Read more about fallback pages over
|
||||
// at:
|
||||
// https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-pages
|
||||
if (router.isFallback) {
|
||||
return null;
|
||||
}
|
||||
// When `isAutoExport` is true, meaning this is a page page has been
|
||||
// automatically statically optimized, and the router is not ready, then
|
||||
// we can't know the pathname yet. Read more about automatic static
|
||||
// optimization at:
|
||||
// https://nextjs.org/docs/advanced-features/automatic-static-optimization
|
||||
if (isAutoExport && !router.isReady) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// The `router.asPath` contains the pathname seen by the browser (including
|
||||
// any query strings), so it should have that stripped. Read more about the
|
||||
// `asPath` option over at:
|
||||
// https://nextjs.org/docs/api-reference/next/router#router-object
|
||||
let url;
|
||||
try {
|
||||
url = new URL(router.asPath, 'http://f');
|
||||
} catch (_) {
|
||||
// fallback to / for invalid asPath values e.g. //
|
||||
return '/';
|
||||
}
|
||||
return url.pathname;
|
||||
}, [
|
||||
router.asPath,
|
||||
router.isFallback,
|
||||
router.isReady,
|
||||
router.pathname
|
||||
]);
|
||||
return /*#__PURE__*/ _jsx(PathnameContext.Provider, {
|
||||
value: value,
|
||||
children: children
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=adapters.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+1779
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+92
@@ -0,0 +1,92 @@
|
||||
import { InvariantError } from '../../invariant-error';
|
||||
import { getSegmentParam } from '../utils/get-segment-param';
|
||||
import { INTERCEPTION_ROUTE_MARKERS } from '../utils/interception-routes';
|
||||
export function parseAppRouteSegment(segment) {
|
||||
if (segment === '') {
|
||||
return null;
|
||||
}
|
||||
// Check if the segment starts with an interception marker
|
||||
const interceptionMarker = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
|
||||
const param = getSegmentParam(segment);
|
||||
if (param) {
|
||||
return {
|
||||
type: 'dynamic',
|
||||
name: segment,
|
||||
param,
|
||||
interceptionMarker
|
||||
};
|
||||
} else if (segment.startsWith('(') && segment.endsWith(')')) {
|
||||
return {
|
||||
type: 'route-group',
|
||||
name: segment,
|
||||
interceptionMarker
|
||||
};
|
||||
} else if (segment.startsWith('@')) {
|
||||
return {
|
||||
type: 'parallel-route',
|
||||
name: segment,
|
||||
interceptionMarker
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'static',
|
||||
name: segment,
|
||||
interceptionMarker
|
||||
};
|
||||
}
|
||||
}
|
||||
export function isNormalizedAppRoute(route) {
|
||||
return route.normalized;
|
||||
}
|
||||
export function isInterceptionAppRoute(route) {
|
||||
return route.interceptionMarker !== undefined && route.interceptingRoute !== undefined && route.interceptedRoute !== undefined;
|
||||
}
|
||||
export function parseAppRoute(pathname, normalized) {
|
||||
const pathnameSegments = pathname.split('/').filter(Boolean);
|
||||
// Build segments array with static and dynamic segments
|
||||
const segments = [];
|
||||
// Parse if this is an interception route.
|
||||
let interceptionMarker;
|
||||
let interceptingRoute;
|
||||
let interceptedRoute;
|
||||
for (const segment of pathnameSegments){
|
||||
// Parse the segment into an AppSegment.
|
||||
const appSegment = parseAppRouteSegment(segment);
|
||||
if (!appSegment) {
|
||||
continue;
|
||||
}
|
||||
if (normalized && (appSegment.type === 'route-group' || appSegment.type === 'parallel-route')) {
|
||||
throw Object.defineProperty(new InvariantError(`${pathname} is being parsed as a normalized route, but it has a route group or parallel route segment.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E923",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
segments.push(appSegment);
|
||||
if (appSegment.interceptionMarker) {
|
||||
const parts = pathname.split(appSegment.interceptionMarker);
|
||||
if (parts.length !== 2) {
|
||||
throw Object.defineProperty(new Error(`Invalid interception route: ${pathname}`), "__NEXT_ERROR_CODE", {
|
||||
value: "E924",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
interceptingRoute = normalized ? parseAppRoute(parts[0], true) : parseAppRoute(parts[0], false);
|
||||
interceptedRoute = normalized ? parseAppRoute(parts[1], true) : parseAppRoute(parts[1], false);
|
||||
interceptionMarker = appSegment.interceptionMarker;
|
||||
}
|
||||
}
|
||||
const dynamicSegments = segments.filter((segment)=>segment.type === 'dynamic');
|
||||
return {
|
||||
normalized,
|
||||
pathname,
|
||||
segments,
|
||||
dynamicSegments,
|
||||
interceptionMarker,
|
||||
interceptingRoute,
|
||||
interceptedRoute
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=app.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+22
@@ -0,0 +1,22 @@
|
||||
import { addPathPrefix } from './add-path-prefix';
|
||||
import { pathHasPrefix } from './path-has-prefix';
|
||||
/**
|
||||
* For a given path and a locale, if the locale is given, it will prefix the
|
||||
* locale. The path shouldn't be an API path. If a default locale is given the
|
||||
* prefix will be omitted if the locale is already the default locale.
|
||||
*/ export function addLocale(path, locale, defaultLocale, ignorePrefix) {
|
||||
// If no locale was given or the locale is the default locale, we don't need
|
||||
// to prefix the path.
|
||||
if (!locale || locale === defaultLocale) return path;
|
||||
const lower = path.toLowerCase();
|
||||
// If the path is an API path or the path already has the locale prefix, we
|
||||
// don't need to prefix the path.
|
||||
if (!ignorePrefix) {
|
||||
if (pathHasPrefix(lower, '/api')) return path;
|
||||
if (pathHasPrefix(lower, `/${locale.toLowerCase()}`)) return path;
|
||||
}
|
||||
// Add the locale prefix to the path.
|
||||
return addPathPrefix(path, `/${locale}`);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=add-locale.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/add-locale.ts"],"sourcesContent":["import { addPathPrefix } from './add-path-prefix'\nimport { pathHasPrefix } from './path-has-prefix'\n\n/**\n * For a given path and a locale, if the locale is given, it will prefix the\n * locale. The path shouldn't be an API path. If a default locale is given the\n * prefix will be omitted if the locale is already the default locale.\n */\nexport function addLocale(\n path: string,\n locale?: string | false,\n defaultLocale?: string,\n ignorePrefix?: boolean\n) {\n // If no locale was given or the locale is the default locale, we don't need\n // to prefix the path.\n if (!locale || locale === defaultLocale) return path\n\n const lower = path.toLowerCase()\n\n // If the path is an API path or the path already has the locale prefix, we\n // don't need to prefix the path.\n if (!ignorePrefix) {\n if (pathHasPrefix(lower, '/api')) return path\n if (pathHasPrefix(lower, `/${locale.toLowerCase()}`)) return path\n }\n\n // Add the locale prefix to the path.\n return addPathPrefix(path, `/${locale}`)\n}\n"],"names":["addPathPrefix","pathHasPrefix","addLocale","path","locale","defaultLocale","ignorePrefix","lower","toLowerCase"],"mappings":"AAAA,SAASA,aAAa,QAAQ,oBAAmB;AACjD,SAASC,aAAa,QAAQ,oBAAmB;AAEjD;;;;CAIC,GACD,OAAO,SAASC,UACdC,IAAY,EACZC,MAAuB,EACvBC,aAAsB,EACtBC,YAAsB;IAEtB,4EAA4E;IAC5E,sBAAsB;IACtB,IAAI,CAACF,UAAUA,WAAWC,eAAe,OAAOF;IAEhD,MAAMI,QAAQJ,KAAKK,WAAW;IAE9B,2EAA2E;IAC3E,iCAAiC;IACjC,IAAI,CAACF,cAAc;QACjB,IAAIL,cAAcM,OAAO,SAAS,OAAOJ;QACzC,IAAIF,cAAcM,OAAO,CAAC,CAAC,EAAEH,OAAOI,WAAW,IAAI,GAAG,OAAOL;IAC/D;IAEA,qCAAqC;IACrC,OAAOH,cAAcG,MAAM,CAAC,CAAC,EAAEC,QAAQ;AACzC","ignoreList":[0]}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { parsePath } from './parse-path';
|
||||
/**
|
||||
* Adds the provided prefix to the given path. It first ensures that the path
|
||||
* is indeed starting with a slash.
|
||||
*/ export function addPathPrefix(path, prefix) {
|
||||
if (!path.startsWith('/') || !prefix) {
|
||||
return path;
|
||||
}
|
||||
const { pathname, query, hash } = parsePath(path);
|
||||
return `${prefix}${pathname}${query}${hash}`;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=add-path-prefix.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/add-path-prefix.ts"],"sourcesContent":["import { parsePath } from './parse-path'\n\n/**\n * Adds the provided prefix to the given path. It first ensures that the path\n * is indeed starting with a slash.\n */\nexport function addPathPrefix(path: string, prefix?: string) {\n if (!path.startsWith('/') || !prefix) {\n return path\n }\n\n const { pathname, query, hash } = parsePath(path)\n return `${prefix}${pathname}${query}${hash}`\n}\n"],"names":["parsePath","addPathPrefix","path","prefix","startsWith","pathname","query","hash"],"mappings":"AAAA,SAASA,SAAS,QAAQ,eAAc;AAExC;;;CAGC,GACD,OAAO,SAASC,cAAcC,IAAY,EAAEC,MAAe;IACzD,IAAI,CAACD,KAAKE,UAAU,CAAC,QAAQ,CAACD,QAAQ;QACpC,OAAOD;IACT;IAEA,MAAM,EAAEG,QAAQ,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGP,UAAUE;IAC5C,OAAO,GAAGC,SAASE,WAAWC,QAAQC,MAAM;AAC9C","ignoreList":[0]}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { parsePath } from './parse-path';
|
||||
/**
|
||||
* Similarly to `addPathPrefix`, this function adds a suffix at the end on the
|
||||
* provided path. It also works only for paths ensuring the argument starts
|
||||
* with a slash.
|
||||
*/ export function addPathSuffix(path, suffix) {
|
||||
if (!path.startsWith('/') || !suffix) {
|
||||
return path;
|
||||
}
|
||||
const { pathname, query, hash } = parsePath(path);
|
||||
return `${pathname}${suffix}${query}${hash}`;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=add-path-suffix.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/add-path-suffix.ts"],"sourcesContent":["import { parsePath } from './parse-path'\n\n/**\n * Similarly to `addPathPrefix`, this function adds a suffix at the end on the\n * provided path. It also works only for paths ensuring the argument starts\n * with a slash.\n */\nexport function addPathSuffix(path: string, suffix?: string) {\n if (!path.startsWith('/') || !suffix) {\n return path\n }\n\n const { pathname, query, hash } = parsePath(path)\n return `${pathname}${suffix}${query}${hash}`\n}\n"],"names":["parsePath","addPathSuffix","path","suffix","startsWith","pathname","query","hash"],"mappings":"AAAA,SAASA,SAAS,QAAQ,eAAc;AAExC;;;;CAIC,GACD,OAAO,SAASC,cAAcC,IAAY,EAAEC,MAAe;IACzD,IAAI,CAACD,KAAKE,UAAU,CAAC,QAAQ,CAACD,QAAQ;QACpC,OAAOD;IACT;IAEA,MAAM,EAAEG,QAAQ,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGP,UAAUE;IAC5C,OAAO,GAAGG,WAAWF,SAASG,QAAQC,MAAM;AAC9C","ignoreList":[0]}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { ensureLeadingSlash } from '../../page-path/ensure-leading-slash';
|
||||
import { isGroupSegment } from '../../segment';
|
||||
/**
|
||||
* Normalizes an app route so it represents the actual request path. Essentially
|
||||
* performing the following transformations:
|
||||
*
|
||||
* - `/(dashboard)/user/[id]/page` to `/user/[id]`
|
||||
* - `/(dashboard)/account/page` to `/account`
|
||||
* - `/user/[id]/page` to `/user/[id]`
|
||||
* - `/account/page` to `/account`
|
||||
* - `/page` to `/`
|
||||
* - `/(dashboard)/user/[id]/route` to `/user/[id]`
|
||||
* - `/(dashboard)/account/route` to `/account`
|
||||
* - `/user/[id]/route` to `/user/[id]`
|
||||
* - `/account/route` to `/account`
|
||||
* - `/route` to `/`
|
||||
* - `/` to `/`
|
||||
*
|
||||
* @param route the app route to normalize
|
||||
* @returns the normalized pathname
|
||||
*/ export function normalizeAppPath(route) {
|
||||
return ensureLeadingSlash(route.split('/').reduce((pathname, segment, index, segments)=>{
|
||||
// Empty segments are ignored.
|
||||
if (!segment) {
|
||||
return pathname;
|
||||
}
|
||||
// Groups are ignored.
|
||||
if (isGroupSegment(segment)) {
|
||||
return pathname;
|
||||
}
|
||||
// Parallel segments are ignored.
|
||||
if (segment[0] === '@') {
|
||||
return pathname;
|
||||
}
|
||||
// The last segment (if it's a leaf) should be ignored.
|
||||
if ((segment === 'page' || segment === 'route') && index === segments.length - 1) {
|
||||
return pathname;
|
||||
}
|
||||
return `${pathname}/${segment}`;
|
||||
}, ''));
|
||||
}
|
||||
/**
|
||||
* Comparator for sorting app paths so that parallel slot paths (containing
|
||||
* `/@`) come before the children/root page path. This ensures the last item
|
||||
* is always the children page, which is what `renderPageComponent` reads via
|
||||
* `appPaths[appPaths.length - 1]`.
|
||||
*
|
||||
* Without this, route group prefixes like `(group)` (char code 0x28) sort
|
||||
* before `@` (0x40), causing the children page to sort first instead of last
|
||||
* and leading to a manifest mismatch / 404 in webpack dev mode.
|
||||
*/ export function compareAppPaths(a, b) {
|
||||
const aHasSlot = a.includes('/@');
|
||||
const bHasSlot = b.includes('/@');
|
||||
if (aHasSlot && !bHasSlot) return -1;
|
||||
if (!aHasSlot && bHasSlot) return 1;
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
/**
|
||||
* Strips the `.rsc` extension if it's in the pathname.
|
||||
* Since this function is used on full urls it checks `?` for searchParams handling.
|
||||
*/ export function normalizeRscURL(url) {
|
||||
return url.replace(/\.rsc($|\?)/, // $1 ensures `?` is preserved
|
||||
'$1');
|
||||
}
|
||||
|
||||
//# sourceMappingURL=app-paths.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/app-paths.ts"],"sourcesContent":["import { ensureLeadingSlash } from '../../page-path/ensure-leading-slash'\nimport { isGroupSegment } from '../../segment'\n\n/**\n * Normalizes an app route so it represents the actual request path. Essentially\n * performing the following transformations:\n *\n * - `/(dashboard)/user/[id]/page` to `/user/[id]`\n * - `/(dashboard)/account/page` to `/account`\n * - `/user/[id]/page` to `/user/[id]`\n * - `/account/page` to `/account`\n * - `/page` to `/`\n * - `/(dashboard)/user/[id]/route` to `/user/[id]`\n * - `/(dashboard)/account/route` to `/account`\n * - `/user/[id]/route` to `/user/[id]`\n * - `/account/route` to `/account`\n * - `/route` to `/`\n * - `/` to `/`\n *\n * @param route the app route to normalize\n * @returns the normalized pathname\n */\nexport function normalizeAppPath(route: string) {\n return ensureLeadingSlash(\n route.split('/').reduce((pathname, segment, index, segments) => {\n // Empty segments are ignored.\n if (!segment) {\n return pathname\n }\n\n // Groups are ignored.\n if (isGroupSegment(segment)) {\n return pathname\n }\n\n // Parallel segments are ignored.\n if (segment[0] === '@') {\n return pathname\n }\n\n // The last segment (if it's a leaf) should be ignored.\n if (\n (segment === 'page' || segment === 'route') &&\n index === segments.length - 1\n ) {\n return pathname\n }\n\n return `${pathname}/${segment}`\n }, '')\n )\n}\n\n/**\n * Comparator for sorting app paths so that parallel slot paths (containing\n * `/@`) come before the children/root page path. This ensures the last item\n * is always the children page, which is what `renderPageComponent` reads via\n * `appPaths[appPaths.length - 1]`.\n *\n * Without this, route group prefixes like `(group)` (char code 0x28) sort\n * before `@` (0x40), causing the children page to sort first instead of last\n * and leading to a manifest mismatch / 404 in webpack dev mode.\n */\nexport function compareAppPaths(a: string, b: string): number {\n const aHasSlot = a.includes('/@')\n const bHasSlot = b.includes('/@')\n if (aHasSlot && !bHasSlot) return -1\n if (!aHasSlot && bHasSlot) return 1\n return a.localeCompare(b)\n}\n\n/**\n * Strips the `.rsc` extension if it's in the pathname.\n * Since this function is used on full urls it checks `?` for searchParams handling.\n */\nexport function normalizeRscURL(url: string) {\n return url.replace(\n /\\.rsc($|\\?)/,\n // $1 ensures `?` is preserved\n '$1'\n )\n}\n"],"names":["ensureLeadingSlash","isGroupSegment","normalizeAppPath","route","split","reduce","pathname","segment","index","segments","length","compareAppPaths","a","b","aHasSlot","includes","bHasSlot","localeCompare","normalizeRscURL","url","replace"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,uCAAsC;AACzE,SAASC,cAAc,QAAQ,gBAAe;AAE9C;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,iBAAiBC,KAAa;IAC5C,OAAOH,mBACLG,MAAMC,KAAK,CAAC,KAAKC,MAAM,CAAC,CAACC,UAAUC,SAASC,OAAOC;QACjD,8BAA8B;QAC9B,IAAI,CAACF,SAAS;YACZ,OAAOD;QACT;QAEA,sBAAsB;QACtB,IAAIL,eAAeM,UAAU;YAC3B,OAAOD;QACT;QAEA,iCAAiC;QACjC,IAAIC,OAAO,CAAC,EAAE,KAAK,KAAK;YACtB,OAAOD;QACT;QAEA,uDAAuD;QACvD,IACE,AAACC,CAAAA,YAAY,UAAUA,YAAY,OAAM,KACzCC,UAAUC,SAASC,MAAM,GAAG,GAC5B;YACA,OAAOJ;QACT;QAEA,OAAO,GAAGA,SAAS,CAAC,EAAEC,SAAS;IACjC,GAAG;AAEP;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASI,gBAAgBC,CAAS,EAAEC,CAAS;IAClD,MAAMC,WAAWF,EAAEG,QAAQ,CAAC;IAC5B,MAAMC,WAAWH,EAAEE,QAAQ,CAAC;IAC5B,IAAID,YAAY,CAACE,UAAU,OAAO,CAAC;IACnC,IAAI,CAACF,YAAYE,UAAU,OAAO;IAClC,OAAOJ,EAAEK,aAAa,CAACJ;AACzB;AAEA;;;CAGC,GACD,OAAO,SAASK,gBAAgBC,GAAW;IACzC,OAAOA,IAAIC,OAAO,CAChB,eACA,8BAA8B;IAC9B;AAEJ","ignoreList":[0]}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Convert router.asPath to a URLSearchParams object
|
||||
// example: /dynamic/[slug]?foo=bar -> { foo: 'bar' }
|
||||
export function asPathToSearchParams(asPath) {
|
||||
return new URL(asPath, 'http://n').searchParams;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=as-path-to-search-params.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/as-path-to-search-params.ts"],"sourcesContent":["// Convert router.asPath to a URLSearchParams object\n// example: /dynamic/[slug]?foo=bar -> { foo: 'bar' }\nexport function asPathToSearchParams(asPath: string): URLSearchParams {\n return new URL(asPath, 'http://n').searchParams\n}\n"],"names":["asPathToSearchParams","asPath","URL","searchParams"],"mappings":"AAAA,oDAAoD;AACpD,qDAAqD;AACrD,OAAO,SAASA,qBAAqBC,MAAc;IACjD,OAAO,IAAIC,IAAID,QAAQ,YAAYE,YAAY;AACjD","ignoreList":[0]}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { hexHash } from '../../hash';
|
||||
export function computeCacheBustingSearchParam(prefetchHeader, segmentPrefetchHeader, stateTreeHeader, nextUrlHeader) {
|
||||
if ((prefetchHeader === undefined || prefetchHeader === '0') && segmentPrefetchHeader === undefined && stateTreeHeader === undefined && nextUrlHeader === undefined) {
|
||||
return '';
|
||||
}
|
||||
return hexHash([
|
||||
prefetchHeader || '0',
|
||||
segmentPrefetchHeader || '0',
|
||||
stateTreeHeader || '0',
|
||||
nextUrlHeader || '0'
|
||||
].join(','));
|
||||
}
|
||||
|
||||
//# sourceMappingURL=cache-busting-search-param.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/cache-busting-search-param.ts"],"sourcesContent":["import { hexHash } from '../../hash'\n\nexport function computeCacheBustingSearchParam(\n prefetchHeader: '1' | '2' | '0' | undefined,\n segmentPrefetchHeader: string | string[] | undefined,\n stateTreeHeader: string | string[] | undefined,\n nextUrlHeader: string | string[] | undefined\n): string {\n if (\n (prefetchHeader === undefined || prefetchHeader === '0') &&\n segmentPrefetchHeader === undefined &&\n stateTreeHeader === undefined &&\n nextUrlHeader === undefined\n ) {\n return ''\n }\n return hexHash(\n [\n prefetchHeader || '0',\n segmentPrefetchHeader || '0',\n stateTreeHeader || '0',\n nextUrlHeader || '0',\n ].join(',')\n )\n}\n"],"names":["hexHash","computeCacheBustingSearchParam","prefetchHeader","segmentPrefetchHeader","stateTreeHeader","nextUrlHeader","undefined","join"],"mappings":"AAAA,SAASA,OAAO,QAAQ,aAAY;AAEpC,OAAO,SAASC,+BACdC,cAA2C,EAC3CC,qBAAoD,EACpDC,eAA8C,EAC9CC,aAA4C;IAE5C,IACE,AAACH,CAAAA,mBAAmBI,aAAaJ,mBAAmB,GAAE,KACtDC,0BAA0BG,aAC1BF,oBAAoBE,aACpBD,kBAAkBC,WAClB;QACA,OAAO;IACT;IACA,OAAON,QACL;QACEE,kBAAkB;QAClBC,yBAAyB;QACzBC,mBAAmB;QACnBC,iBAAiB;KAClB,CAACE,IAAI,CAAC;AAEX","ignoreList":[0]}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
export function compareRouterStates(a, b) {
|
||||
const stateKeys = Object.keys(a);
|
||||
if (stateKeys.length !== Object.keys(b).length) return false;
|
||||
for(let i = stateKeys.length; i--;){
|
||||
const key = stateKeys[i];
|
||||
if (key === 'query') {
|
||||
const queryKeys = Object.keys(a.query);
|
||||
if (queryKeys.length !== Object.keys(b.query).length) {
|
||||
return false;
|
||||
}
|
||||
for(let j = queryKeys.length; j--;){
|
||||
const queryKey = queryKeys[j];
|
||||
if (!b.query.hasOwnProperty(queryKey) || a.query[queryKey] !== b.query[queryKey]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (!b.hasOwnProperty(key) || a[key] !== b[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=compare-states.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/compare-states.ts"],"sourcesContent":["import type { default as Router } from '../router'\n\nexport function compareRouterStates(a: Router['state'], b: Router['state']) {\n const stateKeys = Object.keys(a)\n if (stateKeys.length !== Object.keys(b).length) return false\n\n for (let i = stateKeys.length; i--; ) {\n const key = stateKeys[i]\n if (key === 'query') {\n const queryKeys = Object.keys(a.query)\n if (queryKeys.length !== Object.keys(b.query).length) {\n return false\n }\n for (let j = queryKeys.length; j--; ) {\n const queryKey = queryKeys[j]\n if (\n !b.query.hasOwnProperty(queryKey) ||\n a.query[queryKey] !== b.query[queryKey]\n ) {\n return false\n }\n }\n } else if (\n !b.hasOwnProperty(key) ||\n a[key as keyof Router['state']] !== b[key as keyof Router['state']]\n ) {\n return false\n }\n }\n\n return true\n}\n"],"names":["compareRouterStates","a","b","stateKeys","Object","keys","length","i","key","queryKeys","query","j","queryKey","hasOwnProperty"],"mappings":"AAEA,OAAO,SAASA,oBAAoBC,CAAkB,EAAEC,CAAkB;IACxE,MAAMC,YAAYC,OAAOC,IAAI,CAACJ;IAC9B,IAAIE,UAAUG,MAAM,KAAKF,OAAOC,IAAI,CAACH,GAAGI,MAAM,EAAE,OAAO;IAEvD,IAAK,IAAIC,IAAIJ,UAAUG,MAAM,EAAEC,KAAO;QACpC,MAAMC,MAAML,SAAS,CAACI,EAAE;QACxB,IAAIC,QAAQ,SAAS;YACnB,MAAMC,YAAYL,OAAOC,IAAI,CAACJ,EAAES,KAAK;YACrC,IAAID,UAAUH,MAAM,KAAKF,OAAOC,IAAI,CAACH,EAAEQ,KAAK,EAAEJ,MAAM,EAAE;gBACpD,OAAO;YACT;YACA,IAAK,IAAIK,IAAIF,UAAUH,MAAM,EAAEK,KAAO;gBACpC,MAAMC,WAAWH,SAAS,CAACE,EAAE;gBAC7B,IACE,CAACT,EAAEQ,KAAK,CAACG,cAAc,CAACD,aACxBX,EAAES,KAAK,CAACE,SAAS,KAAKV,EAAEQ,KAAK,CAACE,SAAS,EACvC;oBACA,OAAO;gBACT;YACF;QACF,OAAO,IACL,CAACV,EAAEW,cAAc,CAACL,QAClBP,CAAC,CAACO,IAA6B,KAAKN,CAAC,CAACM,IAA6B,EACnE;YACA,OAAO;QACT;IACF;IAEA,OAAO;AACT","ignoreList":[0]}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { warnOnce } from '../../utils/warn-once';
|
||||
/**
|
||||
* Run function with `scroll-behavior: auto` applied to `<html/>`.
|
||||
* This css change will be reverted after the function finishes.
|
||||
*/ export function disableSmoothScrollDuringRouteTransition(fn, options = {}) {
|
||||
// if only the hash is changed, we don't need to disable smooth scrolling
|
||||
// we only care to prevent smooth scrolling when navigating to a new page to avoid jarring UX
|
||||
if (options.onlyHashChange) {
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
const htmlElement = document.documentElement;
|
||||
const hasDataAttribute = htmlElement.dataset.scrollBehavior === 'smooth';
|
||||
if (!hasDataAttribute) {
|
||||
// Warn if smooth scrolling is detected but no data attribute is present
|
||||
if (process.env.NODE_ENV === 'development' && getComputedStyle(htmlElement).scrollBehavior === 'smooth') {
|
||||
warnOnce('Detected `scroll-behavior: smooth` on the `<html>` element. To disable smooth scrolling during route transitions, ' + 'add `data-scroll-behavior="smooth"` to your <html> element. ' + 'Learn more: https://nextjs.org/docs/messages/missing-data-scroll-behavior');
|
||||
}
|
||||
// No smooth scrolling configured, run directly without style manipulation
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
// Proceed with temporarily disabling smooth scrolling
|
||||
const existing = htmlElement.style.scrollBehavior;
|
||||
htmlElement.style.scrollBehavior = 'auto';
|
||||
if (!options.dontForceLayout) {
|
||||
// In Chrome-based browsers we need to force reflow before calling `scrollTo`.
|
||||
// Otherwise it will not pickup the change in scrollBehavior
|
||||
// More info here: https://github.com/vercel/next.js/issues/40719#issuecomment-1336248042
|
||||
htmlElement.getClientRects();
|
||||
}
|
||||
fn();
|
||||
htmlElement.style.scrollBehavior = existing;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=disable-smooth-scroll.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/disable-smooth-scroll.ts"],"sourcesContent":["import { warnOnce } from '../../utils/warn-once'\n\n/**\n * Run function with `scroll-behavior: auto` applied to `<html/>`.\n * This css change will be reverted after the function finishes.\n */\nexport function disableSmoothScrollDuringRouteTransition(\n fn: () => void,\n options: { dontForceLayout?: boolean; onlyHashChange?: boolean } = {}\n) {\n // if only the hash is changed, we don't need to disable smooth scrolling\n // we only care to prevent smooth scrolling when navigating to a new page to avoid jarring UX\n if (options.onlyHashChange) {\n fn()\n return\n }\n\n const htmlElement = document.documentElement\n const hasDataAttribute = htmlElement.dataset.scrollBehavior === 'smooth'\n\n if (!hasDataAttribute) {\n // Warn if smooth scrolling is detected but no data attribute is present\n if (\n process.env.NODE_ENV === 'development' &&\n getComputedStyle(htmlElement).scrollBehavior === 'smooth'\n ) {\n warnOnce(\n 'Detected `scroll-behavior: smooth` on the `<html>` element. To disable smooth scrolling during route transitions, ' +\n 'add `data-scroll-behavior=\"smooth\"` to your <html> element. ' +\n 'Learn more: https://nextjs.org/docs/messages/missing-data-scroll-behavior'\n )\n }\n // No smooth scrolling configured, run directly without style manipulation\n fn()\n return\n }\n\n // Proceed with temporarily disabling smooth scrolling\n const existing = htmlElement.style.scrollBehavior\n htmlElement.style.scrollBehavior = 'auto'\n if (!options.dontForceLayout) {\n // In Chrome-based browsers we need to force reflow before calling `scrollTo`.\n // Otherwise it will not pickup the change in scrollBehavior\n // More info here: https://github.com/vercel/next.js/issues/40719#issuecomment-1336248042\n htmlElement.getClientRects()\n }\n fn()\n htmlElement.style.scrollBehavior = existing\n}\n"],"names":["warnOnce","disableSmoothScrollDuringRouteTransition","fn","options","onlyHashChange","htmlElement","document","documentElement","hasDataAttribute","dataset","scrollBehavior","process","env","NODE_ENV","getComputedStyle","existing","style","dontForceLayout","getClientRects"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,wBAAuB;AAEhD;;;CAGC,GACD,OAAO,SAASC,yCACdC,EAAc,EACdC,UAAmE,CAAC,CAAC;IAErE,yEAAyE;IACzE,6FAA6F;IAC7F,IAAIA,QAAQC,cAAc,EAAE;QAC1BF;QACA;IACF;IAEA,MAAMG,cAAcC,SAASC,eAAe;IAC5C,MAAMC,mBAAmBH,YAAYI,OAAO,CAACC,cAAc,KAAK;IAEhE,IAAI,CAACF,kBAAkB;QACrB,wEAAwE;QACxE,IACEG,QAAQC,GAAG,CAACC,QAAQ,KAAK,iBACzBC,iBAAiBT,aAAaK,cAAc,KAAK,UACjD;YACAV,SACE,uHACE,iEACA;QAEN;QACA,0EAA0E;QAC1EE;QACA;IACF;IAEA,sDAAsD;IACtD,MAAMa,WAAWV,YAAYW,KAAK,CAACN,cAAc;IACjDL,YAAYW,KAAK,CAACN,cAAc,GAAG;IACnC,IAAI,CAACP,QAAQc,eAAe,EAAE;QAC5B,8EAA8E;QAC9E,4DAA4D;QAC5D,yFAAyF;QACzFZ,YAAYa,cAAc;IAC5B;IACAhB;IACAG,YAAYW,KAAK,CAACN,cAAc,GAAGK;AACrC","ignoreList":[0]}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// escape delimiters used by path-to-regexp
|
||||
export default function escapePathDelimiters(segment, escapeEncoded) {
|
||||
return segment.replace(new RegExp(`([/#?]${escapeEncoded ? '|%(2f|23|3f|5c)' : ''})`, 'gi'), (char)=>encodeURIComponent(char));
|
||||
}
|
||||
|
||||
//# sourceMappingURL=escape-path-delimiters.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/escape-path-delimiters.ts"],"sourcesContent":["// escape delimiters used by path-to-regexp\nexport default function escapePathDelimiters(\n segment: string,\n escapeEncoded?: boolean\n): string {\n return segment.replace(\n new RegExp(`([/#?]${escapeEncoded ? '|%(2f|23|3f|5c)' : ''})`, 'gi'),\n (char: string) => encodeURIComponent(char)\n )\n}\n"],"names":["escapePathDelimiters","segment","escapeEncoded","replace","RegExp","char","encodeURIComponent"],"mappings":"AAAA,2CAA2C;AAC3C,eAAe,SAASA,qBACtBC,OAAe,EACfC,aAAuB;IAEvB,OAAOD,QAAQE,OAAO,CACpB,IAAIC,OAAO,CAAC,MAAM,EAAEF,gBAAgB,oBAAoB,GAAG,CAAC,CAAC,EAAE,OAC/D,CAACG,OAAiBC,mBAAmBD;AAEzC","ignoreList":[0]}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { removeTrailingSlash } from './remove-trailing-slash';
|
||||
import { addPathPrefix } from './add-path-prefix';
|
||||
import { addPathSuffix } from './add-path-suffix';
|
||||
import { addLocale } from './add-locale';
|
||||
export function formatNextPathnameInfo(info) {
|
||||
let pathname = addLocale(info.pathname, info.locale, info.buildId ? undefined : info.defaultLocale, info.ignorePrefix);
|
||||
if (info.buildId || !info.trailingSlash) {
|
||||
pathname = removeTrailingSlash(pathname);
|
||||
}
|
||||
if (info.buildId) {
|
||||
pathname = addPathSuffix(addPathPrefix(pathname, `/_next/data/${info.buildId}`), info.pathname === '/' ? 'index.json' : '.json');
|
||||
}
|
||||
pathname = addPathPrefix(pathname, info.basePath);
|
||||
return !info.buildId && info.trailingSlash ? !pathname.endsWith('/') ? addPathSuffix(pathname, '/') : pathname : removeTrailingSlash(pathname);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=format-next-pathname-info.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/format-next-pathname-info.ts"],"sourcesContent":["import type { NextPathnameInfo } from './get-next-pathname-info'\nimport { removeTrailingSlash } from './remove-trailing-slash'\nimport { addPathPrefix } from './add-path-prefix'\nimport { addPathSuffix } from './add-path-suffix'\nimport { addLocale } from './add-locale'\n\ninterface ExtendedInfo extends NextPathnameInfo {\n defaultLocale?: string\n ignorePrefix?: boolean\n}\n\nexport function formatNextPathnameInfo(info: ExtendedInfo) {\n let pathname = addLocale(\n info.pathname,\n info.locale,\n info.buildId ? undefined : info.defaultLocale,\n info.ignorePrefix\n )\n\n if (info.buildId || !info.trailingSlash) {\n pathname = removeTrailingSlash(pathname)\n }\n\n if (info.buildId) {\n pathname = addPathSuffix(\n addPathPrefix(pathname, `/_next/data/${info.buildId}`),\n info.pathname === '/' ? 'index.json' : '.json'\n )\n }\n\n pathname = addPathPrefix(pathname, info.basePath)\n return !info.buildId && info.trailingSlash\n ? !pathname.endsWith('/')\n ? addPathSuffix(pathname, '/')\n : pathname\n : removeTrailingSlash(pathname)\n}\n"],"names":["removeTrailingSlash","addPathPrefix","addPathSuffix","addLocale","formatNextPathnameInfo","info","pathname","locale","buildId","undefined","defaultLocale","ignorePrefix","trailingSlash","basePath","endsWith"],"mappings":"AACA,SAASA,mBAAmB,QAAQ,0BAAyB;AAC7D,SAASC,aAAa,QAAQ,oBAAmB;AACjD,SAASC,aAAa,QAAQ,oBAAmB;AACjD,SAASC,SAAS,QAAQ,eAAc;AAOxC,OAAO,SAASC,uBAAuBC,IAAkB;IACvD,IAAIC,WAAWH,UACbE,KAAKC,QAAQ,EACbD,KAAKE,MAAM,EACXF,KAAKG,OAAO,GAAGC,YAAYJ,KAAKK,aAAa,EAC7CL,KAAKM,YAAY;IAGnB,IAAIN,KAAKG,OAAO,IAAI,CAACH,KAAKO,aAAa,EAAE;QACvCN,WAAWN,oBAAoBM;IACjC;IAEA,IAAID,KAAKG,OAAO,EAAE;QAChBF,WAAWJ,cACTD,cAAcK,UAAU,CAAC,YAAY,EAAED,KAAKG,OAAO,EAAE,GACrDH,KAAKC,QAAQ,KAAK,MAAM,eAAe;IAE3C;IAEAA,WAAWL,cAAcK,UAAUD,KAAKQ,QAAQ;IAChD,OAAO,CAACR,KAAKG,OAAO,IAAIH,KAAKO,aAAa,GACtC,CAACN,SAASQ,QAAQ,CAAC,OACjBZ,cAAcI,UAAU,OACxBA,WACFN,oBAAoBM;AAC1B","ignoreList":[0]}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Format function modified from nodejs
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
import * as querystring from './querystring';
|
||||
const slashedProtocols = /https?|ftp|gopher|file/;
|
||||
export function formatUrl(urlObj) {
|
||||
let { auth, hostname } = urlObj;
|
||||
let protocol = urlObj.protocol || '';
|
||||
let pathname = urlObj.pathname || '';
|
||||
let hash = urlObj.hash || '';
|
||||
let query = urlObj.query || '';
|
||||
let host = false;
|
||||
auth = auth ? encodeURIComponent(auth).replace(/%3A/i, ':') + '@' : '';
|
||||
if (urlObj.host) {
|
||||
host = auth + urlObj.host;
|
||||
} else if (hostname) {
|
||||
host = auth + (~hostname.indexOf(':') ? `[${hostname}]` : hostname);
|
||||
if (urlObj.port) {
|
||||
host += ':' + urlObj.port;
|
||||
}
|
||||
}
|
||||
if (query && typeof query === 'object') {
|
||||
query = String(querystring.urlQueryToSearchParams(query));
|
||||
}
|
||||
let search = urlObj.search || query && `?${query}` || '';
|
||||
if (protocol && !protocol.endsWith(':')) protocol += ':';
|
||||
if (urlObj.slashes || (!protocol || slashedProtocols.test(protocol)) && host !== false) {
|
||||
host = '//' + (host || '');
|
||||
if (pathname && pathname[0] !== '/') pathname = '/' + pathname;
|
||||
} else if (!host) {
|
||||
host = '';
|
||||
}
|
||||
if (hash && hash[0] !== '#') hash = '#' + hash;
|
||||
if (search && search[0] !== '?') search = '?' + search;
|
||||
pathname = pathname.replace(/[?#]/g, encodeURIComponent);
|
||||
search = search.replace('#', '%23');
|
||||
return `${protocol}${host}${pathname}${search}${hash}`;
|
||||
}
|
||||
export const urlObjectKeys = [
|
||||
'auth',
|
||||
'hash',
|
||||
'host',
|
||||
'hostname',
|
||||
'href',
|
||||
'path',
|
||||
'pathname',
|
||||
'port',
|
||||
'protocol',
|
||||
'query',
|
||||
'search',
|
||||
'slashes'
|
||||
];
|
||||
export function formatWithValidation(url) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (url !== null && typeof url === 'object') {
|
||||
Object.keys(url).forEach((key)=>{
|
||||
if (!urlObjectKeys.includes(key)) {
|
||||
console.warn(`Unknown key passed via urlObject into url.format: ${key}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return formatUrl(url);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=format-url.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+8
@@ -0,0 +1,8 @@
|
||||
// Translates a logical route into its pages asset path (relative from a common prefix)
|
||||
// "asset path" being its javascript file, data file, prerendered html,...
|
||||
export default function getAssetPathFromRoute(route, ext = '') {
|
||||
const path = route === '/' ? '/index' : /^\/index(\/|$)/.test(route) ? `/index${route}` : route;
|
||||
return path + ext;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=get-asset-path-from-route.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/get-asset-path-from-route.ts"],"sourcesContent":["// Translates a logical route into its pages asset path (relative from a common prefix)\n// \"asset path\" being its javascript file, data file, prerendered html,...\nexport default function getAssetPathFromRoute(\n route: string,\n ext: string = ''\n): string {\n const path =\n route === '/'\n ? '/index'\n : /^\\/index(\\/|$)/.test(route)\n ? `/index${route}`\n : route\n return path + ext\n}\n"],"names":["getAssetPathFromRoute","route","ext","path","test"],"mappings":"AAAA,uFAAuF;AACvF,0EAA0E;AAC1E,eAAe,SAASA,sBACtBC,KAAa,EACbC,MAAc,EAAE;IAEhB,MAAMC,OACJF,UAAU,MACN,WACA,iBAAiBG,IAAI,CAACH,SACpB,CAAC,MAAM,EAAEA,OAAO,GAChBA;IACR,OAAOE,OAAOD;AAChB","ignoreList":[0]}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
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, staticSiblings) {
|
||||
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,
|
||||
staticSiblings
|
||||
]
|
||||
};
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
const paramCacheKey = Array.isArray(value) ? value.join('/') : value;
|
||||
return {
|
||||
param: segmentKey,
|
||||
// The value that is passed to user code.
|
||||
value,
|
||||
// The value that is rendered in the router tree.
|
||||
// TODO: If the number of static siblings exceeds some threshold (e.g.,
|
||||
// dozens or hundreds), consider sending a Bloom filter instead of the full
|
||||
// array to reduce payload size. The client would then use the Bloom filter
|
||||
// to check membership with a small false positive rate.
|
||||
treeSegment: [
|
||||
segmentKey,
|
||||
paramCacheKey,
|
||||
dynamicParamType,
|
||||
staticSiblings
|
||||
],
|
||||
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
|
||||
+1
File diff suppressed because one or more lines are too long
+42
@@ -0,0 +1,42 @@
|
||||
import { normalizeLocalePath } from '../../i18n/normalize-locale-path';
|
||||
import { removePathPrefix } from './remove-path-prefix';
|
||||
import { pathHasPrefix } from './path-has-prefix';
|
||||
export function getNextPathnameInfo(pathname, options) {
|
||||
const { basePath, i18n, trailingSlash } = options.nextConfig ?? {};
|
||||
const info = {
|
||||
pathname,
|
||||
trailingSlash: pathname !== '/' ? pathname.endsWith('/') : trailingSlash
|
||||
};
|
||||
if (basePath && pathHasPrefix(info.pathname, basePath)) {
|
||||
info.pathname = removePathPrefix(info.pathname, basePath);
|
||||
info.basePath = basePath;
|
||||
}
|
||||
let pathnameNoDataPrefix = info.pathname;
|
||||
if (info.pathname.startsWith('/_next/data/') && info.pathname.endsWith('.json')) {
|
||||
const paths = info.pathname.replace(/^\/_next\/data\//, '').replace(/\.json$/, '').split('/');
|
||||
const buildId = paths[0];
|
||||
info.buildId = buildId;
|
||||
pathnameNoDataPrefix = paths[1] !== 'index' ? `/${paths.slice(1).join('/')}` : '/';
|
||||
// update pathname with normalized if enabled although
|
||||
// we use normalized to populate locale info still
|
||||
if (options.parseData === true) {
|
||||
info.pathname = pathnameNoDataPrefix;
|
||||
}
|
||||
}
|
||||
// If provided, use the locale route normalizer to detect the locale instead
|
||||
// of the function below.
|
||||
if (i18n) {
|
||||
let result = options.i18nProvider ? options.i18nProvider.analyze(info.pathname) : normalizeLocalePath(info.pathname, i18n.locales);
|
||||
info.locale = result.detectedLocale;
|
||||
info.pathname = result.pathname ?? info.pathname;
|
||||
if (!result.detectedLocale && info.buildId) {
|
||||
result = options.i18nProvider ? options.i18nProvider.analyze(pathnameNoDataPrefix) : normalizeLocalePath(pathnameNoDataPrefix, i18n.locales);
|
||||
if (result.detectedLocale) {
|
||||
info.locale = result.detectedLocale;
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=get-next-pathname-info.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+15
@@ -0,0 +1,15 @@
|
||||
// Translate a pages asset path (relative from a common prefix) back into its logical route
|
||||
import { isDynamicRoute } from './is-dynamic';
|
||||
// "asset path" being its javascript file, data file, prerendered html,...
|
||||
export default function getRouteFromAssetPath(assetPath, ext = '') {
|
||||
assetPath = assetPath.replace(/\\/g, '/');
|
||||
assetPath = ext && assetPath.endsWith(ext) ? assetPath.slice(0, -ext.length) : assetPath;
|
||||
if (assetPath.startsWith('/index/') && !isDynamicRoute(assetPath)) {
|
||||
assetPath = assetPath.slice(6);
|
||||
} else if (assetPath === '/index') {
|
||||
assetPath = '/';
|
||||
}
|
||||
return assetPath;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=get-route-from-asset-path.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/get-route-from-asset-path.ts"],"sourcesContent":["// Translate a pages asset path (relative from a common prefix) back into its logical route\n\nimport { isDynamicRoute } from './is-dynamic'\n\n// \"asset path\" being its javascript file, data file, prerendered html,...\nexport default function getRouteFromAssetPath(\n assetPath: string,\n ext: string = ''\n): string {\n assetPath = assetPath.replace(/\\\\/g, '/')\n assetPath =\n ext && assetPath.endsWith(ext) ? assetPath.slice(0, -ext.length) : assetPath\n if (assetPath.startsWith('/index/') && !isDynamicRoute(assetPath)) {\n assetPath = assetPath.slice(6)\n } else if (assetPath === '/index') {\n assetPath = '/'\n }\n return assetPath\n}\n"],"names":["isDynamicRoute","getRouteFromAssetPath","assetPath","ext","replace","endsWith","slice","length","startsWith"],"mappings":"AAAA,2FAA2F;AAE3F,SAASA,cAAc,QAAQ,eAAc;AAE7C,0EAA0E;AAC1E,eAAe,SAASC,sBACtBC,SAAiB,EACjBC,MAAc,EAAE;IAEhBD,YAAYA,UAAUE,OAAO,CAAC,OAAO;IACrCF,YACEC,OAAOD,UAAUG,QAAQ,CAACF,OAAOD,UAAUI,KAAK,CAAC,GAAG,CAACH,IAAII,MAAM,IAAIL;IACrE,IAAIA,UAAUM,UAAU,CAAC,cAAc,CAACR,eAAeE,YAAY;QACjEA,YAAYA,UAAUI,KAAK,CAAC;IAC9B,OAAO,IAAIJ,cAAc,UAAU;QACjCA,YAAY;IACd;IACA,OAAOA;AACT","ignoreList":[0]}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { INTERCEPTION_ROUTE_MARKERS } from './interception-routes';
|
||||
/**
|
||||
* Parse dynamic route segment to type of parameter
|
||||
*/ export function getSegmentParam(segment) {
|
||||
const interceptionMarker = INTERCEPTION_ROUTE_MARKERS.find((marker)=>segment.startsWith(marker));
|
||||
// if an interception marker is part of the path segment, we need to jump ahead
|
||||
// to the relevant portion for param parsing
|
||||
if (interceptionMarker) {
|
||||
segment = segment.slice(interceptionMarker.length);
|
||||
}
|
||||
if (segment.startsWith('[[...') && segment.endsWith(']]')) {
|
||||
return {
|
||||
// TODO-APP: Optional catchall does not currently work with parallel routes,
|
||||
// so for now aren't handling a potential interception marker.
|
||||
paramType: 'optional-catchall',
|
||||
paramName: segment.slice(5, -2)
|
||||
};
|
||||
}
|
||||
if (segment.startsWith('[...') && segment.endsWith(']')) {
|
||||
return {
|
||||
paramType: interceptionMarker ? `catchall-intercepted-${interceptionMarker}` : 'catchall',
|
||||
paramName: segment.slice(4, -1)
|
||||
};
|
||||
}
|
||||
if (segment.startsWith('[') && segment.endsWith(']')) {
|
||||
return {
|
||||
paramType: interceptionMarker ? `dynamic-intercepted-${interceptionMarker}` : 'dynamic',
|
||||
paramName: segment.slice(1, -1)
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export function isCatchAll(type) {
|
||||
return type === 'catchall' || type === 'catchall-intercepted-(..)(..)' || type === 'catchall-intercepted-(.)' || type === 'catchall-intercepted-(..)' || type === 'catchall-intercepted-(...)' || type === 'optional-catchall';
|
||||
}
|
||||
export function getParamProperties(paramType) {
|
||||
let repeat = false;
|
||||
let optional = false;
|
||||
switch(paramType){
|
||||
case 'catchall':
|
||||
case 'catchall-intercepted-(..)(..)':
|
||||
case 'catchall-intercepted-(.)':
|
||||
case 'catchall-intercepted-(..)':
|
||||
case 'catchall-intercepted-(...)':
|
||||
repeat = true;
|
||||
break;
|
||||
case 'optional-catchall':
|
||||
repeat = true;
|
||||
optional = true;
|
||||
break;
|
||||
case 'dynamic':
|
||||
case 'dynamic-intercepted-(..)(..)':
|
||||
case 'dynamic-intercepted-(.)':
|
||||
case 'dynamic-intercepted-(..)':
|
||||
case 'dynamic-intercepted-(...)':
|
||||
break;
|
||||
default:
|
||||
paramType;
|
||||
}
|
||||
return {
|
||||
repeat,
|
||||
optional
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=get-segment-param.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/get-segment-param.tsx"],"sourcesContent":["import { INTERCEPTION_ROUTE_MARKERS } from './interception-routes'\nimport type { DynamicParamTypes } from '../../app-router-types'\n\nexport type SegmentParam = {\n paramName: string\n paramType: DynamicParamTypes\n}\n\n/**\n * Parse dynamic route segment to type of parameter\n */\nexport function getSegmentParam(segment: string): SegmentParam | null {\n const interceptionMarker = INTERCEPTION_ROUTE_MARKERS.find((marker) =>\n segment.startsWith(marker)\n )\n\n // if an interception marker is part of the path segment, we need to jump ahead\n // to the relevant portion for param parsing\n if (interceptionMarker) {\n segment = segment.slice(interceptionMarker.length)\n }\n\n if (segment.startsWith('[[...') && segment.endsWith(']]')) {\n return {\n // TODO-APP: Optional catchall does not currently work with parallel routes,\n // so for now aren't handling a potential interception marker.\n paramType: 'optional-catchall',\n paramName: segment.slice(5, -2),\n }\n }\n\n if (segment.startsWith('[...') && segment.endsWith(']')) {\n return {\n paramType: interceptionMarker\n ? `catchall-intercepted-${interceptionMarker}`\n : 'catchall',\n paramName: segment.slice(4, -1),\n }\n }\n\n if (segment.startsWith('[') && segment.endsWith(']')) {\n return {\n paramType: interceptionMarker\n ? `dynamic-intercepted-${interceptionMarker}`\n : 'dynamic',\n paramName: segment.slice(1, -1),\n }\n }\n\n return null\n}\n\nexport function isCatchAll(\n type: DynamicParamTypes\n): type is\n | 'catchall'\n | 'catchall-intercepted-(..)(..)'\n | 'catchall-intercepted-(.)'\n | 'catchall-intercepted-(..)'\n | 'catchall-intercepted-(...)'\n | 'optional-catchall' {\n return (\n type === 'catchall' ||\n type === 'catchall-intercepted-(..)(..)' ||\n type === 'catchall-intercepted-(.)' ||\n type === 'catchall-intercepted-(..)' ||\n type === 'catchall-intercepted-(...)' ||\n type === 'optional-catchall'\n )\n}\n\nexport function getParamProperties(paramType: DynamicParamTypes): {\n repeat: boolean\n optional: boolean\n} {\n let repeat = false\n let optional = false\n\n switch (paramType) {\n case 'catchall':\n case 'catchall-intercepted-(..)(..)':\n case 'catchall-intercepted-(.)':\n case 'catchall-intercepted-(..)':\n case 'catchall-intercepted-(...)':\n repeat = true\n break\n case 'optional-catchall':\n repeat = true\n optional = true\n break\n case 'dynamic':\n case 'dynamic-intercepted-(..)(..)':\n case 'dynamic-intercepted-(.)':\n case 'dynamic-intercepted-(..)':\n case 'dynamic-intercepted-(...)':\n break\n default:\n paramType satisfies never\n }\n\n return { repeat, optional }\n}\n"],"names":["INTERCEPTION_ROUTE_MARKERS","getSegmentParam","segment","interceptionMarker","find","marker","startsWith","slice","length","endsWith","paramType","paramName","isCatchAll","type","getParamProperties","repeat","optional"],"mappings":"AAAA,SAASA,0BAA0B,QAAQ,wBAAuB;AAQlE;;CAEC,GACD,OAAO,SAASC,gBAAgBC,OAAe;IAC7C,MAAMC,qBAAqBH,2BAA2BI,IAAI,CAAC,CAACC,SAC1DH,QAAQI,UAAU,CAACD;IAGrB,+EAA+E;IAC/E,4CAA4C;IAC5C,IAAIF,oBAAoB;QACtBD,UAAUA,QAAQK,KAAK,CAACJ,mBAAmBK,MAAM;IACnD;IAEA,IAAIN,QAAQI,UAAU,CAAC,YAAYJ,QAAQO,QAAQ,CAAC,OAAO;QACzD,OAAO;YACL,4EAA4E;YAC5E,8DAA8D;YAC9DC,WAAW;YACXC,WAAWT,QAAQK,KAAK,CAAC,GAAG,CAAC;QAC/B;IACF;IAEA,IAAIL,QAAQI,UAAU,CAAC,WAAWJ,QAAQO,QAAQ,CAAC,MAAM;QACvD,OAAO;YACLC,WAAWP,qBACP,CAAC,qBAAqB,EAAEA,oBAAoB,GAC5C;YACJQ,WAAWT,QAAQK,KAAK,CAAC,GAAG,CAAC;QAC/B;IACF;IAEA,IAAIL,QAAQI,UAAU,CAAC,QAAQJ,QAAQO,QAAQ,CAAC,MAAM;QACpD,OAAO;YACLC,WAAWP,qBACP,CAAC,oBAAoB,EAAEA,oBAAoB,GAC3C;YACJQ,WAAWT,QAAQK,KAAK,CAAC,GAAG,CAAC;QAC/B;IACF;IAEA,OAAO;AACT;AAEA,OAAO,SAASK,WACdC,IAAuB;IAQvB,OACEA,SAAS,cACTA,SAAS,mCACTA,SAAS,8BACTA,SAAS,+BACTA,SAAS,gCACTA,SAAS;AAEb;AAEA,OAAO,SAASC,mBAAmBJ,SAA4B;IAI7D,IAAIK,SAAS;IACb,IAAIC,WAAW;IAEf,OAAQN;QACN,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;YACHK,SAAS;YACT;QACF,KAAK;YACHA,SAAS;YACTC,WAAW;YACX;QACF,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;YACH;QACF;YACEN;IACJ;IAEA,OAAO;QAAEK;QAAQC;IAAS;AAC5B","ignoreList":[0]}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// This regex contains the bots that we need to do a blocking render for and can't safely stream the response
|
||||
// due to how they parse the DOM. For example, they might explicitly check for metadata in the `head` tag, so we can't stream metadata tags after the `head` was sent.
|
||||
// Note: The pattern [\w-]+-Google captures all Google crawlers with "-Google" suffix (e.g., Mediapartners-Google, AdsBot-Google, Storebot-Google)
|
||||
// as well as crawlers starting with "Google-" (e.g., Google-PageRenderer, Google-InspectionTool)
|
||||
export const HTML_LIMITED_BOT_UA_RE = /[\w-]+-Google|Google-[\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight/i;
|
||||
|
||||
//# sourceMappingURL=html-bots.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/html-bots.ts"],"sourcesContent":["// This regex contains the bots that we need to do a blocking render for and can't safely stream the response\n// due to how they parse the DOM. For example, they might explicitly check for metadata in the `head` tag, so we can't stream metadata tags after the `head` was sent.\n// Note: The pattern [\\w-]+-Google captures all Google crawlers with \"-Google\" suffix (e.g., Mediapartners-Google, AdsBot-Google, Storebot-Google)\n// as well as crawlers starting with \"Google-\" (e.g., Google-PageRenderer, Google-InspectionTool)\nexport const HTML_LIMITED_BOT_UA_RE =\n /[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight/i\n"],"names":["HTML_LIMITED_BOT_UA_RE"],"mappings":"AAAA,6GAA6G;AAC7G,sKAAsK;AACtK,kJAAkJ;AAClJ,iGAAiG;AACjG,OAAO,MAAMA,yBACX,sTAAqT","ignoreList":[0]}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export { getSortedRoutes, getSortedRouteObjects } from './sorted-routes';
|
||||
export { isDynamicRoute } from './is-dynamic';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/index.ts"],"sourcesContent":["export { getSortedRoutes, getSortedRouteObjects } from './sorted-routes'\nexport { isDynamicRoute } from './is-dynamic'\n"],"names":["getSortedRoutes","getSortedRouteObjects","isDynamicRoute"],"mappings":"AAAA,SAASA,eAAe,EAAEC,qBAAqB,QAAQ,kBAAiB;AACxE,SAASC,cAAc,QAAQ,eAAc","ignoreList":[0]}
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
export function interceptionPrefixFromParamType(paramType) {
|
||||
switch(paramType){
|
||||
case 'catchall-intercepted-(..)(..)':
|
||||
case 'dynamic-intercepted-(..)(..)':
|
||||
return '(..)(..)';
|
||||
case 'catchall-intercepted-(.)':
|
||||
case 'dynamic-intercepted-(.)':
|
||||
return '(.)';
|
||||
case 'catchall-intercepted-(..)':
|
||||
case 'dynamic-intercepted-(..)':
|
||||
return '(..)';
|
||||
case 'catchall-intercepted-(...)':
|
||||
case 'dynamic-intercepted-(...)':
|
||||
return '(...)';
|
||||
case 'catchall':
|
||||
case 'dynamic':
|
||||
case 'optional-catchall':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=interception-prefix-from-param-type.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/interception-prefix-from-param-type.ts"],"sourcesContent":["import type { DynamicParamTypes } from '../../app-router-types'\n\nexport function interceptionPrefixFromParamType(\n paramType: DynamicParamTypes\n): string | null {\n switch (paramType) {\n case 'catchall-intercepted-(..)(..)':\n case 'dynamic-intercepted-(..)(..)':\n return '(..)(..)'\n case 'catchall-intercepted-(.)':\n case 'dynamic-intercepted-(.)':\n return '(.)'\n case 'catchall-intercepted-(..)':\n case 'dynamic-intercepted-(..)':\n return '(..)'\n case 'catchall-intercepted-(...)':\n case 'dynamic-intercepted-(...)':\n return '(...)'\n case 'catchall':\n case 'dynamic':\n case 'optional-catchall':\n default:\n return null\n }\n}\n"],"names":["interceptionPrefixFromParamType","paramType"],"mappings":"AAEA,OAAO,SAASA,gCACdC,SAA4B;IAE5B,OAAQA;QACN,KAAK;QACL,KAAK;YACH,OAAO;QACT,KAAK;QACL,KAAK;YACH,OAAO;QACT,KAAK;QACL,KAAK;YACH,OAAO;QACT,KAAK;QACL,KAAK;YACH,OAAO;QACT,KAAK;QACL,KAAK;QACL,KAAK;QACL;YACE,OAAO;IACX;AACF","ignoreList":[0]}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import { normalizeAppPath } from './app-paths';
|
||||
// order matters here, the first match will be used
|
||||
export const INTERCEPTION_ROUTE_MARKERS = [
|
||||
'(..)(..)',
|
||||
'(.)',
|
||||
'(..)',
|
||||
'(...)'
|
||||
];
|
||||
export function isInterceptionRouteAppPath(path) {
|
||||
// TODO-APP: add more serious validation
|
||||
return path.split('/').find((segment)=>INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m))) !== undefined;
|
||||
}
|
||||
export function extractInterceptionRouteInformation(path) {
|
||||
let interceptingRoute;
|
||||
let marker;
|
||||
let interceptedRoute;
|
||||
for (const segment of path.split('/')){
|
||||
marker = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
|
||||
if (marker) {
|
||||
;
|
||||
[interceptingRoute, interceptedRoute] = path.split(marker, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!interceptingRoute || !marker || !interceptedRoute) {
|
||||
throw Object.defineProperty(new Error(`Invalid interception route: ${path}. Must be in the format /<intercepting route>/(..|...|..)(..)/<intercepted route>`), "__NEXT_ERROR_CODE", {
|
||||
value: "E269",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
interceptingRoute = normalizeAppPath(interceptingRoute) // normalize the path, e.g. /(blog)/feed -> /feed
|
||||
;
|
||||
switch(marker){
|
||||
case '(.)':
|
||||
// (.) indicates that we should match with sibling routes, so we just need to append the intercepted route to the intercepting route
|
||||
if (interceptingRoute === '/') {
|
||||
interceptedRoute = `/${interceptedRoute}`;
|
||||
} else {
|
||||
interceptedRoute = interceptingRoute + '/' + interceptedRoute;
|
||||
}
|
||||
break;
|
||||
case '(..)':
|
||||
// (..) indicates that we should match at one level up, so we need to remove the last segment of the intercepting route
|
||||
if (interceptingRoute === '/') {
|
||||
throw Object.defineProperty(new Error(`Invalid interception route: ${path}. Cannot use (..) marker at the root level, use (.) instead.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E207",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
interceptedRoute = interceptingRoute.split('/').slice(0, -1).concat(interceptedRoute).join('/');
|
||||
break;
|
||||
case '(...)':
|
||||
// (...) will match the route segment in the root directory, so we need to use the root directory to prepend the intercepted route
|
||||
interceptedRoute = '/' + interceptedRoute;
|
||||
break;
|
||||
case '(..)(..)':
|
||||
// (..)(..) indicates that we should match at two levels up, so we need to remove the last two segments of the intercepting route
|
||||
const splitInterceptingRoute = interceptingRoute.split('/');
|
||||
if (splitInterceptingRoute.length <= 2) {
|
||||
throw Object.defineProperty(new Error(`Invalid interception route: ${path}. Cannot use (..)(..) marker at the root level or one level up.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E486",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
interceptedRoute = splitInterceptingRoute.slice(0, -2).concat(interceptedRoute).join('/');
|
||||
break;
|
||||
default:
|
||||
throw Object.defineProperty(new Error('Invariant: unexpected marker'), "__NEXT_ERROR_CODE", {
|
||||
value: "E112",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
return {
|
||||
interceptingRoute,
|
||||
interceptedRoute
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=interception-routes.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+43
@@ -0,0 +1,43 @@
|
||||
import { getRouteMatcher } from './route-matcher';
|
||||
import { getRouteRegex } from './route-regex';
|
||||
export function interpolateAs(route, asPathname, query) {
|
||||
let interpolatedRoute = '';
|
||||
const dynamicRegex = getRouteRegex(route);
|
||||
const dynamicGroups = dynamicRegex.groups;
|
||||
const dynamicMatches = // Try to match the dynamic route against the asPath
|
||||
(asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : '') || // Fall back to reading the values from the href
|
||||
// TODO: should this take priority; also need to change in the router.
|
||||
query;
|
||||
interpolatedRoute = route;
|
||||
const params = Object.keys(dynamicGroups);
|
||||
if (!params.every((param)=>{
|
||||
let value = dynamicMatches[param] || '';
|
||||
const { repeat, optional } = dynamicGroups[param];
|
||||
// support single-level catch-all
|
||||
// TODO: more robust handling for user-error (passing `/`)
|
||||
let replaced = `[${repeat ? '...' : ''}${param}]`;
|
||||
if (optional) {
|
||||
replaced = `${!value ? '/' : ''}[${replaced}]`;
|
||||
}
|
||||
if (repeat && !Array.isArray(value)) value = [
|
||||
value
|
||||
];
|
||||
return (optional || param in dynamicMatches) && // Interpolate group into data URL if present
|
||||
(interpolatedRoute = interpolatedRoute.replace(replaced, repeat ? value.map(// these values should be fully encoded instead of just
|
||||
// path delimiter escaped since they are being inserted
|
||||
// into the URL and we expect URL encoded segments
|
||||
// when parsing dynamic route params
|
||||
(segment)=>encodeURIComponent(segment)).join('/') : encodeURIComponent(value)) || '/');
|
||||
})) {
|
||||
interpolatedRoute = '' // did not satisfy all requirements
|
||||
;
|
||||
// n.b. We ignore this error because we handle warning for this case in
|
||||
// development in the `<Link>` component directly.
|
||||
}
|
||||
return {
|
||||
params,
|
||||
result: interpolatedRoute
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=interpolate-as.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/interpolate-as.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\n\nimport { getRouteMatcher } from './route-matcher'\nimport { getRouteRegex } from './route-regex'\n\nexport function interpolateAs(\n route: string,\n asPathname: string,\n query: ParsedUrlQuery\n) {\n let interpolatedRoute = ''\n\n const dynamicRegex = getRouteRegex(route)\n const dynamicGroups = dynamicRegex.groups\n const dynamicMatches =\n // Try to match the dynamic route against the asPath\n (asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : '') ||\n // Fall back to reading the values from the href\n // TODO: should this take priority; also need to change in the router.\n query\n\n interpolatedRoute = route\n const params = Object.keys(dynamicGroups)\n\n if (\n !params.every((param) => {\n let value = dynamicMatches[param] || ''\n const { repeat, optional } = dynamicGroups[param]\n\n // support single-level catch-all\n // TODO: more robust handling for user-error (passing `/`)\n let replaced = `[${repeat ? '...' : ''}${param}]`\n if (optional) {\n replaced = `${!value ? '/' : ''}[${replaced}]`\n }\n if (repeat && !Array.isArray(value)) value = [value]\n\n return (\n (optional || param in dynamicMatches) &&\n // Interpolate group into data URL if present\n (interpolatedRoute =\n interpolatedRoute!.replace(\n replaced,\n repeat\n ? (value as string[])\n .map(\n // these values should be fully encoded instead of just\n // path delimiter escaped since they are being inserted\n // into the URL and we expect URL encoded segments\n // when parsing dynamic route params\n (segment) => encodeURIComponent(segment)\n )\n .join('/')\n : encodeURIComponent(value as string)\n ) || '/')\n )\n })\n ) {\n interpolatedRoute = '' // did not satisfy all requirements\n\n // n.b. We ignore this error because we handle warning for this case in\n // development in the `<Link>` component directly.\n }\n return {\n params,\n result: interpolatedRoute,\n }\n}\n"],"names":["getRouteMatcher","getRouteRegex","interpolateAs","route","asPathname","query","interpolatedRoute","dynamicRegex","dynamicGroups","groups","dynamicMatches","params","Object","keys","every","param","value","repeat","optional","replaced","Array","isArray","replace","map","segment","encodeURIComponent","join","result"],"mappings":"AAEA,SAASA,eAAe,QAAQ,kBAAiB;AACjD,SAASC,aAAa,QAAQ,gBAAe;AAE7C,OAAO,SAASC,cACdC,KAAa,EACbC,UAAkB,EAClBC,KAAqB;IAErB,IAAIC,oBAAoB;IAExB,MAAMC,eAAeN,cAAcE;IACnC,MAAMK,gBAAgBD,aAAaE,MAAM;IACzC,MAAMC,iBAEJ,AADA,oDAAoD;IACnDN,CAAAA,eAAeD,QAAQH,gBAAgBO,cAAcH,cAAc,EAAC,KACrE,gDAAgD;IAChD,sEAAsE;IACtEC;IAEFC,oBAAoBH;IACpB,MAAMQ,SAASC,OAAOC,IAAI,CAACL;IAE3B,IACE,CAACG,OAAOG,KAAK,CAAC,CAACC;QACb,IAAIC,QAAQN,cAAc,CAACK,MAAM,IAAI;QACrC,MAAM,EAAEE,MAAM,EAAEC,QAAQ,EAAE,GAAGV,aAAa,CAACO,MAAM;QAEjD,iCAAiC;QACjC,0DAA0D;QAC1D,IAAII,WAAW,CAAC,CAAC,EAAEF,SAAS,QAAQ,KAAKF,MAAM,CAAC,CAAC;QACjD,IAAIG,UAAU;YACZC,WAAW,GAAG,CAACH,QAAQ,MAAM,GAAG,CAAC,EAAEG,SAAS,CAAC,CAAC;QAChD;QACA,IAAIF,UAAU,CAACG,MAAMC,OAAO,CAACL,QAAQA,QAAQ;YAACA;SAAM;QAEpD,OACE,AAACE,CAAAA,YAAYH,SAASL,cAAa,KACnC,6CAA6C;QAC5CJ,CAAAA,oBACCA,kBAAmBgB,OAAO,CACxBH,UACAF,SACI,AAACD,MACEO,GAAG,CACF,uDAAuD;QACvD,uDAAuD;QACvD,kDAAkD;QAClD,oCAAoC;QACpC,CAACC,UAAYC,mBAAmBD,UAEjCE,IAAI,CAAC,OACRD,mBAAmBT,WACpB,GAAE;IAEb,IACA;QACAV,oBAAoB,GAAG,mCAAmC;;IAE1D,uEAAuE;IACvE,kDAAkD;IACpD;IACA,OAAO;QACLK;QACAgB,QAAQrB;IACV;AACF","ignoreList":[0]}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { HTML_LIMITED_BOT_UA_RE } from './html-bots';
|
||||
// Bot crawler that will spin up a headless browser and execute JS.
|
||||
// Only the main Googlebot search crawler executes JavaScript, not other Google crawlers.
|
||||
// x-ref: https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers
|
||||
// This regex specifically matches "Googlebot" but NOT "Mediapartners-Google", "AdsBot-Google", etc.
|
||||
const HEADLESS_BROWSER_BOT_UA_RE = /Googlebot(?!-)|Googlebot$/i;
|
||||
export const HTML_LIMITED_BOT_UA_RE_STRING = HTML_LIMITED_BOT_UA_RE.source;
|
||||
export { HTML_LIMITED_BOT_UA_RE };
|
||||
function isDomBotUA(userAgent) {
|
||||
return HEADLESS_BROWSER_BOT_UA_RE.test(userAgent);
|
||||
}
|
||||
function isHtmlLimitedBotUA(userAgent) {
|
||||
return HTML_LIMITED_BOT_UA_RE.test(userAgent);
|
||||
}
|
||||
export function isBot(userAgent) {
|
||||
return isDomBotUA(userAgent) || isHtmlLimitedBotUA(userAgent);
|
||||
}
|
||||
export function getBotType(userAgent) {
|
||||
if (isDomBotUA(userAgent)) {
|
||||
return 'dom';
|
||||
}
|
||||
if (isHtmlLimitedBotUA(userAgent)) {
|
||||
return 'html';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=is-bot.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/is-bot.ts"],"sourcesContent":["import { HTML_LIMITED_BOT_UA_RE } from './html-bots'\n\n// Bot crawler that will spin up a headless browser and execute JS.\n// Only the main Googlebot search crawler executes JavaScript, not other Google crawlers.\n// x-ref: https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers\n// This regex specifically matches \"Googlebot\" but NOT \"Mediapartners-Google\", \"AdsBot-Google\", etc.\nconst HEADLESS_BROWSER_BOT_UA_RE = /Googlebot(?!-)|Googlebot$/i\n\nexport const HTML_LIMITED_BOT_UA_RE_STRING = HTML_LIMITED_BOT_UA_RE.source\n\nexport { HTML_LIMITED_BOT_UA_RE }\n\nfunction isDomBotUA(userAgent: string) {\n return HEADLESS_BROWSER_BOT_UA_RE.test(userAgent)\n}\n\nfunction isHtmlLimitedBotUA(userAgent: string) {\n return HTML_LIMITED_BOT_UA_RE.test(userAgent)\n}\n\nexport function isBot(userAgent: string): boolean {\n return isDomBotUA(userAgent) || isHtmlLimitedBotUA(userAgent)\n}\n\nexport function getBotType(userAgent: string): 'dom' | 'html' | undefined {\n if (isDomBotUA(userAgent)) {\n return 'dom'\n }\n if (isHtmlLimitedBotUA(userAgent)) {\n return 'html'\n }\n return undefined\n}\n"],"names":["HTML_LIMITED_BOT_UA_RE","HEADLESS_BROWSER_BOT_UA_RE","HTML_LIMITED_BOT_UA_RE_STRING","source","isDomBotUA","userAgent","test","isHtmlLimitedBotUA","isBot","getBotType","undefined"],"mappings":"AAAA,SAASA,sBAAsB,QAAQ,cAAa;AAEpD,mEAAmE;AACnE,yFAAyF;AACzF,4FAA4F;AAC5F,oGAAoG;AACpG,MAAMC,6BAA6B;AAEnC,OAAO,MAAMC,gCAAgCF,uBAAuBG,MAAM,CAAA;AAE1E,SAASH,sBAAsB,GAAE;AAEjC,SAASI,WAAWC,SAAiB;IACnC,OAAOJ,2BAA2BK,IAAI,CAACD;AACzC;AAEA,SAASE,mBAAmBF,SAAiB;IAC3C,OAAOL,uBAAuBM,IAAI,CAACD;AACrC;AAEA,OAAO,SAASG,MAAMH,SAAiB;IACrC,OAAOD,WAAWC,cAAcE,mBAAmBF;AACrD;AAEA,OAAO,SAASI,WAAWJ,SAAiB;IAC1C,IAAID,WAAWC,YAAY;QACzB,OAAO;IACT;IACA,IAAIE,mBAAmBF,YAAY;QACjC,OAAO;IACT;IACA,OAAOK;AACT","ignoreList":[0]}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { extractInterceptionRouteInformation, isInterceptionRouteAppPath } from './interception-routes';
|
||||
// Identify /.*[param].*/ in route string
|
||||
const TEST_ROUTE = /\/[^/]*\[[^/]+\][^/]*(?=\/|$)/;
|
||||
// Identify /[param]/ in route string
|
||||
const TEST_STRICT_ROUTE = /\/\[[^/]+\](?=\/|$)/;
|
||||
/**
|
||||
* Check if a route is dynamic.
|
||||
*
|
||||
* @param route - The route to check.
|
||||
* @param strict - Whether to use strict mode which prohibits segments with prefixes/suffixes (default: true).
|
||||
* @returns Whether the route is dynamic.
|
||||
*/ export function isDynamicRoute(route, strict = true) {
|
||||
if (isInterceptionRouteAppPath(route)) {
|
||||
route = extractInterceptionRouteInformation(route).interceptedRoute;
|
||||
}
|
||||
if (strict) {
|
||||
return TEST_STRICT_ROUTE.test(route);
|
||||
}
|
||||
return TEST_ROUTE.test(route);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=is-dynamic.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/is-dynamic.ts"],"sourcesContent":["import {\n extractInterceptionRouteInformation,\n isInterceptionRouteAppPath,\n} from './interception-routes'\n\n// Identify /.*[param].*/ in route string\nconst TEST_ROUTE = /\\/[^/]*\\[[^/]+\\][^/]*(?=\\/|$)/\n\n// Identify /[param]/ in route string\nconst TEST_STRICT_ROUTE = /\\/\\[[^/]+\\](?=\\/|$)/\n\n/**\n * Check if a route is dynamic.\n *\n * @param route - The route to check.\n * @param strict - Whether to use strict mode which prohibits segments with prefixes/suffixes (default: true).\n * @returns Whether the route is dynamic.\n */\nexport function isDynamicRoute(route: string, strict: boolean = true): boolean {\n if (isInterceptionRouteAppPath(route)) {\n route = extractInterceptionRouteInformation(route).interceptedRoute\n }\n\n if (strict) {\n return TEST_STRICT_ROUTE.test(route)\n }\n\n return TEST_ROUTE.test(route)\n}\n"],"names":["extractInterceptionRouteInformation","isInterceptionRouteAppPath","TEST_ROUTE","TEST_STRICT_ROUTE","isDynamicRoute","route","strict","interceptedRoute","test"],"mappings":"AAAA,SACEA,mCAAmC,EACnCC,0BAA0B,QACrB,wBAAuB;AAE9B,yCAAyC;AACzC,MAAMC,aAAa;AAEnB,qCAAqC;AACrC,MAAMC,oBAAoB;AAE1B;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,KAAa,EAAEC,SAAkB,IAAI;IAClE,IAAIL,2BAA2BI,QAAQ;QACrCA,QAAQL,oCAAoCK,OAAOE,gBAAgB;IACrE;IAEA,IAAID,QAAQ;QACV,OAAOH,kBAAkBK,IAAI,CAACH;IAChC;IAEA,OAAOH,WAAWM,IAAI,CAACH;AACzB","ignoreList":[0]}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { isAbsoluteUrl, getLocationOrigin } from '../../utils';
|
||||
import { hasBasePath } from '../../../../client/has-base-path';
|
||||
/**
|
||||
* Detects whether a given url is routable by the Next.js router (browser only).
|
||||
*/ export function isLocalURL(url) {
|
||||
// prevent a hydration mismatch on href for url with anchor refs
|
||||
if (!isAbsoluteUrl(url)) return true;
|
||||
try {
|
||||
// absolute urls can be local if they are on the same origin
|
||||
const locationOrigin = getLocationOrigin();
|
||||
const resolved = new URL(url, locationOrigin);
|
||||
return resolved.origin === locationOrigin && hasBasePath(resolved.pathname);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=is-local-url.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/is-local-url.ts"],"sourcesContent":["import { isAbsoluteUrl, getLocationOrigin } from '../../utils'\nimport { hasBasePath } from '../../../../client/has-base-path'\n\n/**\n * Detects whether a given url is routable by the Next.js router (browser only).\n */\nexport function isLocalURL(url: string): boolean {\n // prevent a hydration mismatch on href for url with anchor refs\n if (!isAbsoluteUrl(url)) return true\n try {\n // absolute urls can be local if they are on the same origin\n const locationOrigin = getLocationOrigin()\n const resolved = new URL(url, locationOrigin)\n return resolved.origin === locationOrigin && hasBasePath(resolved.pathname)\n } catch (_) {\n return false\n }\n}\n"],"names":["isAbsoluteUrl","getLocationOrigin","hasBasePath","isLocalURL","url","locationOrigin","resolved","URL","origin","pathname","_"],"mappings":"AAAA,SAASA,aAAa,EAAEC,iBAAiB,QAAQ,cAAa;AAC9D,SAASC,WAAW,QAAQ,mCAAkC;AAE9D;;CAEC,GACD,OAAO,SAASC,WAAWC,GAAW;IACpC,gEAAgE;IAChE,IAAI,CAACJ,cAAcI,MAAM,OAAO;IAChC,IAAI;QACF,4DAA4D;QAC5D,MAAMC,iBAAiBJ;QACvB,MAAMK,WAAW,IAAIC,IAAIH,KAAKC;QAC9B,OAAOC,SAASE,MAAM,KAAKH,kBAAkBH,YAAYI,SAASG,QAAQ;IAC5E,EAAE,OAAOC,GAAG;QACV,OAAO;IACT;AACF","ignoreList":[0]}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { matchHas } from './prepare-destination';
|
||||
export function getMiddlewareRouteMatcher(matchers) {
|
||||
return (pathname, req, query)=>{
|
||||
for (const matcher of matchers){
|
||||
const routeMatch = new RegExp(matcher.regexp).exec(pathname);
|
||||
if (!routeMatch) {
|
||||
continue;
|
||||
}
|
||||
if (matcher.has || matcher.missing) {
|
||||
const hasParams = matchHas(req, query, matcher.has, matcher.missing);
|
||||
if (!hasParams) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=middleware-route-matcher.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/middleware-route-matcher.ts"],"sourcesContent":["import type { BaseNextRequest } from '../../../../server/base-http'\nimport type { ProxyMatcher } from '../../../../build/analysis/get-page-static-info'\nimport type { Params } from '../../../../server/request/params'\nimport { matchHas } from './prepare-destination'\n\nexport interface MiddlewareRouteMatch {\n (\n pathname: string | null | undefined,\n request: BaseNextRequest,\n query: Params\n ): boolean\n}\n\nexport function getMiddlewareRouteMatcher(\n matchers: ProxyMatcher[]\n): MiddlewareRouteMatch {\n return (\n pathname: string | null | undefined,\n req: BaseNextRequest,\n query: Params\n ) => {\n for (const matcher of matchers) {\n const routeMatch = new RegExp(matcher.regexp).exec(pathname!)\n if (!routeMatch) {\n continue\n }\n\n if (matcher.has || matcher.missing) {\n const hasParams = matchHas(req, query, matcher.has, matcher.missing)\n if (!hasParams) {\n continue\n }\n }\n\n return true\n }\n\n return false\n }\n}\n"],"names":["matchHas","getMiddlewareRouteMatcher","matchers","pathname","req","query","matcher","routeMatch","RegExp","regexp","exec","has","missing","hasParams"],"mappings":"AAGA,SAASA,QAAQ,QAAQ,wBAAuB;AAUhD,OAAO,SAASC,0BACdC,QAAwB;IAExB,OAAO,CACLC,UACAC,KACAC;QAEA,KAAK,MAAMC,WAAWJ,SAAU;YAC9B,MAAMK,aAAa,IAAIC,OAAOF,QAAQG,MAAM,EAAEC,IAAI,CAACP;YACnD,IAAI,CAACI,YAAY;gBACf;YACF;YAEA,IAAID,QAAQK,GAAG,IAAIL,QAAQM,OAAO,EAAE;gBAClC,MAAMC,YAAYb,SAASI,KAAKC,OAAOC,QAAQK,GAAG,EAAEL,QAAQM,OAAO;gBACnE,IAAI,CAACC,WAAW;oBACd;gBACF;YACF;YAEA,OAAO;QACT;QAEA,OAAO;IACT;AACF","ignoreList":[0]}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export function omit(object, keys) {
|
||||
const omitted = {};
|
||||
Object.keys(object).forEach((key)=>{
|
||||
if (!keys.includes(key)) {
|
||||
omitted[key] = object[key];
|
||||
}
|
||||
});
|
||||
return omitted;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=omit.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/omit.ts"],"sourcesContent":["export function omit<T extends { [key: string]: unknown }, K extends keyof T>(\n object: T,\n keys: K[]\n): Omit<T, K> {\n const omitted: { [key: string]: unknown } = {}\n Object.keys(object).forEach((key) => {\n if (!keys.includes(key as K)) {\n omitted[key] = object[key]\n }\n })\n return omitted as Omit<T, K>\n}\n"],"names":["omit","object","keys","omitted","Object","forEach","key","includes"],"mappings":"AAAA,OAAO,SAASA,KACdC,MAAS,EACTC,IAAS;IAET,MAAMC,UAAsC,CAAC;IAC7CC,OAAOF,IAAI,CAACD,QAAQI,OAAO,CAAC,CAACC;QAC3B,IAAI,CAACJ,KAAKK,QAAQ,CAACD,MAAW;YAC5BH,OAAO,CAACG,IAAI,GAAGL,MAAM,CAACK,IAAI;QAC5B;IACF;IACA,OAAOH;AACT","ignoreList":[0]}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { DEFAULT_SEGMENT_KEY } from '../../segment';
|
||||
export function parseLoaderTree(tree) {
|
||||
const [segment, parallelRoutes, modules, staticSiblings] = tree;
|
||||
const { layout, template } = modules;
|
||||
let { page } = modules;
|
||||
// a __DEFAULT__ segment means that this route didn't match any of the
|
||||
// segments in the route, so we should use the default page
|
||||
page = segment === DEFAULT_SEGMENT_KEY ? modules.defaultPage : page;
|
||||
const conventionPath = layout?.[1] || template?.[1] || page?.[1];
|
||||
return {
|
||||
page,
|
||||
segment,
|
||||
modules,
|
||||
/* it can be either layout / template / page */ conventionPath,
|
||||
parallelRoutes,
|
||||
staticSiblings
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=parse-loader-tree.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/parse-loader-tree.ts"],"sourcesContent":["import { DEFAULT_SEGMENT_KEY } from '../../segment'\nimport type { LoaderTree } from '../../../../server/lib/app-dir-module'\n\nexport function parseLoaderTree(tree: LoaderTree) {\n const [segment, parallelRoutes, modules, staticSiblings] = tree\n const { layout, template } = modules\n let { page } = modules\n // a __DEFAULT__ segment means that this route didn't match any of the\n // segments in the route, so we should use the default page\n page = segment === DEFAULT_SEGMENT_KEY ? modules.defaultPage : page\n\n const conventionPath = layout?.[1] || template?.[1] || page?.[1]\n\n return {\n page,\n segment,\n modules,\n /* it can be either layout / template / page */\n conventionPath,\n parallelRoutes,\n staticSiblings,\n }\n}\n"],"names":["DEFAULT_SEGMENT_KEY","parseLoaderTree","tree","segment","parallelRoutes","modules","staticSiblings","layout","template","page","defaultPage","conventionPath"],"mappings":"AAAA,SAASA,mBAAmB,QAAQ,gBAAe;AAGnD,OAAO,SAASC,gBAAgBC,IAAgB;IAC9C,MAAM,CAACC,SAASC,gBAAgBC,SAASC,eAAe,GAAGJ;IAC3D,MAAM,EAAEK,MAAM,EAAEC,QAAQ,EAAE,GAAGH;IAC7B,IAAI,EAAEI,IAAI,EAAE,GAAGJ;IACf,sEAAsE;IACtE,2DAA2D;IAC3DI,OAAON,YAAYH,sBAAsBK,QAAQK,WAAW,GAAGD;IAE/D,MAAME,iBAAiBJ,QAAQ,CAAC,EAAE,IAAIC,UAAU,CAAC,EAAE,IAAIC,MAAM,CAAC,EAAE;IAEhE,OAAO;QACLA;QACAN;QACAE;QACA,6CAA6C,GAC7CM;QACAP;QACAE;IACF;AACF","ignoreList":[0]}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Given a path this function will find the pathname, query and hash and return
|
||||
* them. This is useful to parse full paths on the client side.
|
||||
* @param path A path to parse e.g. /foo/bar?id=1#hash
|
||||
*/ export function parsePath(path) {
|
||||
const hashIndex = path.indexOf('#');
|
||||
const queryIndex = path.indexOf('?');
|
||||
const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex);
|
||||
if (hasQuery || hashIndex > -1) {
|
||||
return {
|
||||
pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),
|
||||
query: hasQuery ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : undefined) : '',
|
||||
hash: hashIndex > -1 ? path.slice(hashIndex) : ''
|
||||
};
|
||||
}
|
||||
return {
|
||||
pathname: path,
|
||||
query: '',
|
||||
hash: ''
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=parse-path.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/parse-path.ts"],"sourcesContent":["/**\n * Given a path this function will find the pathname, query and hash and return\n * them. This is useful to parse full paths on the client side.\n * @param path A path to parse e.g. /foo/bar?id=1#hash\n */\nexport function parsePath(path: string) {\n const hashIndex = path.indexOf('#')\n const queryIndex = path.indexOf('?')\n const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex)\n\n if (hasQuery || hashIndex > -1) {\n return {\n pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),\n query: hasQuery\n ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : undefined)\n : '',\n hash: hashIndex > -1 ? path.slice(hashIndex) : '',\n }\n }\n\n return { pathname: path, query: '', hash: '' }\n}\n"],"names":["parsePath","path","hashIndex","indexOf","queryIndex","hasQuery","pathname","substring","query","undefined","hash","slice"],"mappings":"AAAA;;;;CAIC,GACD,OAAO,SAASA,UAAUC,IAAY;IACpC,MAAMC,YAAYD,KAAKE,OAAO,CAAC;IAC/B,MAAMC,aAAaH,KAAKE,OAAO,CAAC;IAChC,MAAME,WAAWD,aAAa,CAAC,KAAMF,CAAAA,YAAY,KAAKE,aAAaF,SAAQ;IAE3E,IAAIG,YAAYH,YAAY,CAAC,GAAG;QAC9B,OAAO;YACLI,UAAUL,KAAKM,SAAS,CAAC,GAAGF,WAAWD,aAAaF;YACpDM,OAAOH,WACHJ,KAAKM,SAAS,CAACH,YAAYF,YAAY,CAAC,IAAIA,YAAYO,aACxD;YACJC,MAAMR,YAAY,CAAC,IAAID,KAAKU,KAAK,CAACT,aAAa;QACjD;IACF;IAEA,OAAO;QAAEI,UAAUL;QAAMO,OAAO;QAAIE,MAAM;IAAG;AAC/C","ignoreList":[0]}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { getLocationOrigin } from '../../utils';
|
||||
import { searchParamsToUrlQuery } from './querystring';
|
||||
export function parseRelativeUrl(url, base, parseQuery = true) {
|
||||
const globalBase = new URL(typeof window === 'undefined' ? 'http://n' : getLocationOrigin());
|
||||
const resolvedBase = base ? new URL(base, globalBase) : url.startsWith('.') ? new URL(typeof window === 'undefined' ? 'http://n' : window.location.href) : globalBase;
|
||||
const { pathname, searchParams, search, hash, href, origin } = url.startsWith('/') ? // See https://nodejs.org/api/http.html#messageurl
|
||||
// Not using `origin` to support other protocols
|
||||
new URL(`${resolvedBase.protocol}//${resolvedBase.host}${url}`) : new URL(url, resolvedBase);
|
||||
if (origin !== globalBase.origin) {
|
||||
throw Object.defineProperty(new Error(`invariant: invalid relative URL, router received ${url}`), "__NEXT_ERROR_CODE", {
|
||||
value: "E159",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
return {
|
||||
auth: null,
|
||||
host: null,
|
||||
hostname: null,
|
||||
pathname,
|
||||
port: null,
|
||||
protocol: null,
|
||||
query: parseQuery ? searchParamsToUrlQuery(searchParams) : undefined,
|
||||
search,
|
||||
hash,
|
||||
href: href.slice(origin.length),
|
||||
// We don't know for relative URLs at this point since we set a custom, internal
|
||||
// base that isn't surfaced to users.
|
||||
slashes: null
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=parse-relative-url.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/parse-relative-url.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\nimport { getLocationOrigin } from '../../utils'\nimport { searchParamsToUrlQuery } from './querystring'\n\nexport interface ParsedRelativeUrl {\n auth: string | null\n hash: string\n host: string | null\n hostname: string | null\n href: string\n pathname: string\n port: string | null\n protocol: string | null\n query: ParsedUrlQuery\n search: string\n slashes: null\n}\n\n/**\n * Parses path-relative urls (e.g. `/hello/world?foo=bar`). If url isn't path-relative\n * (e.g. `./hello`) then at least base must be.\n * Absolute urls are rejected with one exception, in the browser, absolute urls that are on\n * the current origin will be parsed as relative\n */\nexport function parseRelativeUrl(\n url: string,\n base?: string,\n parseQuery?: true\n): ParsedRelativeUrl\nexport function parseRelativeUrl(\n url: string,\n base: string | undefined,\n parseQuery: false\n): Omit<ParsedRelativeUrl, 'query'>\nexport function parseRelativeUrl(\n url: string,\n base?: string,\n parseQuery = true\n): ParsedRelativeUrl | Omit<ParsedRelativeUrl, 'query'> {\n const globalBase = new URL(\n typeof window === 'undefined' ? 'http://n' : getLocationOrigin()\n )\n\n const resolvedBase = base\n ? new URL(base, globalBase)\n : url.startsWith('.')\n ? new URL(\n typeof window === 'undefined' ? 'http://n' : window.location.href\n )\n : globalBase\n\n const { pathname, searchParams, search, hash, href, origin } = url.startsWith(\n '/'\n )\n ? // 'http://localhost:3000///' would be received as '///' in Node.js' IncomingMessage\n // See https://nodejs.org/api/http.html#messageurl\n // Not using `origin` to support other protocols\n new URL(`${resolvedBase.protocol}//${resolvedBase.host}${url}`)\n : new URL(url, resolvedBase)\n\n if (origin !== globalBase.origin) {\n throw new Error(`invariant: invalid relative URL, router received ${url}`)\n }\n\n return {\n auth: null,\n host: null,\n hostname: null,\n pathname,\n port: null,\n protocol: null,\n query: parseQuery ? searchParamsToUrlQuery(searchParams) : undefined,\n search,\n hash,\n href: href.slice(origin.length),\n // We don't know for relative URLs at this point since we set a custom, internal\n // base that isn't surfaced to users.\n slashes: null,\n }\n}\n"],"names":["getLocationOrigin","searchParamsToUrlQuery","parseRelativeUrl","url","base","parseQuery","globalBase","URL","window","resolvedBase","startsWith","location","href","pathname","searchParams","search","hash","origin","protocol","host","Error","auth","hostname","port","query","undefined","slice","length","slashes"],"mappings":"AACA,SAASA,iBAAiB,QAAQ,cAAa;AAC/C,SAASC,sBAAsB,QAAQ,gBAAe;AAgCtD,OAAO,SAASC,iBACdC,GAAW,EACXC,IAAa,EACbC,aAAa,IAAI;IAEjB,MAAMC,aAAa,IAAIC,IACrB,OAAOC,WAAW,cAAc,aAAaR;IAG/C,MAAMS,eAAeL,OACjB,IAAIG,IAAIH,MAAME,cACdH,IAAIO,UAAU,CAAC,OACb,IAAIH,IACF,OAAOC,WAAW,cAAc,aAAaA,OAAOG,QAAQ,CAACC,IAAI,IAEnEN;IAEN,MAAM,EAAEO,QAAQ,EAAEC,YAAY,EAAEC,MAAM,EAAEC,IAAI,EAAEJ,IAAI,EAAEK,MAAM,EAAE,GAAGd,IAAIO,UAAU,CAC3E,OAGE,kDAAkD;IAClD,gDAAgD;IAChD,IAAIH,IAAI,GAAGE,aAAaS,QAAQ,CAAC,EAAE,EAAET,aAAaU,IAAI,GAAGhB,KAAK,IAC9D,IAAII,IAAIJ,KAAKM;IAEjB,IAAIQ,WAAWX,WAAWW,MAAM,EAAE;QAChC,MAAM,qBAAoE,CAApE,IAAIG,MAAM,CAAC,iDAAiD,EAAEjB,KAAK,GAAnE,qBAAA;mBAAA;wBAAA;0BAAA;QAAmE;IAC3E;IAEA,OAAO;QACLkB,MAAM;QACNF,MAAM;QACNG,UAAU;QACVT;QACAU,MAAM;QACNL,UAAU;QACVM,OAAOnB,aAAaJ,uBAAuBa,gBAAgBW;QAC3DV;QACAC;QACAJ,MAAMA,KAAKc,KAAK,CAACT,OAAOU,MAAM;QAC9B,gFAAgF;QAChF,qCAAqC;QACrCC,SAAS;IACX;AACF","ignoreList":[0]}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { searchParamsToUrlQuery } from './querystring';
|
||||
import { parseRelativeUrl } from './parse-relative-url';
|
||||
export function parseUrl(url) {
|
||||
if (url.startsWith('/')) {
|
||||
return parseRelativeUrl(url);
|
||||
}
|
||||
const parsedURL = new URL(url);
|
||||
const username = parsedURL.username;
|
||||
const password = parsedURL.password;
|
||||
const auth = username ? password ? `${username}:${password}` : username : null;
|
||||
const pathname = parsedURL.pathname;
|
||||
const search = parsedURL.search;
|
||||
return {
|
||||
auth,
|
||||
hash: parsedURL.hash,
|
||||
hostname: parsedURL.hostname,
|
||||
href: parsedURL.href,
|
||||
pathname,
|
||||
port: parsedURL.port,
|
||||
protocol: parsedURL.protocol,
|
||||
query: searchParamsToUrlQuery(parsedURL.searchParams),
|
||||
search,
|
||||
origin: parsedURL.origin,
|
||||
slashes: parsedURL.href.slice(parsedURL.protocol.length, parsedURL.protocol.length + 2) === '//'
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=parse-url.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/parse-url.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\n\nimport { searchParamsToUrlQuery } from './querystring'\nimport { parseRelativeUrl } from './parse-relative-url'\n\nexport interface ParsedUrl {\n auth: string | null\n hash: string\n hostname: string | null\n href: string\n origin?: string | null\n pathname: string\n port: string | null\n protocol: string | null\n query: ParsedUrlQuery\n search: string\n slashes: boolean | null\n}\n\nexport function parseUrl(url: string): ParsedUrl {\n if (url.startsWith('/')) {\n return parseRelativeUrl(url)\n }\n\n const parsedURL = new URL(url)\n const username = parsedURL.username\n const password = parsedURL.password\n const auth = username\n ? password\n ? `${username}:${password}`\n : username\n : null\n const pathname = parsedURL.pathname\n const search = parsedURL.search\n return {\n auth,\n hash: parsedURL.hash,\n hostname: parsedURL.hostname,\n href: parsedURL.href,\n pathname,\n port: parsedURL.port,\n protocol: parsedURL.protocol,\n query: searchParamsToUrlQuery(parsedURL.searchParams),\n search,\n origin: parsedURL.origin,\n slashes:\n parsedURL.href.slice(\n parsedURL.protocol.length,\n parsedURL.protocol.length + 2\n ) === '//',\n }\n}\n"],"names":["searchParamsToUrlQuery","parseRelativeUrl","parseUrl","url","startsWith","parsedURL","URL","username","password","auth","pathname","search","hash","hostname","href","port","protocol","query","searchParams","origin","slashes","slice","length"],"mappings":"AAEA,SAASA,sBAAsB,QAAQ,gBAAe;AACtD,SAASC,gBAAgB,QAAQ,uBAAsB;AAgBvD,OAAO,SAASC,SAASC,GAAW;IAClC,IAAIA,IAAIC,UAAU,CAAC,MAAM;QACvB,OAAOH,iBAAiBE;IAC1B;IAEA,MAAME,YAAY,IAAIC,IAAIH;IAC1B,MAAMI,WAAWF,UAAUE,QAAQ;IACnC,MAAMC,WAAWH,UAAUG,QAAQ;IACnC,MAAMC,OAAOF,WACTC,WACE,GAAGD,SAAS,CAAC,EAAEC,UAAU,GACzBD,WACF;IACJ,MAAMG,WAAWL,UAAUK,QAAQ;IACnC,MAAMC,SAASN,UAAUM,MAAM;IAC/B,OAAO;QACLF;QACAG,MAAMP,UAAUO,IAAI;QACpBC,UAAUR,UAAUQ,QAAQ;QAC5BC,MAAMT,UAAUS,IAAI;QACpBJ;QACAK,MAAMV,UAAUU,IAAI;QACpBC,UAAUX,UAAUW,QAAQ;QAC5BC,OAAOjB,uBAAuBK,UAAUa,YAAY;QACpDP;QACAQ,QAAQd,UAAUc,MAAM;QACxBC,SACEf,UAAUS,IAAI,CAACO,KAAK,CAClBhB,UAAUW,QAAQ,CAACM,MAAM,EACzBjB,UAAUW,QAAQ,CAACM,MAAM,GAAG,OACxB;IACV;AACF","ignoreList":[0]}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { parsePath } from './parse-path';
|
||||
/**
|
||||
* Checks if a given path starts with a given prefix. It ensures it matches
|
||||
* exactly without containing extra chars. e.g. prefix /docs should replace
|
||||
* for /docs, /docs/, /docs/a but not /docsss
|
||||
* @param path The path to check.
|
||||
* @param prefix The prefix to check against.
|
||||
*/ export function pathHasPrefix(path, prefix) {
|
||||
if (typeof path !== 'string') {
|
||||
return false;
|
||||
}
|
||||
const { pathname } = parsePath(path);
|
||||
return pathname === prefix || pathname.startsWith(prefix + '/');
|
||||
}
|
||||
|
||||
//# sourceMappingURL=path-has-prefix.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/path-has-prefix.ts"],"sourcesContent":["import { parsePath } from './parse-path'\n\n/**\n * Checks if a given path starts with a given prefix. It ensures it matches\n * exactly without containing extra chars. e.g. prefix /docs should replace\n * for /docs, /docs/, /docs/a but not /docsss\n * @param path The path to check.\n * @param prefix The prefix to check against.\n */\nexport function pathHasPrefix(path: string, prefix: string) {\n if (typeof path !== 'string') {\n return false\n }\n\n const { pathname } = parsePath(path)\n return pathname === prefix || pathname.startsWith(prefix + '/')\n}\n"],"names":["parsePath","pathHasPrefix","path","prefix","pathname","startsWith"],"mappings":"AAAA,SAASA,SAAS,QAAQ,eAAc;AAExC;;;;;;CAMC,GACD,OAAO,SAASC,cAAcC,IAAY,EAAEC,MAAc;IACxD,IAAI,OAAOD,SAAS,UAAU;QAC5B,OAAO;IACT;IAEA,MAAM,EAAEE,QAAQ,EAAE,GAAGJ,UAAUE;IAC/B,OAAOE,aAAaD,UAAUC,SAASC,UAAU,CAACF,SAAS;AAC7D","ignoreList":[0]}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { regexpToFunction } from 'next/dist/compiled/path-to-regexp';
|
||||
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp';
|
||||
/**
|
||||
* Generates a path matcher function for a given path and options based on
|
||||
* path-to-regexp. By default the match will be case insensitive, non strict
|
||||
* and delimited by `/`.
|
||||
*/ export function getPathMatch(path, options) {
|
||||
const keys = [];
|
||||
const regexp = pathToRegexp(path, keys, {
|
||||
delimiter: '/',
|
||||
sensitive: typeof options?.sensitive === 'boolean' ? options.sensitive : false,
|
||||
strict: options?.strict
|
||||
});
|
||||
const matcher = regexpToFunction(options?.regexModifier ? new RegExp(options.regexModifier(regexp.source), regexp.flags) : regexp, keys);
|
||||
/**
|
||||
* A matcher function that will check if a given pathname matches the path
|
||||
* given in the builder function. When the path does not match it will return
|
||||
* `false` but if it does it will return an object with the matched params
|
||||
* merged with the params provided in the second argument.
|
||||
*/ return (pathname, params)=>{
|
||||
// If no pathname is provided it's not a match.
|
||||
if (typeof pathname !== 'string') return false;
|
||||
const match = matcher(pathname);
|
||||
// If the path did not match `false` will be returned.
|
||||
if (!match) return false;
|
||||
/**
|
||||
* If unnamed params are not allowed they must be removed from
|
||||
* the matched parameters. path-to-regexp uses "string" for named and
|
||||
* "number" for unnamed parameters.
|
||||
*/ if (options?.removeUnnamedParams) {
|
||||
for (const key of keys){
|
||||
if (typeof key.name === 'number') {
|
||||
delete match.params[key.name];
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
...params,
|
||||
...match.params
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=path-match.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/path-match.ts"],"sourcesContent":["import type { Key } from 'next/dist/compiled/path-to-regexp'\nimport { regexpToFunction } from 'next/dist/compiled/path-to-regexp'\nimport { pathToRegexp } from 'next/dist/compiled/path-to-regexp'\n\ninterface Options {\n /**\n * A transformer function that will be applied to the regexp generated\n * from the provided path and path-to-regexp.\n */\n regexModifier?: (regex: string) => string\n /**\n * When true the function will remove all unnamed parameters\n * from the matched parameters.\n */\n removeUnnamedParams?: boolean\n /**\n * When true the regexp won't allow an optional trailing delimiter\n * to match.\n */\n strict?: boolean\n\n /**\n * When true the matcher will be case-sensitive, defaults to false\n */\n sensitive?: boolean\n}\n\nexport type PatchMatcher = (\n pathname: string,\n params?: Record<string, any>\n) => Record<string, any> | false\n\n/**\n * Generates a path matcher function for a given path and options based on\n * path-to-regexp. By default the match will be case insensitive, non strict\n * and delimited by `/`.\n */\nexport function getPathMatch(path: string, options?: Options): PatchMatcher {\n const keys: Key[] = []\n const regexp = pathToRegexp(path, keys, {\n delimiter: '/',\n sensitive:\n typeof options?.sensitive === 'boolean' ? options.sensitive : false,\n strict: options?.strict,\n })\n\n const matcher = regexpToFunction<Record<string, any>>(\n options?.regexModifier\n ? new RegExp(options.regexModifier(regexp.source), regexp.flags)\n : regexp,\n keys\n )\n\n /**\n * A matcher function that will check if a given pathname matches the path\n * given in the builder function. When the path does not match it will return\n * `false` but if it does it will return an object with the matched params\n * merged with the params provided in the second argument.\n */\n return (pathname, params) => {\n // If no pathname is provided it's not a match.\n if (typeof pathname !== 'string') return false\n\n const match = matcher(pathname)\n\n // If the path did not match `false` will be returned.\n if (!match) return false\n\n /**\n * If unnamed params are not allowed they must be removed from\n * the matched parameters. path-to-regexp uses \"string\" for named and\n * \"number\" for unnamed parameters.\n */\n if (options?.removeUnnamedParams) {\n for (const key of keys) {\n if (typeof key.name === 'number') {\n delete match.params[key.name]\n }\n }\n }\n\n return { ...params, ...match.params }\n }\n}\n"],"names":["regexpToFunction","pathToRegexp","getPathMatch","path","options","keys","regexp","delimiter","sensitive","strict","matcher","regexModifier","RegExp","source","flags","pathname","params","match","removeUnnamedParams","key","name"],"mappings":"AACA,SAASA,gBAAgB,QAAQ,oCAAmC;AACpE,SAASC,YAAY,QAAQ,oCAAmC;AA8BhE;;;;CAIC,GACD,OAAO,SAASC,aAAaC,IAAY,EAAEC,OAAiB;IAC1D,MAAMC,OAAc,EAAE;IACtB,MAAMC,SAASL,aAAaE,MAAME,MAAM;QACtCE,WAAW;QACXC,WACE,OAAOJ,SAASI,cAAc,YAAYJ,QAAQI,SAAS,GAAG;QAChEC,QAAQL,SAASK;IACnB;IAEA,MAAMC,UAAUV,iBACdI,SAASO,gBACL,IAAIC,OAAOR,QAAQO,aAAa,CAACL,OAAOO,MAAM,GAAGP,OAAOQ,KAAK,IAC7DR,QACJD;IAGF;;;;;GAKC,GACD,OAAO,CAACU,UAAUC;QAChB,+CAA+C;QAC/C,IAAI,OAAOD,aAAa,UAAU,OAAO;QAEzC,MAAME,QAAQP,QAAQK;QAEtB,sDAAsD;QACtD,IAAI,CAACE,OAAO,OAAO;QAEnB;;;;KAIC,GACD,IAAIb,SAASc,qBAAqB;YAChC,KAAK,MAAMC,OAAOd,KAAM;gBACtB,IAAI,OAAOc,IAAIC,IAAI,KAAK,UAAU;oBAChC,OAAOH,MAAMD,MAAM,CAACG,IAAIC,IAAI,CAAC;gBAC/B;YACF;QACF;QAEA,OAAO;YAAE,GAAGJ,MAAM;YAAE,GAAGC,MAAMD,MAAM;QAAC;IACtC;AACF","ignoreList":[0]}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
import { escapeStringRegexp } from '../../escape-regexp';
|
||||
import { parseUrl } from './parse-url';
|
||||
import { INTERCEPTION_ROUTE_MARKERS, isInterceptionRouteAppPath } from './interception-routes';
|
||||
import { getCookieParser } from '../../../../server/api-utils/get-cookie-parser';
|
||||
import { safePathToRegexp, safeCompile } from './route-match-utils';
|
||||
/**
|
||||
* Ensure only a-zA-Z are used for param names for proper interpolating
|
||||
* with path-to-regexp
|
||||
*/ function getSafeParamName(paramName) {
|
||||
let newParamName = '';
|
||||
for(let i = 0; i < paramName.length; i++){
|
||||
const charCode = paramName.charCodeAt(i);
|
||||
if (charCode > 64 && charCode < 91 || // A-Z
|
||||
charCode > 96 && charCode < 123 // a-z
|
||||
) {
|
||||
newParamName += paramName[i];
|
||||
}
|
||||
}
|
||||
return newParamName;
|
||||
}
|
||||
function escapeSegment(str, segmentName) {
|
||||
return str.replace(new RegExp(`:${escapeStringRegexp(segmentName)}`, 'g'), `__ESC_COLON_${segmentName}`);
|
||||
}
|
||||
function unescapeSegments(str) {
|
||||
return str.replace(/__ESC_COLON_/gi, ':');
|
||||
}
|
||||
export function matchHas(req, query, has = [], missing = []) {
|
||||
const params = {};
|
||||
const hasMatch = (hasItem)=>{
|
||||
let value;
|
||||
let key = hasItem.key;
|
||||
switch(hasItem.type){
|
||||
case 'header':
|
||||
{
|
||||
key = key.toLowerCase();
|
||||
value = req.headers[key];
|
||||
break;
|
||||
}
|
||||
case 'cookie':
|
||||
{
|
||||
if ('cookies' in req) {
|
||||
value = req.cookies[hasItem.key];
|
||||
} else {
|
||||
const cookies = getCookieParser(req.headers)();
|
||||
value = cookies[hasItem.key];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'query':
|
||||
{
|
||||
value = query[key];
|
||||
break;
|
||||
}
|
||||
case 'host':
|
||||
{
|
||||
const { host } = req?.headers || {};
|
||||
// remove port from host if present
|
||||
const hostname = host?.split(':', 1)[0].toLowerCase();
|
||||
value = hostname;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasItem.value && value) {
|
||||
params[getSafeParamName(key)] = value;
|
||||
return true;
|
||||
} else if (value) {
|
||||
const matcher = new RegExp(`^${hasItem.value}$`);
|
||||
const matches = Array.isArray(value) ? value.slice(-1)[0].match(matcher) : value.match(matcher);
|
||||
if (matches) {
|
||||
if (Array.isArray(matches)) {
|
||||
if (matches.groups) {
|
||||
Object.keys(matches.groups).forEach((groupKey)=>{
|
||||
params[groupKey] = matches.groups[groupKey];
|
||||
});
|
||||
} else if (hasItem.type === 'host' && matches[0]) {
|
||||
params.host = matches[0];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const allMatch = has.every((item)=>hasMatch(item)) && !missing.some((item)=>hasMatch(item));
|
||||
if (allMatch) {
|
||||
return params;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function compileNonPath(value, params) {
|
||||
if (!value.includes(':')) {
|
||||
return value;
|
||||
}
|
||||
for (const key of Object.keys(params)){
|
||||
if (value.includes(`:${key}`)) {
|
||||
value = value.replace(new RegExp(`:${key}\\*`, 'g'), `:${key}--ESCAPED_PARAM_ASTERISKS`).replace(new RegExp(`:${key}\\?`, 'g'), `:${key}--ESCAPED_PARAM_QUESTION`).replace(new RegExp(`:${key}\\+`, 'g'), `:${key}--ESCAPED_PARAM_PLUS`).replace(new RegExp(`:${key}(?!\\w)`, 'g'), `--ESCAPED_PARAM_COLON${key}`);
|
||||
}
|
||||
}
|
||||
value = value.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, '\\$1').replace(/--ESCAPED_PARAM_PLUS/g, '+').replace(/--ESCAPED_PARAM_COLON/g, ':').replace(/--ESCAPED_PARAM_QUESTION/g, '?').replace(/--ESCAPED_PARAM_ASTERISKS/g, '*');
|
||||
// the value needs to start with a forward-slash to be compiled
|
||||
// correctly
|
||||
return safeCompile(`/${value}`, {
|
||||
validate: false
|
||||
})(params).slice(1);
|
||||
}
|
||||
export function parseDestination(args) {
|
||||
let escaped = args.destination;
|
||||
for (const param of Object.keys({
|
||||
...args.params,
|
||||
...args.query
|
||||
})){
|
||||
if (!param) continue;
|
||||
escaped = escapeSegment(escaped, param);
|
||||
}
|
||||
const parsed = parseUrl(escaped);
|
||||
let pathname = parsed.pathname;
|
||||
if (pathname) {
|
||||
pathname = unescapeSegments(pathname);
|
||||
}
|
||||
let href = parsed.href;
|
||||
if (href) {
|
||||
href = unescapeSegments(href);
|
||||
}
|
||||
let hostname = parsed.hostname;
|
||||
if (hostname) {
|
||||
hostname = unescapeSegments(hostname);
|
||||
}
|
||||
let hash = parsed.hash;
|
||||
if (hash) {
|
||||
hash = unescapeSegments(hash);
|
||||
}
|
||||
let search = parsed.search;
|
||||
if (search) {
|
||||
search = unescapeSegments(search);
|
||||
}
|
||||
let origin = parsed.origin;
|
||||
if (origin) {
|
||||
origin = unescapeSegments(origin);
|
||||
}
|
||||
return {
|
||||
...parsed,
|
||||
pathname,
|
||||
hostname,
|
||||
href,
|
||||
hash,
|
||||
search,
|
||||
origin
|
||||
};
|
||||
}
|
||||
export function prepareDestination(args) {
|
||||
const parsedDestination = parseDestination(args);
|
||||
const { hostname: destHostname, query: destQuery, search: destSearch } = parsedDestination;
|
||||
// The following code assumes that the pathname here includes the hash if it's
|
||||
// present.
|
||||
let destPath = parsedDestination.pathname;
|
||||
if (parsedDestination.hash) {
|
||||
destPath = `${destPath}${parsedDestination.hash}`;
|
||||
}
|
||||
const destParams = [];
|
||||
const destPathParamKeys = [];
|
||||
safePathToRegexp(destPath, destPathParamKeys);
|
||||
for (const key of destPathParamKeys){
|
||||
destParams.push(key.name);
|
||||
}
|
||||
if (destHostname) {
|
||||
const destHostnameParamKeys = [];
|
||||
safePathToRegexp(destHostname, destHostnameParamKeys);
|
||||
for (const key of destHostnameParamKeys){
|
||||
destParams.push(key.name);
|
||||
}
|
||||
}
|
||||
const destPathCompiler = safeCompile(destPath, // we don't validate while compiling the destination since we should
|
||||
// have already validated before we got to this point and validating
|
||||
// breaks compiling destinations with named pattern params from the source
|
||||
// e.g. /something:hello(.*) -> /another/:hello is broken with validation
|
||||
// since compile validation is meant for reversing and not for inserting
|
||||
// params from a separate path-regex into another
|
||||
{
|
||||
validate: false
|
||||
});
|
||||
let destHostnameCompiler;
|
||||
if (destHostname) {
|
||||
destHostnameCompiler = safeCompile(destHostname, {
|
||||
validate: false
|
||||
});
|
||||
}
|
||||
// update any params in query values
|
||||
for (const [key, strOrArray] of Object.entries(destQuery)){
|
||||
// the value needs to start with a forward-slash to be compiled
|
||||
// correctly
|
||||
if (Array.isArray(strOrArray)) {
|
||||
destQuery[key] = strOrArray.map((value)=>compileNonPath(unescapeSegments(value), args.params));
|
||||
} else if (typeof strOrArray === 'string') {
|
||||
destQuery[key] = compileNonPath(unescapeSegments(strOrArray), args.params);
|
||||
}
|
||||
}
|
||||
// add path params to query if it's not a redirect and not
|
||||
// already defined in destination query or path
|
||||
let paramKeys = Object.keys(args.params).filter((name)=>name !== 'nextInternalLocale');
|
||||
if (args.appendParamsToQuery && !paramKeys.some((key)=>destParams.includes(key))) {
|
||||
for (const key of paramKeys){
|
||||
if (!(key in destQuery)) {
|
||||
destQuery[key] = args.params[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
let newUrl;
|
||||
// The compiler also that the interception route marker is an unnamed param, hence '0',
|
||||
// so we need to add it to the params object.
|
||||
if (isInterceptionRouteAppPath(destPath)) {
|
||||
for (const segment of destPath.split('/')){
|
||||
const marker = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
|
||||
if (marker) {
|
||||
if (marker === '(..)(..)') {
|
||||
args.params['0'] = '(..)';
|
||||
args.params['1'] = '(..)';
|
||||
} else {
|
||||
args.params['0'] = marker;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
newUrl = destPathCompiler(args.params);
|
||||
const [pathname, hash] = newUrl.split('#', 2);
|
||||
if (destHostnameCompiler) {
|
||||
parsedDestination.hostname = destHostnameCompiler(args.params);
|
||||
}
|
||||
parsedDestination.pathname = pathname;
|
||||
parsedDestination.hash = `${hash ? '#' : ''}${hash || ''}`;
|
||||
parsedDestination.search = destSearch ? compileNonPath(destSearch, args.params) : '';
|
||||
} catch (err) {
|
||||
if (err.message.match(/Expected .*? to not repeat, but got an array/)) {
|
||||
throw Object.defineProperty(new Error(`To use a multi-match in the destination you must add \`*\` at the end of the param name to signify it should repeat. https://nextjs.org/docs/messages/invalid-multi-match`), "__NEXT_ERROR_CODE", {
|
||||
value: "E329",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
// Query merge order lowest priority to highest
|
||||
// 1. initial URL query values
|
||||
// 2. path segment values
|
||||
// 3. destination specified query values
|
||||
parsedDestination.query = {
|
||||
...args.query,
|
||||
...parsedDestination.query
|
||||
};
|
||||
return {
|
||||
newUrl,
|
||||
destQuery,
|
||||
parsedDestination
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=prepare-destination.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+53
@@ -0,0 +1,53 @@
|
||||
export function searchParamsToUrlQuery(searchParams) {
|
||||
const query = {};
|
||||
for (const [key, value] of searchParams.entries()){
|
||||
const existing = query[key];
|
||||
if (typeof existing === 'undefined') {
|
||||
query[key] = value;
|
||||
} else if (Array.isArray(existing)) {
|
||||
existing.push(value);
|
||||
} else {
|
||||
query[key] = [
|
||||
existing,
|
||||
value
|
||||
];
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
function stringifyUrlQueryParam(param) {
|
||||
if (typeof param === 'string') {
|
||||
return param;
|
||||
}
|
||||
if (typeof param === 'number' && !isNaN(param) || typeof param === 'boolean') {
|
||||
return String(param);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
export function urlQueryToSearchParams(query) {
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(query)){
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value){
|
||||
searchParams.append(key, stringifyUrlQueryParam(item));
|
||||
}
|
||||
} else {
|
||||
searchParams.set(key, stringifyUrlQueryParam(value));
|
||||
}
|
||||
}
|
||||
return searchParams;
|
||||
}
|
||||
export function assign(target, ...searchParamsList) {
|
||||
for (const searchParams of searchParamsList){
|
||||
for (const key of searchParams.keys()){
|
||||
target.delete(key);
|
||||
}
|
||||
for (const [key, value] of searchParams.entries()){
|
||||
target.append(key, value);
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=querystring.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/querystring.ts"],"sourcesContent":["import type { ParsedUrlQuery } from 'querystring'\n\nexport function searchParamsToUrlQuery(\n searchParams: URLSearchParams\n): ParsedUrlQuery {\n const query: ParsedUrlQuery = {}\n for (const [key, value] of searchParams.entries()) {\n const existing = query[key]\n if (typeof existing === 'undefined') {\n query[key] = value\n } else if (Array.isArray(existing)) {\n existing.push(value)\n } else {\n query[key] = [existing, value]\n }\n }\n return query\n}\n\nfunction stringifyUrlQueryParam(param: unknown): string {\n if (typeof param === 'string') {\n return param\n }\n\n if (\n (typeof param === 'number' && !isNaN(param)) ||\n typeof param === 'boolean'\n ) {\n return String(param)\n } else {\n return ''\n }\n}\n\nexport function urlQueryToSearchParams(query: ParsedUrlQuery): URLSearchParams {\n const searchParams = new URLSearchParams()\n for (const [key, value] of Object.entries(query)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n searchParams.append(key, stringifyUrlQueryParam(item))\n }\n } else {\n searchParams.set(key, stringifyUrlQueryParam(value))\n }\n }\n return searchParams\n}\n\nexport function assign(\n target: URLSearchParams,\n ...searchParamsList: URLSearchParams[]\n): URLSearchParams {\n for (const searchParams of searchParamsList) {\n for (const key of searchParams.keys()) {\n target.delete(key)\n }\n\n for (const [key, value] of searchParams.entries()) {\n target.append(key, value)\n }\n }\n\n return target\n}\n"],"names":["searchParamsToUrlQuery","searchParams","query","key","value","entries","existing","Array","isArray","push","stringifyUrlQueryParam","param","isNaN","String","urlQueryToSearchParams","URLSearchParams","Object","item","append","set","assign","target","searchParamsList","keys","delete"],"mappings":"AAEA,OAAO,SAASA,uBACdC,YAA6B;IAE7B,MAAMC,QAAwB,CAAC;IAC/B,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIH,aAAaI,OAAO,GAAI;QACjD,MAAMC,WAAWJ,KAAK,CAACC,IAAI;QAC3B,IAAI,OAAOG,aAAa,aAAa;YACnCJ,KAAK,CAACC,IAAI,GAAGC;QACf,OAAO,IAAIG,MAAMC,OAAO,CAACF,WAAW;YAClCA,SAASG,IAAI,CAACL;QAChB,OAAO;YACLF,KAAK,CAACC,IAAI,GAAG;gBAACG;gBAAUF;aAAM;QAChC;IACF;IACA,OAAOF;AACT;AAEA,SAASQ,uBAAuBC,KAAc;IAC5C,IAAI,OAAOA,UAAU,UAAU;QAC7B,OAAOA;IACT;IAEA,IACE,AAAC,OAAOA,UAAU,YAAY,CAACC,MAAMD,UACrC,OAAOA,UAAU,WACjB;QACA,OAAOE,OAAOF;IAChB,OAAO;QACL,OAAO;IACT;AACF;AAEA,OAAO,SAASG,uBAAuBZ,KAAqB;IAC1D,MAAMD,eAAe,IAAIc;IACzB,KAAK,MAAM,CAACZ,KAAKC,MAAM,IAAIY,OAAOX,OAAO,CAACH,OAAQ;QAChD,IAAIK,MAAMC,OAAO,CAACJ,QAAQ;YACxB,KAAK,MAAMa,QAAQb,MAAO;gBACxBH,aAAaiB,MAAM,CAACf,KAAKO,uBAAuBO;YAClD;QACF,OAAO;YACLhB,aAAakB,GAAG,CAAChB,KAAKO,uBAAuBN;QAC/C;IACF;IACA,OAAOH;AACT;AAEA,OAAO,SAASmB,OACdC,MAAuB,EACvB,GAAGC,gBAAmC;IAEtC,KAAK,MAAMrB,gBAAgBqB,iBAAkB;QAC3C,KAAK,MAAMnB,OAAOF,aAAasB,IAAI,GAAI;YACrCF,OAAOG,MAAM,CAACrB;QAChB;QAEA,KAAK,MAAM,CAACA,KAAKC,MAAM,IAAIH,aAAaI,OAAO,GAAI;YACjDgB,OAAOH,MAAM,CAACf,KAAKC;QACrB;IACF;IAEA,OAAOiB;AACT","ignoreList":[0]}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* The result of parsing a URL relative to a base URL.
|
||||
*/ export function parseRelativeURL(url, base) {
|
||||
const baseURL = typeof base === 'string' ? new URL(base) : base;
|
||||
const relative = new URL(url, base);
|
||||
// The URL is relative if the origin is the same as the base URL.
|
||||
const isRelative = relative.origin === baseURL.origin;
|
||||
return {
|
||||
url: isRelative ? relative.toString().slice(baseURL.origin.length) : relative.toString(),
|
||||
isRelative
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Given a URL as a string and a base URL it will make the URL relative
|
||||
* if the parsed protocol and host is the same as the one in the base
|
||||
* URL. Otherwise it returns the same URL string.
|
||||
*/ export function getRelativeURL(url, base) {
|
||||
const relative = parseRelativeURL(url, base);
|
||||
return relative.url;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=relativize-url.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/relativize-url.ts"],"sourcesContent":["/**\n * The result of parsing a URL relative to a base URL.\n */\nexport type RelativeURL = {\n /**\n * The relative URL. Either a URL including the origin or a relative URL.\n */\n url: string\n\n /**\n * Whether the URL is relative to the base URL.\n */\n isRelative: boolean\n}\n\nexport function parseRelativeURL(\n url: string | URL,\n base: string | URL\n): RelativeURL {\n const baseURL = typeof base === 'string' ? new URL(base) : base\n const relative = new URL(url, base)\n\n // The URL is relative if the origin is the same as the base URL.\n const isRelative = relative.origin === baseURL.origin\n\n return {\n url: isRelative\n ? relative.toString().slice(baseURL.origin.length)\n : relative.toString(),\n isRelative,\n }\n}\n\n/**\n * Given a URL as a string and a base URL it will make the URL relative\n * if the parsed protocol and host is the same as the one in the base\n * URL. Otherwise it returns the same URL string.\n */\nexport function getRelativeURL(url: string | URL, base: string | URL): string {\n const relative = parseRelativeURL(url, base)\n return relative.url\n}\n"],"names":["parseRelativeURL","url","base","baseURL","URL","relative","isRelative","origin","toString","slice","length","getRelativeURL"],"mappings":"AAAA;;CAEC,GAaD,OAAO,SAASA,iBACdC,GAAiB,EACjBC,IAAkB;IAElB,MAAMC,UAAU,OAAOD,SAAS,WAAW,IAAIE,IAAIF,QAAQA;IAC3D,MAAMG,WAAW,IAAID,IAAIH,KAAKC;IAE9B,iEAAiE;IACjE,MAAMI,aAAaD,SAASE,MAAM,KAAKJ,QAAQI,MAAM;IAErD,OAAO;QACLN,KAAKK,aACDD,SAASG,QAAQ,GAAGC,KAAK,CAACN,QAAQI,MAAM,CAACG,MAAM,IAC/CL,SAASG,QAAQ;QACrBF;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASK,eAAeV,GAAiB,EAAEC,IAAkB;IAClE,MAAMG,WAAWL,iBAAiBC,KAAKC;IACvC,OAAOG,SAASJ,GAAG;AACrB","ignoreList":[0]}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { pathHasPrefix } from './path-has-prefix';
|
||||
/**
|
||||
* Given a path and a prefix it will remove the prefix when it exists in the
|
||||
* given path. It ensures it matches exactly without containing extra chars
|
||||
* and if the prefix is not there it will be noop.
|
||||
*
|
||||
* @param path The path to remove the prefix from.
|
||||
* @param prefix The prefix to be removed.
|
||||
*/ export function removePathPrefix(path, prefix) {
|
||||
// If the path doesn't start with the prefix we can return it as is. This
|
||||
// protects us from situations where the prefix is a substring of the path
|
||||
// prefix such as:
|
||||
//
|
||||
// For prefix: /blog
|
||||
//
|
||||
// /blog -> true
|
||||
// /blog/ -> true
|
||||
// /blog/1 -> true
|
||||
// /blogging -> false
|
||||
// /blogging/ -> false
|
||||
// /blogging/1 -> false
|
||||
if (!pathHasPrefix(path, prefix)) {
|
||||
return path;
|
||||
}
|
||||
// Remove the prefix from the path via slicing.
|
||||
const withoutPrefix = path.slice(prefix.length);
|
||||
// If the path without the prefix starts with a `/` we can return it as is.
|
||||
if (withoutPrefix.startsWith('/')) {
|
||||
return withoutPrefix;
|
||||
}
|
||||
// If the path without the prefix doesn't start with a `/` we need to add it
|
||||
// back to the path to make sure it's a valid path.
|
||||
return `/${withoutPrefix}`;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=remove-path-prefix.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/remove-path-prefix.ts"],"sourcesContent":["import { pathHasPrefix } from './path-has-prefix'\n\n/**\n * Given a path and a prefix it will remove the prefix when it exists in the\n * given path. It ensures it matches exactly without containing extra chars\n * and if the prefix is not there it will be noop.\n *\n * @param path The path to remove the prefix from.\n * @param prefix The prefix to be removed.\n */\nexport function removePathPrefix(path: string, prefix: string): string {\n // If the path doesn't start with the prefix we can return it as is. This\n // protects us from situations where the prefix is a substring of the path\n // prefix such as:\n //\n // For prefix: /blog\n //\n // /blog -> true\n // /blog/ -> true\n // /blog/1 -> true\n // /blogging -> false\n // /blogging/ -> false\n // /blogging/1 -> false\n if (!pathHasPrefix(path, prefix)) {\n return path\n }\n\n // Remove the prefix from the path via slicing.\n const withoutPrefix = path.slice(prefix.length)\n\n // If the path without the prefix starts with a `/` we can return it as is.\n if (withoutPrefix.startsWith('/')) {\n return withoutPrefix\n }\n\n // If the path without the prefix doesn't start with a `/` we need to add it\n // back to the path to make sure it's a valid path.\n return `/${withoutPrefix}`\n}\n"],"names":["pathHasPrefix","removePathPrefix","path","prefix","withoutPrefix","slice","length","startsWith"],"mappings":"AAAA,SAASA,aAAa,QAAQ,oBAAmB;AAEjD;;;;;;;CAOC,GACD,OAAO,SAASC,iBAAiBC,IAAY,EAAEC,MAAc;IAC3D,yEAAyE;IACzE,0EAA0E;IAC1E,kBAAkB;IAClB,EAAE;IACF,oBAAoB;IACpB,EAAE;IACF,kBAAkB;IAClB,mBAAmB;IACnB,oBAAoB;IACpB,uBAAuB;IACvB,wBAAwB;IACxB,yBAAyB;IACzB,IAAI,CAACH,cAAcE,MAAMC,SAAS;QAChC,OAAOD;IACT;IAEA,+CAA+C;IAC/C,MAAME,gBAAgBF,KAAKG,KAAK,CAACF,OAAOG,MAAM;IAE9C,2EAA2E;IAC3E,IAAIF,cAAcG,UAAU,CAAC,MAAM;QACjC,OAAOH;IACT;IAEA,4EAA4E;IAC5E,mDAAmD;IACnD,OAAO,CAAC,CAAC,EAAEA,eAAe;AAC5B","ignoreList":[0]}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Removes the trailing slash for a given route or page path. Preserves the
|
||||
* root page. Examples:
|
||||
* - `/foo/bar/` -> `/foo/bar`
|
||||
* - `/foo/bar` -> `/foo/bar`
|
||||
* - `/` -> `/`
|
||||
*/ export function removeTrailingSlash(route) {
|
||||
return route.replace(/\/$/, '') || '/';
|
||||
}
|
||||
|
||||
//# sourceMappingURL=remove-trailing-slash.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/remove-trailing-slash.ts"],"sourcesContent":["/**\n * Removes the trailing slash for a given route or page path. Preserves the\n * root page. Examples:\n * - `/foo/bar/` -> `/foo/bar`\n * - `/foo/bar` -> `/foo/bar`\n * - `/` -> `/`\n */\nexport function removeTrailingSlash(route: string) {\n return route.replace(/\\/$/, '') || '/'\n}\n"],"names":["removeTrailingSlash","route","replace"],"mappings":"AAAA;;;;;;CAMC,GACD,OAAO,SAASA,oBAAoBC,KAAa;IAC/C,OAAOA,MAAMC,OAAO,CAAC,OAAO,OAAO;AACrC","ignoreList":[0]}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import { InvariantError } from '../../invariant-error';
|
||||
import { interceptionPrefixFromParamType } from './interception-prefix-from-param-type';
|
||||
/**
|
||||
* Extracts the param value from a path segment, handling interception markers
|
||||
* based on the expected param type.
|
||||
*
|
||||
* @param pathSegment - The path segment to extract the value from
|
||||
* @param params - The current params object for resolving dynamic param references
|
||||
* @param paramType - The expected param type which may include interception marker info
|
||||
* @returns The extracted param value
|
||||
*/ function getParamValueFromSegment(pathSegment, params, paramType) {
|
||||
// If the segment is dynamic, resolve it from the params object
|
||||
if (pathSegment.type === 'dynamic') {
|
||||
return params[pathSegment.param.paramName];
|
||||
}
|
||||
// If the paramType indicates this is an intercepted param, strip the marker
|
||||
// that matches the interception marker in the param type
|
||||
const interceptionPrefix = interceptionPrefixFromParamType(paramType);
|
||||
if (interceptionPrefix === pathSegment.interceptionMarker) {
|
||||
return pathSegment.name.replace(pathSegment.interceptionMarker, '');
|
||||
}
|
||||
// For static segments, use the name
|
||||
return pathSegment.name;
|
||||
}
|
||||
/**
|
||||
* Resolves a route parameter value from the route segments at the given depth.
|
||||
* This shared logic is used by both extractPathnameRouteParamSegmentsFromLoaderTree
|
||||
* and resolveRouteParamsFromTree.
|
||||
*
|
||||
* @param paramName - The parameter name to resolve
|
||||
* @param paramType - The parameter type (dynamic, catchall, etc.)
|
||||
* @param depth - The current depth in the route tree
|
||||
* @param route - The normalized route containing segments
|
||||
* @param params - The current params object (used to resolve embedded param references)
|
||||
* @param options - Configuration options
|
||||
* @returns The resolved parameter value, or undefined if it cannot be resolved
|
||||
*/ export function resolveParamValue(paramName, paramType, depth, route, params) {
|
||||
switch(paramType){
|
||||
case 'catchall':
|
||||
case 'optional-catchall':
|
||||
case 'catchall-intercepted-(..)(..)':
|
||||
case 'catchall-intercepted-(.)':
|
||||
case 'catchall-intercepted-(..)':
|
||||
case 'catchall-intercepted-(...)':
|
||||
// For catchall routes, derive from pathname using depth to determine
|
||||
// which segments to use
|
||||
const processedSegments = [];
|
||||
// Process segments to handle any embedded dynamic params
|
||||
for(let index = depth; index < route.segments.length; index++){
|
||||
const pathSegment = route.segments[index];
|
||||
if (pathSegment.type === 'static') {
|
||||
let value = pathSegment.name;
|
||||
// For intercepted catch-all params, strip the marker from the first segment
|
||||
const interceptionPrefix = interceptionPrefixFromParamType(paramType);
|
||||
if (interceptionPrefix && index === depth && interceptionPrefix === pathSegment.interceptionMarker) {
|
||||
// Strip the interception marker from the value
|
||||
value = value.replace(pathSegment.interceptionMarker, '');
|
||||
}
|
||||
processedSegments.push(value);
|
||||
} else {
|
||||
// If the segment is a param placeholder, check if we have its value
|
||||
if (!params.hasOwnProperty(pathSegment.param.paramName)) {
|
||||
// If the segment is an optional catchall, we can break out of the
|
||||
// loop because it's optional!
|
||||
if (pathSegment.param.paramType === 'optional-catchall') {
|
||||
break;
|
||||
}
|
||||
// Unknown param placeholder in pathname - can't derive full value
|
||||
return undefined;
|
||||
}
|
||||
// If the segment matches a param, use the param value
|
||||
// We don't encode values here as that's handled during retrieval.
|
||||
const paramValue = params[pathSegment.param.paramName];
|
||||
if (Array.isArray(paramValue)) {
|
||||
processedSegments.push(...paramValue);
|
||||
} else {
|
||||
processedSegments.push(paramValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (processedSegments.length > 0) {
|
||||
return processedSegments;
|
||||
} else if (paramType === 'optional-catchall') {
|
||||
return undefined;
|
||||
} else {
|
||||
// We shouldn't be able to match a catchall segment without any path
|
||||
// segments if it's not an optional catchall
|
||||
throw Object.defineProperty(new InvariantError(`Unexpected empty path segments match for a route "${route.pathname}" with param "${paramName}" of type "${paramType}"`), "__NEXT_ERROR_CODE", {
|
||||
value: "E931",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
case 'dynamic':
|
||||
case 'dynamic-intercepted-(..)(..)':
|
||||
case 'dynamic-intercepted-(.)':
|
||||
case 'dynamic-intercepted-(..)':
|
||||
case 'dynamic-intercepted-(...)':
|
||||
// For regular dynamic parameters, take the segment at this depth
|
||||
if (depth < route.segments.length) {
|
||||
const pathSegment = route.segments[depth];
|
||||
// Check if the segment at this depth is a placeholder for an unknown param
|
||||
if (pathSegment.type === 'dynamic' && !params.hasOwnProperty(pathSegment.param.paramName)) {
|
||||
// The segment is a placeholder like [category] and we don't have the value
|
||||
return undefined;
|
||||
}
|
||||
// If the segment matches a param, use the param value from params object
|
||||
// Otherwise it's a static segment, just use it directly
|
||||
// We don't encode values here as that's handled during retrieval
|
||||
return getParamValueFromSegment(pathSegment, params, paramType);
|
||||
}
|
||||
return undefined;
|
||||
default:
|
||||
paramType;
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=resolve-param-value.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+108
@@ -0,0 +1,108 @@
|
||||
import { getPathMatch } from './path-match';
|
||||
import { matchHas, prepareDestination } from './prepare-destination';
|
||||
import { removeTrailingSlash } from './remove-trailing-slash';
|
||||
import { normalizeLocalePath } from '../../i18n/normalize-locale-path';
|
||||
import { removeBasePath } from '../../../../client/remove-base-path';
|
||||
import { parseRelativeUrl } from './parse-relative-url';
|
||||
export default function resolveRewrites(asPath, pages, rewrites, query, resolveHref, locales) {
|
||||
let matchedPage = false;
|
||||
let externalDest = false;
|
||||
let parsedAs = parseRelativeUrl(asPath);
|
||||
let fsPathname = removeTrailingSlash(normalizeLocalePath(removeBasePath(parsedAs.pathname), locales).pathname);
|
||||
let resolvedHref;
|
||||
const handleRewrite = (rewrite)=>{
|
||||
const matcher = getPathMatch(rewrite.source + (process.env.__NEXT_TRAILING_SLASH ? '(/)?' : ''), {
|
||||
removeUnnamedParams: true,
|
||||
strict: true
|
||||
});
|
||||
let params = matcher(parsedAs.pathname);
|
||||
if ((rewrite.has || rewrite.missing) && params) {
|
||||
const hasParams = matchHas({
|
||||
headers: {
|
||||
host: document.location.hostname,
|
||||
'user-agent': navigator.userAgent
|
||||
},
|
||||
cookies: document.cookie.split('; ').reduce((acc, item)=>{
|
||||
const [key, ...value] = item.split('=');
|
||||
acc[key] = value.join('=');
|
||||
return acc;
|
||||
}, {})
|
||||
}, parsedAs.query, rewrite.has, rewrite.missing);
|
||||
if (hasParams) {
|
||||
Object.assign(params, hasParams);
|
||||
} else {
|
||||
params = false;
|
||||
}
|
||||
}
|
||||
if (params) {
|
||||
if (!rewrite.destination) {
|
||||
// this is a proxied rewrite which isn't handled on the client
|
||||
externalDest = true;
|
||||
return true;
|
||||
}
|
||||
const destRes = prepareDestination({
|
||||
appendParamsToQuery: true,
|
||||
destination: rewrite.destination,
|
||||
params: params,
|
||||
query: query
|
||||
});
|
||||
parsedAs = destRes.parsedDestination;
|
||||
asPath = destRes.newUrl;
|
||||
Object.assign(query, destRes.parsedDestination.query);
|
||||
fsPathname = removeTrailingSlash(normalizeLocalePath(removeBasePath(asPath), locales).pathname);
|
||||
if (pages.includes(fsPathname)) {
|
||||
// check if we now match a page as this means we are done
|
||||
// resolving the rewrites
|
||||
matchedPage = true;
|
||||
resolvedHref = fsPathname;
|
||||
return true;
|
||||
}
|
||||
// check if we match a dynamic-route, if so we break the rewrites chain
|
||||
resolvedHref = resolveHref(fsPathname);
|
||||
if (resolvedHref !== asPath && pages.includes(resolvedHref)) {
|
||||
matchedPage = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
let finished = false;
|
||||
for(let i = 0; i < rewrites.beforeFiles.length; i++){
|
||||
// we don't end after match in beforeFiles to allow
|
||||
// continuing through all beforeFiles rewrites
|
||||
handleRewrite(rewrites.beforeFiles[i]);
|
||||
}
|
||||
matchedPage = pages.includes(fsPathname);
|
||||
if (!matchedPage) {
|
||||
if (!finished) {
|
||||
for(let i = 0; i < rewrites.afterFiles.length; i++){
|
||||
if (handleRewrite(rewrites.afterFiles[i])) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// check dynamic route before processing fallback rewrites
|
||||
if (!finished) {
|
||||
resolvedHref = resolveHref(fsPathname);
|
||||
matchedPage = pages.includes(resolvedHref);
|
||||
finished = matchedPage;
|
||||
}
|
||||
if (!finished) {
|
||||
for(let i = 0; i < rewrites.fallback.length; i++){
|
||||
if (handleRewrite(rewrites.fallback[i])) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
asPath,
|
||||
parsedAs,
|
||||
matchedPage,
|
||||
resolvedHref,
|
||||
externalDest
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=resolve-rewrites.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Client-safe utilities for route matching that don't import server-side
|
||||
* utilities to avoid bundling issues with Turbopack
|
||||
*/ import { pathToRegexp, compile, regexpToFunction } from 'next/dist/compiled/path-to-regexp';
|
||||
import { hasAdjacentParameterIssues, normalizeAdjacentParameters, stripParameterSeparators, stripNormalizedSeparators } from '../../../../lib/route-pattern-normalizer';
|
||||
/**
|
||||
* Client-safe wrapper around pathToRegexp that handles path-to-regexp 6.3.0+ validation errors.
|
||||
* This includes both "Can not repeat without prefix/suffix" and "Must have text between parameters" errors.
|
||||
*/ export function safePathToRegexp(route, keys, options) {
|
||||
if (typeof route !== 'string') {
|
||||
return pathToRegexp(route, keys, options);
|
||||
}
|
||||
// Check if normalization is needed and cache the result
|
||||
const needsNormalization = hasAdjacentParameterIssues(route);
|
||||
const routeToUse = needsNormalization ? normalizeAdjacentParameters(route) : route;
|
||||
try {
|
||||
return pathToRegexp(routeToUse, keys, options);
|
||||
} catch (error) {
|
||||
// Only try normalization if we haven't already normalized
|
||||
if (!needsNormalization) {
|
||||
try {
|
||||
const normalizedRoute = normalizeAdjacentParameters(route);
|
||||
return pathToRegexp(normalizedRoute, keys, options);
|
||||
} catch (retryError) {
|
||||
// If that doesn't work, fall back to original error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Client-safe wrapper around compile that handles path-to-regexp 6.3.0+ validation errors.
|
||||
* No server-side error reporting to avoid bundling issues.
|
||||
* When normalization is applied, the returned compiler function automatically strips
|
||||
* the internal separator from the output URL.
|
||||
*/ export function safeCompile(route, options) {
|
||||
// Check if normalization is needed and cache the result
|
||||
const needsNormalization = hasAdjacentParameterIssues(route);
|
||||
const routeToUse = needsNormalization ? normalizeAdjacentParameters(route) : route;
|
||||
try {
|
||||
const compiler = compile(routeToUse, options);
|
||||
// If we normalized the route, wrap the compiler to strip separators from output
|
||||
// The normalization inserts _NEXTSEP_ as a literal string in the pattern to satisfy
|
||||
// path-to-regexp validation, but we don't want it in the final compiled URL
|
||||
if (needsNormalization) {
|
||||
return (params)=>{
|
||||
return stripNormalizedSeparators(compiler(params));
|
||||
};
|
||||
}
|
||||
return compiler;
|
||||
} catch (error) {
|
||||
// Only try normalization if we haven't already normalized
|
||||
if (!needsNormalization) {
|
||||
try {
|
||||
const normalizedRoute = normalizeAdjacentParameters(route);
|
||||
const compiler = compile(normalizedRoute, options);
|
||||
// Wrap the compiler to strip separators from output
|
||||
return (params)=>{
|
||||
return stripNormalizedSeparators(compiler(params));
|
||||
};
|
||||
} catch (retryError) {
|
||||
// If that doesn't work, fall back to original error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Client-safe wrapper around regexpToFunction that automatically cleans parameters.
|
||||
*/ export function safeRegexpToFunction(regexp, keys) {
|
||||
const originalMatcher = regexpToFunction(regexp, keys || []);
|
||||
return (pathname)=>{
|
||||
const result = originalMatcher(pathname);
|
||||
if (!result) return false;
|
||||
// Clean parameters before returning
|
||||
return {
|
||||
...result,
|
||||
params: stripParameterSeparators(result.params)
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Safe wrapper for route matcher functions that automatically cleans parameters.
|
||||
* This is client-safe and doesn't import path-to-regexp.
|
||||
*/ export function safeRouteMatcher(matcherFn) {
|
||||
return (pathname)=>{
|
||||
const result = matcherFn(pathname);
|
||||
if (!result) return false;
|
||||
// Clean parameters before returning
|
||||
return stripParameterSeparators(result);
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=route-match-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+35
@@ -0,0 +1,35 @@
|
||||
import { DecodeError } from '../../utils';
|
||||
import { safeRouteMatcher } from './route-match-utils';
|
||||
export function getRouteMatcher({ re, groups }) {
|
||||
const rawMatcher = (pathname)=>{
|
||||
const routeMatch = re.exec(pathname);
|
||||
if (!routeMatch) return false;
|
||||
const decode = (param)=>{
|
||||
try {
|
||||
return decodeURIComponent(param);
|
||||
} catch {
|
||||
throw Object.defineProperty(new DecodeError('failed to decode param'), "__NEXT_ERROR_CODE", {
|
||||
value: "E528",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
};
|
||||
const params = {};
|
||||
for (const [key, group] of Object.entries(groups)){
|
||||
const match = routeMatch[group.pos];
|
||||
if (match !== undefined) {
|
||||
if (group.repeat) {
|
||||
params[key] = match.split('/').map((entry)=>decode(entry));
|
||||
} else {
|
||||
params[key] = decode(match);
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
};
|
||||
// Wrap with safe matcher to handle parameter cleaning
|
||||
return safeRouteMatcher(rawMatcher);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=route-matcher.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../../../src/shared/lib/router/utils/route-matcher.ts"],"sourcesContent":["import type { Group } from './route-regex'\nimport { DecodeError } from '../../utils'\nimport type { Params } from '../../../../server/request/params'\nimport { safeRouteMatcher } from './route-match-utils'\n\nexport interface RouteMatchFn {\n (pathname: string): false | Params\n}\n\ntype RouteMatcherOptions = {\n // We only use the exec method of the RegExp object. This helps us avoid using\n // type assertions that the passed in properties are of the correct type.\n re: Pick<RegExp, 'exec'>\n groups: Record<string, Group>\n}\n\nexport function getRouteMatcher({\n re,\n groups,\n}: RouteMatcherOptions): RouteMatchFn {\n const rawMatcher = (pathname: string) => {\n const routeMatch = re.exec(pathname)\n if (!routeMatch) return false\n\n const decode = (param: string) => {\n try {\n return decodeURIComponent(param)\n } catch {\n throw new DecodeError('failed to decode param')\n }\n }\n\n const params: Params = {}\n for (const [key, group] of Object.entries(groups)) {\n const match = routeMatch[group.pos]\n if (match !== undefined) {\n if (group.repeat) {\n params[key] = match.split('/').map((entry) => decode(entry))\n } else {\n params[key] = decode(match)\n }\n }\n }\n\n return params\n }\n\n // Wrap with safe matcher to handle parameter cleaning\n return safeRouteMatcher(rawMatcher)\n}\n"],"names":["DecodeError","safeRouteMatcher","getRouteMatcher","re","groups","rawMatcher","pathname","routeMatch","exec","decode","param","decodeURIComponent","params","key","group","Object","entries","match","pos","undefined","repeat","split","map","entry"],"mappings":"AACA,SAASA,WAAW,QAAQ,cAAa;AAEzC,SAASC,gBAAgB,QAAQ,sBAAqB;AAatD,OAAO,SAASC,gBAAgB,EAC9BC,EAAE,EACFC,MAAM,EACc;IACpB,MAAMC,aAAa,CAACC;QAClB,MAAMC,aAAaJ,GAAGK,IAAI,CAACF;QAC3B,IAAI,CAACC,YAAY,OAAO;QAExB,MAAME,SAAS,CAACC;YACd,IAAI;gBACF,OAAOC,mBAAmBD;YAC5B,EAAE,OAAM;gBACN,MAAM,qBAAyC,CAAzC,IAAIV,YAAY,2BAAhB,qBAAA;2BAAA;gCAAA;kCAAA;gBAAwC;YAChD;QACF;QAEA,MAAMY,SAAiB,CAAC;QACxB,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACZ,QAAS;YACjD,MAAMa,QAAQV,UAAU,CAACO,MAAMI,GAAG,CAAC;YACnC,IAAID,UAAUE,WAAW;gBACvB,IAAIL,MAAMM,MAAM,EAAE;oBAChBR,MAAM,CAACC,IAAI,GAAGI,MAAMI,KAAK,CAAC,KAAKC,GAAG,CAAC,CAACC,QAAUd,OAAOc;gBACvD,OAAO;oBACLX,MAAM,CAACC,IAAI,GAAGJ,OAAOQ;gBACvB;YACF;QACF;QAEA,OAAOL;IACT;IAEA,sDAAsD;IACtD,OAAOX,iBAAiBI;AAC1B","ignoreList":[0]}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
import { NEXT_INTERCEPTION_MARKER_PREFIX, NEXT_QUERY_PARAM_PREFIX } from '../../../../lib/constants';
|
||||
import { INTERCEPTION_ROUTE_MARKERS } from './interception-routes';
|
||||
import { escapeStringRegexp } from '../../escape-regexp';
|
||||
import { removeTrailingSlash } from './remove-trailing-slash';
|
||||
import { PARAMETER_PATTERN, parseMatchedParameter } from './get-dynamic-param';
|
||||
function getParametrizedRoute(route, includeSuffix, includePrefix) {
|
||||
const groups = {};
|
||||
let groupIndex = 1;
|
||||
const segments = [];
|
||||
for (const segment of removeTrailingSlash(route).slice(1).split('/')){
|
||||
const markerMatch = INTERCEPTION_ROUTE_MARKERS.find((m)=>segment.startsWith(m));
|
||||
const paramMatches = segment.match(PARAMETER_PATTERN) // Check for parameters
|
||||
;
|
||||
if (markerMatch && paramMatches && paramMatches[2]) {
|
||||
const { key, optional, repeat } = parseMatchedParameter(paramMatches[2]);
|
||||
groups[key] = {
|
||||
pos: groupIndex++,
|
||||
repeat,
|
||||
optional
|
||||
};
|
||||
segments.push(`/${escapeStringRegexp(markerMatch)}([^/]+?)`);
|
||||
} else if (paramMatches && paramMatches[2]) {
|
||||
const { key, repeat, optional } = parseMatchedParameter(paramMatches[2]);
|
||||
groups[key] = {
|
||||
pos: groupIndex++,
|
||||
repeat,
|
||||
optional
|
||||
};
|
||||
if (includePrefix && paramMatches[1]) {
|
||||
segments.push(`/${escapeStringRegexp(paramMatches[1])}`);
|
||||
}
|
||||
let s = repeat ? optional ? '(?:/(.+?))?' : '/(.+?)' : '/([^/]+?)';
|
||||
// Remove the leading slash if includePrefix already added it.
|
||||
if (includePrefix && paramMatches[1]) {
|
||||
s = s.substring(1);
|
||||
}
|
||||
segments.push(s);
|
||||
} else {
|
||||
segments.push(`/${escapeStringRegexp(segment)}`);
|
||||
}
|
||||
// If there's a suffix, add it to the segments if it's enabled.
|
||||
if (includeSuffix && paramMatches && paramMatches[3]) {
|
||||
segments.push(escapeStringRegexp(paramMatches[3]));
|
||||
}
|
||||
}
|
||||
return {
|
||||
parameterizedRoute: segments.join(''),
|
||||
groups
|
||||
};
|
||||
}
|
||||
/**
|
||||
* From a normalized route this function generates a regular expression and
|
||||
* a corresponding groups object intended to be used to store matching groups
|
||||
* from the regular expression.
|
||||
*/ export function getRouteRegex(normalizedRoute, { includeSuffix = false, includePrefix = false, excludeOptionalTrailingSlash = false } = {}) {
|
||||
const { parameterizedRoute, groups } = getParametrizedRoute(normalizedRoute, includeSuffix, includePrefix);
|
||||
let re = parameterizedRoute;
|
||||
if (!excludeOptionalTrailingSlash) {
|
||||
re += '(?:/)?';
|
||||
}
|
||||
return {
|
||||
re: new RegExp(`^${re}$`),
|
||||
groups: groups
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Builds a function to generate a minimal routeKey using only a-z and minimal
|
||||
* number of characters.
|
||||
*/ function buildGetSafeRouteKey() {
|
||||
let i = 0;
|
||||
return ()=>{
|
||||
let routeKey = '';
|
||||
let j = ++i;
|
||||
while(j > 0){
|
||||
routeKey += String.fromCharCode(97 + (j - 1) % 26);
|
||||
j = Math.floor((j - 1) / 26);
|
||||
}
|
||||
return routeKey;
|
||||
};
|
||||
}
|
||||
function getSafeKeyFromSegment({ interceptionMarker, getSafeRouteKey, segment, routeKeys, keyPrefix, backreferenceDuplicateKeys }) {
|
||||
const { key, optional, repeat } = parseMatchedParameter(segment);
|
||||
// replace any non-word characters since they can break
|
||||
// the named regex
|
||||
let cleanedKey = key.replace(/\W/g, '');
|
||||
if (keyPrefix) {
|
||||
cleanedKey = `${keyPrefix}${cleanedKey}`;
|
||||
}
|
||||
let invalidKey = false;
|
||||
// check if the key is still invalid and fallback to using a known
|
||||
// safe key
|
||||
if (cleanedKey.length === 0 || cleanedKey.length > 30) {
|
||||
invalidKey = true;
|
||||
}
|
||||
if (!isNaN(parseInt(cleanedKey.slice(0, 1)))) {
|
||||
invalidKey = true;
|
||||
}
|
||||
if (invalidKey) {
|
||||
cleanedKey = getSafeRouteKey();
|
||||
}
|
||||
const duplicateKey = cleanedKey in routeKeys;
|
||||
if (keyPrefix) {
|
||||
routeKeys[cleanedKey] = `${keyPrefix}${key}`;
|
||||
} else {
|
||||
routeKeys[cleanedKey] = key;
|
||||
}
|
||||
// if the segment has an interception marker, make sure that's part of the regex pattern
|
||||
// this is to ensure that the route with the interception marker doesn't incorrectly match
|
||||
// the non-intercepted route (ie /app/(.)[username] should not match /app/[username])
|
||||
const interceptionPrefix = interceptionMarker ? escapeStringRegexp(interceptionMarker) : '';
|
||||
let pattern;
|
||||
if (duplicateKey && backreferenceDuplicateKeys) {
|
||||
// Use a backreference to the key to ensure that the key is the same value
|
||||
// in each of the placeholders.
|
||||
pattern = `\\k<${cleanedKey}>`;
|
||||
} else if (repeat) {
|
||||
pattern = `(?<${cleanedKey}>.+?)`;
|
||||
} else {
|
||||
pattern = `(?<${cleanedKey}>[^/]+?)`;
|
||||
}
|
||||
return {
|
||||
key,
|
||||
pattern: optional ? `(?:/${interceptionPrefix}${pattern})?` : `/${interceptionPrefix}${pattern}`,
|
||||
cleanedKey: cleanedKey,
|
||||
optional,
|
||||
repeat
|
||||
};
|
||||
}
|
||||
function getNamedParametrizedRoute(route, prefixRouteKeys, includeSuffix, includePrefix, backreferenceDuplicateKeys, reference = {
|
||||
names: {},
|
||||
intercepted: {}
|
||||
}) {
|
||||
const getSafeRouteKey = buildGetSafeRouteKey();
|
||||
const routeKeys = {};
|
||||
const segments = [];
|
||||
const inverseParts = [];
|
||||
// Ensure we don't mutate the original reference object.
|
||||
reference = structuredClone(reference);
|
||||
for (const segment of removeTrailingSlash(route).slice(1).split('/')){
|
||||
const hasInterceptionMarker = INTERCEPTION_ROUTE_MARKERS.some((m)=>segment.startsWith(m));
|
||||
const paramMatches = segment.match(PARAMETER_PATTERN) // Check for parameters
|
||||
;
|
||||
const interceptionMarker = hasInterceptionMarker ? paramMatches?.[1] : undefined;
|
||||
let keyPrefix;
|
||||
if (interceptionMarker && paramMatches?.[2]) {
|
||||
keyPrefix = prefixRouteKeys ? NEXT_INTERCEPTION_MARKER_PREFIX : undefined;
|
||||
reference.intercepted[paramMatches[2]] = interceptionMarker;
|
||||
} else if (paramMatches?.[2] && reference.intercepted[paramMatches[2]]) {
|
||||
keyPrefix = prefixRouteKeys ? NEXT_INTERCEPTION_MARKER_PREFIX : undefined;
|
||||
} else {
|
||||
keyPrefix = prefixRouteKeys ? NEXT_QUERY_PARAM_PREFIX : undefined;
|
||||
}
|
||||
if (interceptionMarker && paramMatches && paramMatches[2]) {
|
||||
// If there's an interception marker, add it to the segments.
|
||||
const { key, pattern, cleanedKey, repeat, optional } = getSafeKeyFromSegment({
|
||||
getSafeRouteKey,
|
||||
interceptionMarker,
|
||||
segment: paramMatches[2],
|
||||
routeKeys,
|
||||
keyPrefix,
|
||||
backreferenceDuplicateKeys
|
||||
});
|
||||
segments.push(pattern);
|
||||
inverseParts.push(`/${paramMatches[1]}:${reference.names[key] ?? cleanedKey}${repeat ? optional ? '*' : '+' : ''}`);
|
||||
reference.names[key] ??= cleanedKey;
|
||||
} else if (paramMatches && paramMatches[2]) {
|
||||
// If there's a prefix, add it to the segments if it's enabled.
|
||||
if (includePrefix && paramMatches[1]) {
|
||||
segments.push(`/${escapeStringRegexp(paramMatches[1])}`);
|
||||
inverseParts.push(`/${paramMatches[1]}`);
|
||||
}
|
||||
const { key, pattern, cleanedKey, repeat, optional } = getSafeKeyFromSegment({
|
||||
getSafeRouteKey,
|
||||
segment: paramMatches[2],
|
||||
routeKeys,
|
||||
keyPrefix,
|
||||
backreferenceDuplicateKeys
|
||||
});
|
||||
// Remove the leading slash if includePrefix already added it.
|
||||
let s = pattern;
|
||||
if (includePrefix && paramMatches[1]) {
|
||||
s = s.substring(1);
|
||||
}
|
||||
segments.push(s);
|
||||
inverseParts.push(`/:${reference.names[key] ?? cleanedKey}${repeat ? optional ? '*' : '+' : ''}`);
|
||||
reference.names[key] ??= cleanedKey;
|
||||
} else {
|
||||
segments.push(`/${escapeStringRegexp(segment)}`);
|
||||
inverseParts.push(`/${segment}`);
|
||||
}
|
||||
// If there's a suffix, add it to the segments if it's enabled.
|
||||
if (includeSuffix && paramMatches && paramMatches[3]) {
|
||||
segments.push(escapeStringRegexp(paramMatches[3]));
|
||||
inverseParts.push(paramMatches[3]);
|
||||
}
|
||||
}
|
||||
return {
|
||||
namedParameterizedRoute: segments.join(''),
|
||||
routeKeys,
|
||||
pathToRegexpPattern: inverseParts.join(''),
|
||||
reference
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This function extends `getRouteRegex` generating also a named regexp where
|
||||
* each group is named along with a routeKeys object that indexes the assigned
|
||||
* named group with its corresponding key. When the routeKeys need to be
|
||||
* prefixed to uniquely identify internally the "prefixRouteKey" arg should
|
||||
* be "true" currently this is only the case when creating the routes-manifest
|
||||
* during the build
|
||||
*/ export function getNamedRouteRegex(normalizedRoute, options) {
|
||||
const result = getNamedParametrizedRoute(normalizedRoute, options.prefixRouteKeys, options.includeSuffix ?? false, options.includePrefix ?? false, options.backreferenceDuplicateKeys ?? false, options.reference);
|
||||
let namedRegex = result.namedParameterizedRoute;
|
||||
if (!options.excludeOptionalTrailingSlash) {
|
||||
namedRegex += '(?:/)?';
|
||||
}
|
||||
return {
|
||||
...getRouteRegex(normalizedRoute, options),
|
||||
namedRegex: `^${namedRegex}$`,
|
||||
routeKeys: result.routeKeys,
|
||||
pathToRegexpPattern: result.pathToRegexpPattern,
|
||||
reference: result.reference
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Generates a named regexp.
|
||||
* This is intended to be using for build time only.
|
||||
*/ export function getNamedMiddlewareRegex(normalizedRoute, options) {
|
||||
const { parameterizedRoute } = getParametrizedRoute(normalizedRoute, false, false);
|
||||
const { catchAll = true } = options;
|
||||
if (parameterizedRoute === '/') {
|
||||
let catchAllRegex = catchAll ? '.*' : '';
|
||||
return {
|
||||
namedRegex: `^/${catchAllRegex}$`
|
||||
};
|
||||
}
|
||||
const { namedParameterizedRoute } = getNamedParametrizedRoute(normalizedRoute, false, false, false, false, undefined);
|
||||
let catchAllGroupedRegex = catchAll ? '(?:(/.*)?)' : '';
|
||||
return {
|
||||
namedRegex: `^${namedParameterizedRoute}${catchAllGroupedRegex}$`
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=route-regex.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+213
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* A route that can be sorted by specificity.
|
||||
*/ /**
|
||||
* Determines the specificity of a route segment for sorting purposes.
|
||||
*
|
||||
* In Next.js routing, more specific routes should match before less specific ones.
|
||||
* This function returns a numeric value where lower numbers indicate higher specificity.
|
||||
*
|
||||
* Specificity order (most to least specific):
|
||||
* 1. Static segments (e.g., "about", "api") - return 0
|
||||
* 2. Dynamic segments (e.g., "[id]", "[slug]") - return 1
|
||||
* 3. Catch-all segments (e.g., "[...slug]") - return 2
|
||||
* 4. Optional catch-all segments (e.g., "[[...slug]]") - return 3
|
||||
*
|
||||
* @param segment - A single path segment (e.g., "api", "[id]", "[...slug]")
|
||||
* @returns A numeric specificity value (0-3, where 0 is most specific)
|
||||
*/ export function getSegmentSpecificity(segment) {
|
||||
// Static segments are most specific - they match exactly one path
|
||||
if (!segment.includes('[')) {
|
||||
return 0;
|
||||
}
|
||||
// Optional catch-all [[...param]] is least specific - matches zero or more segments
|
||||
if (segment.startsWith('[[...') && segment.endsWith(']]')) {
|
||||
return 3;
|
||||
}
|
||||
// Catch-all [...param] is less specific - matches one or more segments
|
||||
if (segment.startsWith('[...') && segment.endsWith(']')) {
|
||||
return 2;
|
||||
}
|
||||
// Regular dynamic [param] is more specific than catch-all - matches exactly one segment
|
||||
if (segment.startsWith('[') && segment.endsWith(']')) {
|
||||
return 1;
|
||||
}
|
||||
// Default to static (fallback case)
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Compares two route paths using a depth-first traversal approach.
|
||||
*
|
||||
* This function implements a deterministic comparison that sorts routes by specificity:
|
||||
* 1. More specific routes come first (fewer dynamic segments)
|
||||
* 2. Shorter routes are more specific than longer ones
|
||||
* 3. Routes with same specificity are sorted lexicographically
|
||||
*
|
||||
* The comparison is done segment by segment, left to right, similar to how
|
||||
* you would traverse a route tree in depth-first order.
|
||||
*
|
||||
* @param pathA - First route path to compare (e.g., "/api/users/[id]")
|
||||
* @param pathB - Second route path to compare (e.g., "/api/[...slug]")
|
||||
* @returns Negative if pathA is more specific, positive if pathB is more specific, 0 if equal
|
||||
*/ export function compareRouteSegments(pathA, pathB) {
|
||||
// Split paths into segments, removing empty strings from leading/trailing slashes
|
||||
const segmentsA = pathA.split('/').filter(Boolean);
|
||||
const segmentsB = pathB.split('/').filter(Boolean);
|
||||
// Compare segment by segment up to the length of the longer path
|
||||
const maxLength = Math.max(segmentsA.length, segmentsB.length);
|
||||
for(let i = 0; i < maxLength; i++){
|
||||
const segA = segmentsA[i] || '';
|
||||
const segB = segmentsB[i] || '';
|
||||
// Handle length differences: shorter routes are MORE specific
|
||||
// Example: "/api" is more specific than "/api/users"
|
||||
if (!segA && segB) return -1 // pathA is shorter, so more specific
|
||||
;
|
||||
if (segA && !segB) return 1 // pathB is shorter, so more specific
|
||||
;
|
||||
if (!segA && !segB) return 0 // Both paths ended, they're equal
|
||||
;
|
||||
// Compare segment specificity using our specificity scoring
|
||||
const specificityA = getSegmentSpecificity(segA);
|
||||
const specificityB = getSegmentSpecificity(segB);
|
||||
// Lower specificity number = more specific route
|
||||
// Example: "api" (0) vs "[slug]" (1) - "api" wins
|
||||
if (specificityA !== specificityB) {
|
||||
return specificityA - specificityB;
|
||||
}
|
||||
// If segments have same specificity, compare lexicographically for determinism
|
||||
// Example: "[id]" vs "[slug]" - "[id]" comes first alphabetically
|
||||
if (segA !== segB) {
|
||||
return segA.localeCompare(segB);
|
||||
}
|
||||
// Segments are identical, continue to next segment
|
||||
}
|
||||
// All segments compared equally
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Compares two complete routes for sorting purposes.
|
||||
*
|
||||
* Routes are compared with a two-tier priority system:
|
||||
* 1. Primary: Compare by source path specificity
|
||||
* 2. Secondary: If sources are equal, compare by page path specificity
|
||||
*
|
||||
* This ensures that routes are primarily organized by their source patterns,
|
||||
* with page-specific variations grouped together.
|
||||
*
|
||||
* @param a - First route to compare
|
||||
* @param b - Second route to compare
|
||||
* @returns Negative if route a should come first, positive if route b should come first, 0 if equal
|
||||
*/ function compareSortableRoutes(a, b) {
|
||||
// First compare by source specificity - this is the primary sorting criterion
|
||||
// Source represents the original route pattern and takes precedence
|
||||
const sourceResult = compareRouteSegments(a.sourcePage, b.sourcePage);
|
||||
if (sourceResult !== 0) return sourceResult;
|
||||
// If sources are identical, compare by page specificity as a tiebreaker
|
||||
// Page represents the final rendered route and provides secondary ordering
|
||||
return compareRouteSegments(a.page, b.page);
|
||||
}
|
||||
/**
|
||||
* Sorts an array of routes by specificity using a deterministic depth-first traversal approach.
|
||||
*
|
||||
* This function implements Next.js route matching priority where more specific routes
|
||||
* should be matched before less specific ones. The sorting is deterministic and stable,
|
||||
* meaning identical inputs will always produce identical outputs.
|
||||
*
|
||||
* Sorting criteria (in order of priority):
|
||||
* 1. Source path specificity (primary)
|
||||
* 2. Page path specificity (secondary)
|
||||
* 3. Lexicographic ordering (tertiary, for determinism)
|
||||
*
|
||||
* Examples of specificity order:
|
||||
* - "/api/users" (static) comes before "/api/[slug]" (dynamic)
|
||||
* - "/api/[id]" (dynamic) comes before "/api/[...slug]" (catch-all)
|
||||
* - "/api/[...slug]" (catch-all) comes before "/api/[[...slug]]" (optional catch-all)
|
||||
*
|
||||
* @param routes - Array of routes to sort
|
||||
* @returns New sorted array (does not mutate input)
|
||||
*/ export function sortSortableRoutes(routes) {
|
||||
// Because sort is always in-place, we need to create a shallow copy to avoid
|
||||
// mutating the input array.
|
||||
return [
|
||||
...routes
|
||||
].sort(compareSortableRoutes);
|
||||
}
|
||||
/**
|
||||
* Sorts an array of pages by specificity using a deterministic depth-first
|
||||
* traversal approach.
|
||||
*
|
||||
* @param pages - Array of pages to sort
|
||||
* @returns New sorted array (does not mutate input)
|
||||
*/ export function sortPages(pages) {
|
||||
// Because sort is always in-place, we need to create a shallow copy to avoid
|
||||
// mutating the input array.
|
||||
return [
|
||||
...pages
|
||||
].sort(compareRouteSegments);
|
||||
}
|
||||
/**
|
||||
* Sorts an object by specificity of the keys using a deterministic depth-first traversal approach.
|
||||
*
|
||||
* @param obj - object of pages to sort
|
||||
* @returns New sorted object (does not mutate input)
|
||||
*/ export function sortPagesObject(obj) {
|
||||
// Because sort is always in-place, we need to create a shallow copy to avoid
|
||||
// mutating the input.
|
||||
return Object.keys(obj).sort(compareRouteSegments).reduce((acc, key)=>{
|
||||
acc[key] = obj[key];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
/**
|
||||
* Sorts an array of objects by sourcePage and page using a deterministic
|
||||
* depth-first traversal approach.
|
||||
*
|
||||
* @param objects - Array of objects to sort
|
||||
* @param getter - Function to get the sourcePage and page from an object
|
||||
* @returns New sorted array (does not mutate input)
|
||||
*/ export function sortSortableRouteObjects(objects, getter) {
|
||||
// Create a SortableRoute for each object.
|
||||
const routes = [];
|
||||
for (const object of objects){
|
||||
const route = getter(object);
|
||||
routes.push({
|
||||
...route,
|
||||
object
|
||||
});
|
||||
}
|
||||
// In-place sort the SortableRoutes.
|
||||
routes.sort(compareSortableRoutes);
|
||||
// Map the sorted SortableRoutes back to the original objects.
|
||||
return routes.map(({ object })=>object);
|
||||
}
|
||||
/**
|
||||
* Sorts an array of objects by page using a deterministic depth-first traversal
|
||||
* approach.
|
||||
*
|
||||
* @param objects - Array of objects to sort
|
||||
* @param getter - Function to get the page from an object
|
||||
* @returns New sorted array (does not mutate input)
|
||||
*/ export function sortPageObjects(objects, getter) {
|
||||
const indexes = {};
|
||||
const pages = new Set();
|
||||
for(let i = 0; i < objects.length; i++){
|
||||
const object = objects[i];
|
||||
const page = getter(object);
|
||||
indexes[page]?.push(i) || (indexes[page] = [
|
||||
i
|
||||
]);
|
||||
pages.add(page);
|
||||
}
|
||||
// Sort the unique pages.
|
||||
const sortedPages = Array.from(pages).sort(compareRouteSegments);
|
||||
// Map the sorted pages back to the original objects.
|
||||
return sortedPages.reduce((sortedObjects, page)=>{
|
||||
// Add all objects for this page to the sorted array.
|
||||
for (const i of indexes[page]){
|
||||
sortedObjects.push(objects[i]);
|
||||
}
|
||||
// Return the sorted array.
|
||||
return sortedObjects;
|
||||
}, []);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=sortable-routes.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+226
@@ -0,0 +1,226 @@
|
||||
class UrlNode {
|
||||
insert(urlPath) {
|
||||
this._insert(urlPath.split('/').filter(Boolean), [], false);
|
||||
}
|
||||
smoosh() {
|
||||
return this._smoosh();
|
||||
}
|
||||
_smoosh(prefix = '/') {
|
||||
const childrenPaths = [
|
||||
...this.children.keys()
|
||||
].sort();
|
||||
if (this.slugName !== null) {
|
||||
childrenPaths.splice(childrenPaths.indexOf('[]'), 1);
|
||||
}
|
||||
if (this.restSlugName !== null) {
|
||||
childrenPaths.splice(childrenPaths.indexOf('[...]'), 1);
|
||||
}
|
||||
if (this.optionalRestSlugName !== null) {
|
||||
childrenPaths.splice(childrenPaths.indexOf('[[...]]'), 1);
|
||||
}
|
||||
const routes = childrenPaths.map((c)=>this.children.get(c)._smoosh(`${prefix}${c}/`)).reduce((prev, curr)=>[
|
||||
...prev,
|
||||
...curr
|
||||
], []);
|
||||
if (this.slugName !== null) {
|
||||
routes.push(...this.children.get('[]')._smoosh(`${prefix}[${this.slugName}]/`));
|
||||
}
|
||||
if (!this.placeholder) {
|
||||
const r = prefix === '/' ? '/' : prefix.slice(0, -1);
|
||||
if (this.optionalRestSlugName != null) {
|
||||
throw Object.defineProperty(new Error(`You cannot define a route with the same specificity as a optional catch-all route ("${r}" and "${r}[[...${this.optionalRestSlugName}]]").`), "__NEXT_ERROR_CODE", {
|
||||
value: "E458",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
routes.unshift(r);
|
||||
}
|
||||
if (this.restSlugName !== null) {
|
||||
routes.push(...this.children.get('[...]')._smoosh(`${prefix}[...${this.restSlugName}]/`));
|
||||
}
|
||||
if (this.optionalRestSlugName !== null) {
|
||||
routes.push(...this.children.get('[[...]]')._smoosh(`${prefix}[[...${this.optionalRestSlugName}]]/`));
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
_insert(urlPaths, slugNames, isCatchAll) {
|
||||
if (urlPaths.length === 0) {
|
||||
this.placeholder = false;
|
||||
return;
|
||||
}
|
||||
if (isCatchAll) {
|
||||
throw Object.defineProperty(new Error(`Catch-all must be the last part of the URL.`), "__NEXT_ERROR_CODE", {
|
||||
value: "E392",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
// The next segment in the urlPaths list
|
||||
let nextSegment = urlPaths[0];
|
||||
// Check if the segment matches `[something]`
|
||||
if (nextSegment.startsWith('[') && nextSegment.endsWith(']')) {
|
||||
// Strip `[` and `]`, leaving only `something`
|
||||
let segmentName = nextSegment.slice(1, -1);
|
||||
let isOptional = false;
|
||||
if (segmentName.startsWith('[') && segmentName.endsWith(']')) {
|
||||
// Strip optional `[` and `]`, leaving only `something`
|
||||
segmentName = segmentName.slice(1, -1);
|
||||
isOptional = true;
|
||||
}
|
||||
if (segmentName.startsWith('…')) {
|
||||
throw Object.defineProperty(new Error(`Detected a three-dot character ('…') at ('${segmentName}'). Did you mean ('...')?`), "__NEXT_ERROR_CODE", {
|
||||
value: "E147",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
if (segmentName.startsWith('...')) {
|
||||
// Strip `...`, leaving only `something`
|
||||
segmentName = segmentName.substring(3);
|
||||
isCatchAll = true;
|
||||
}
|
||||
if (segmentName.startsWith('[') || segmentName.endsWith(']')) {
|
||||
throw Object.defineProperty(new Error(`Segment names may not start or end with extra brackets ('${segmentName}').`), "__NEXT_ERROR_CODE", {
|
||||
value: "E421",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
if (segmentName.startsWith('.')) {
|
||||
throw Object.defineProperty(new Error(`Segment names may not start with erroneous periods ('${segmentName}').`), "__NEXT_ERROR_CODE", {
|
||||
value: "E288",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
function handleSlug(previousSlug, nextSlug) {
|
||||
if (previousSlug !== null) {
|
||||
// If the specific segment already has a slug but the slug is not `something`
|
||||
// This prevents collisions like:
|
||||
// pages/[post]/index.js
|
||||
// pages/[id]/index.js
|
||||
// Because currently multiple dynamic params on the same segment level are not supported
|
||||
if (previousSlug !== nextSlug) {
|
||||
// TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment.
|
||||
throw Object.defineProperty(new Error(`You cannot use different slug names for the same dynamic path ('${previousSlug}' !== '${nextSlug}').`), "__NEXT_ERROR_CODE", {
|
||||
value: "E337",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
slugNames.forEach((slug)=>{
|
||||
if (slug === nextSlug) {
|
||||
throw Object.defineProperty(new Error(`You cannot have the same slug name "${nextSlug}" repeat within a single dynamic path`), "__NEXT_ERROR_CODE", {
|
||||
value: "E247",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
if (slug.replace(/\W/g, '') === nextSegment.replace(/\W/g, '')) {
|
||||
throw Object.defineProperty(new Error(`You cannot have the slug names "${slug}" and "${nextSlug}" differ only by non-word symbols within a single dynamic path`), "__NEXT_ERROR_CODE", {
|
||||
value: "E499",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
slugNames.push(nextSlug);
|
||||
}
|
||||
if (isCatchAll) {
|
||||
if (isOptional) {
|
||||
if (this.restSlugName != null) {
|
||||
throw Object.defineProperty(new Error(`You cannot use both an required and optional catch-all route at the same level ("[...${this.restSlugName}]" and "${urlPaths[0]}" ).`), "__NEXT_ERROR_CODE", {
|
||||
value: "E299",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
handleSlug(this.optionalRestSlugName, segmentName);
|
||||
// slugName is kept as it can only be one particular slugName
|
||||
this.optionalRestSlugName = segmentName;
|
||||
// nextSegment is overwritten to [[...]] so that it can later be sorted specifically
|
||||
nextSegment = '[[...]]';
|
||||
} else {
|
||||
if (this.optionalRestSlugName != null) {
|
||||
throw Object.defineProperty(new Error(`You cannot use both an optional and required catch-all route at the same level ("[[...${this.optionalRestSlugName}]]" and "${urlPaths[0]}").`), "__NEXT_ERROR_CODE", {
|
||||
value: "E300",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
handleSlug(this.restSlugName, segmentName);
|
||||
// slugName is kept as it can only be one particular slugName
|
||||
this.restSlugName = segmentName;
|
||||
// nextSegment is overwritten to [...] so that it can later be sorted specifically
|
||||
nextSegment = '[...]';
|
||||
}
|
||||
} else {
|
||||
if (isOptional) {
|
||||
throw Object.defineProperty(new Error(`Optional route parameters are not yet supported ("${urlPaths[0]}").`), "__NEXT_ERROR_CODE", {
|
||||
value: "E435",
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
handleSlug(this.slugName, segmentName);
|
||||
// slugName is kept as it can only be one particular slugName
|
||||
this.slugName = segmentName;
|
||||
// nextSegment is overwritten to [] so that it can later be sorted specifically
|
||||
nextSegment = '[]';
|
||||
}
|
||||
}
|
||||
// If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
|
||||
if (!this.children.has(nextSegment)) {
|
||||
this.children.set(nextSegment, new UrlNode());
|
||||
}
|
||||
this.children.get(nextSegment)._insert(urlPaths.slice(1), slugNames, isCatchAll);
|
||||
}
|
||||
constructor(){
|
||||
this.placeholder = true;
|
||||
this.children = new Map();
|
||||
this.slugName = null;
|
||||
this.restSlugName = null;
|
||||
this.optionalRestSlugName = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `sortSortableRoutes` or `sortPages` instead.
|
||||
*/ export function getSortedRoutes(normalizedPages) {
|
||||
// First the UrlNode is created, and every UrlNode can have only 1 dynamic segment
|
||||
// Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js
|
||||
// Only 1 dynamic segment per nesting level
|
||||
// So in the case that is test/integration/dynamic-routing it'll be this:
|
||||
// pages/[post]/comments.js
|
||||
// pages/blog/[post]/comment/[id].js
|
||||
// Both are fine because `pages/[post]` and `pages/blog` are on the same level
|
||||
// So in this case `UrlNode` created here has `this.slugName === 'post'`
|
||||
// And since your PR passed through `slugName` as an array basically it'd including it in too many possibilities
|
||||
// Instead what has to be passed through is the upwards path's dynamic names
|
||||
const root = new UrlNode();
|
||||
// Here the `root` gets injected multiple paths, and insert will break them up into sublevels
|
||||
normalizedPages.forEach((pagePath)=>root.insert(pagePath));
|
||||
// Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
|
||||
return root.smoosh();
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `sortSortableRouteObjects` or `sortPageObjects` instead.
|
||||
*/ export function getSortedRouteObjects(objects, getter) {
|
||||
// We're assuming here that all the pathnames are unique, that way we can
|
||||
// sort the list and use the index as the key.
|
||||
const indexes = {};
|
||||
const pathnames = [];
|
||||
for(let i = 0; i < objects.length; i++){
|
||||
const pathname = getter(objects[i]);
|
||||
indexes[pathname] = i;
|
||||
pathnames[i] = pathname;
|
||||
}
|
||||
// Sort the pathnames.
|
||||
const sorted = getSortedRoutes(pathnames);
|
||||
// Map the sorted pathnames back to the original objects using the new sorted
|
||||
// index.
|
||||
return sorted.map((pathname)=>objects[indexes[pathname]]);
|
||||
}
|
||||
|
||||
//# sourceMappingURL=sorted-routes.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user