-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
Copy pathparams-and-props.ts
113 lines (102 loc) · 3.94 KB
/
params-and-props.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import type { ComponentInstance, Params, Props, RouteData } from '../../@types/astro.js';
import { DEFAULT_404_COMPONENT } from '../constants.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import type { Logger } from '../logger/core.js';
import { routeIsFallback } from '../redirects/helpers.js';
import { routeIsRedirect } from '../redirects/index.js';
import type { RouteCache } from './route-cache.js';
import { callGetStaticPaths, findPathItemByKey } from './route-cache.js';
interface GetParamsAndPropsOptions {
mod: ComponentInstance | undefined;
routeData?: RouteData | undefined;
routeCache: RouteCache;
pathname: string;
logger: Logger;
serverLike: boolean;
}
export async function getProps(opts: GetParamsAndPropsOptions): Promise<Props> {
const { logger, mod, routeData: route, routeCache, pathname, serverLike } = opts;
// If there's no route, or if there's a pathname (e.g. a static `src/pages/normal.astro` file),
// then we know for sure they don't have params and props, return a fallback value.
if (!route || route.pathname) {
return {};
}
if (
routeIsRedirect(route) ||
routeIsFallback(route) ||
route.component === DEFAULT_404_COMPONENT
) {
return {};
}
// During build, the route cache should already be populated.
// During development, the route cache is filled on-demand and may be empty.
const staticPaths = await callGetStaticPaths({
mod,
route,
routeCache,
logger,
ssr: serverLike,
});
const params = getParams(route, pathname);
const matchedStaticPath = findPathItemByKey(staticPaths, params, route, logger);
if (!matchedStaticPath && (serverLike ? route.prerender : true)) {
throw new AstroError({
...AstroErrorData.NoMatchingStaticPathFound,
message: AstroErrorData.NoMatchingStaticPathFound.message(pathname),
hint: AstroErrorData.NoMatchingStaticPathFound.hint([route.component]),
});
}
if (mod) {
validatePrerenderEndpointCollision(route, mod, params);
}
const props: Props = matchedStaticPath?.props ? { ...matchedStaticPath.props } : {};
return props;
}
/**
* When given a route with the pattern `/[x]/[y]/[z]/svelte`, and a pathname `/a/b/c/svelte`,
* returns the params object: { x: "a", y: "b", z: "c" }.
*/
export function getParams(route: RouteData, pathname: string): Params {
if (!route.params.length) return {};
// The RegExp pattern expects a decoded string, but the pathname is encoded
// when the URL contains non-English characters.
const paramsMatch = route.pattern.exec(decodeURIComponent(pathname));
if (!paramsMatch) return {};
const params: Params = {};
route.params.forEach((key, i) => {
if (key.startsWith('...')) {
params[key.slice(3)] = paramsMatch[i + 1] ? paramsMatch[i + 1] : undefined;
} else {
params[key] = paramsMatch[i + 1];
}
});
return params;
}
/**
* If we have an endpoint at `src/pages/api/[slug].ts` that's prerendered, and the `slug`
* is `undefined`, throw an error as we can't generate the `/api` file and `/api` directory
* at the same time. Using something like `[slug].json.ts` instead will work.
*/
function validatePrerenderEndpointCollision(
route: RouteData,
mod: ComponentInstance,
params: Params,
) {
if (route.type === 'endpoint' && mod.getStaticPaths) {
const lastSegment = route.segments[route.segments.length - 1];
const paramValues = Object.values(params);
const lastParam = paramValues[paramValues.length - 1];
// Check last segment is solely `[slug]` or `[...slug]` case (dynamic). Make sure it's not
// `foo[slug].js` by checking segment length === 1. Also check here if that param is undefined.
if (lastSegment.length === 1 && lastSegment[0].dynamic && lastParam === undefined) {
throw new AstroError({
...AstroErrorData.PrerenderDynamicEndpointPathCollide,
message: AstroErrorData.PrerenderDynamicEndpointPathCollide.message(route.route),
hint: AstroErrorData.PrerenderDynamicEndpointPathCollide.hint(route.component),
location: {
file: route.component,
},
});
}
}
}