@markless/router 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-server-entry-DaYssO9x.js +1331 -0
- package/dist/debug-channel-CewnC9fH.js +2 -0
- package/dist/{debug-channel-CSzo-Zj8.js → debug-channel-Di6SppZv.js} +36 -17
- package/dist/index-BjxpxRL7.d.ts +248 -0
- package/dist/index.d.ts +2 -212
- package/dist/index.js +108 -40
- package/dist/protocol-constants-B4WxrZAS.js +26 -0
- package/dist/request-locals-z3rJTizs.js +90 -0
- package/dist/route-href-CGooO_g6.js +18 -0
- package/dist/route-manifest-DMizh42B.js +456 -0
- package/dist/typescript-plugin.cjs +9 -6
- package/dist/typescript-plugin.d.ts +3 -11194
- package/dist/typescript-plugin.js +9 -6
- package/dist/vite/route-href.d.ts +2 -3
- package/dist/vite/route-href.js +1 -17
- package/dist/vite/runtime/create-route-discovery.d.ts +3 -4
- package/dist/vite/runtime/create-route-discovery.js +1 -1
- package/dist/vite/runtime/create-server-entry.d.ts +27 -5
- package/dist/vite/runtime/create-server-entry.js +2 -400
- package/dist/vite/runtime/mdx-route.d.ts +1116 -17
- package/dist/vite/runtime/mdx-route.js +1097 -31
- package/dist/vite.d.ts +10 -6
- package/dist/vite.js +965 -203
- package/package.json +7 -6
- package/src/vite/entries/client-entry.ts +10 -1
- package/src/vite/entries/fragment-entry.ts +797 -0
- package/src/vite/entries/resume-entry.ts +41 -7
- package/dist/request-files-C-0LLSsx.js +0 -232
- package/dist/route-manifest-DPgGIyrS.js +0 -186
|
@@ -1,26 +1,60 @@
|
|
|
1
|
-
const mdxPageModuleLoaders = import.meta.glob('/pages/**/*.mdx'
|
|
1
|
+
const mdxPageModuleLoaders = import.meta.glob('/pages/**/*.mdx', {
|
|
2
|
+
query: '?markless-resume',
|
|
3
|
+
});
|
|
2
4
|
const tsrxResumeModuleLoaders = import.meta.glob('/pages/**/*.tsrx', {
|
|
3
5
|
query: '?markless-resume',
|
|
4
6
|
});
|
|
7
|
+
const routeResumeModules = new WeakMap<() => Promise<unknown>, Promise<unknown>>();
|
|
5
8
|
|
|
6
9
|
export async function resumeContainerEvent(input: {
|
|
7
10
|
readonly root: ParentNode;
|
|
8
11
|
readonly [key: string]: unknown;
|
|
9
12
|
}) {
|
|
10
13
|
const file = routeFileFromRoot(input.root);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
try {
|
|
15
|
+
const loadRouteResumeModule = file && routeResumeModuleLoader(file);
|
|
16
|
+
const routeResumeModule = loadRouteResumeModule
|
|
17
|
+
? await loadRouteModuleOnce(loadRouteResumeModule)
|
|
18
|
+
: undefined;
|
|
19
|
+
const resume = (routeResumeModule as RouteResumeModule | undefined)?.resumeContainerEvent;
|
|
20
|
+
if (typeof resume !== 'function') {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Markless Router could not resume route module: ${file ?? '<unknown>'}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
await resume(input);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
const failure = `${error}`;
|
|
28
|
+
// A gesture needing a chunk a deploy removed reloads once; the failure names the chunk's hashed URL.
|
|
29
|
+
if (
|
|
30
|
+
import.meta.env.DEV ||
|
|
31
|
+
!input.event ||
|
|
32
|
+
!/imported module|script failed/.test(failure) ||
|
|
33
|
+
sessionStorage.mlChunkReload === failure
|
|
34
|
+
) {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
sessionStorage.mlChunkReload = failure;
|
|
38
|
+
location.reload();
|
|
16
39
|
}
|
|
17
|
-
await resume(input);
|
|
18
40
|
}
|
|
19
41
|
|
|
20
42
|
interface RouteResumeModule {
|
|
21
43
|
readonly resumeContainerEvent?: (input: unknown) => unknown;
|
|
22
44
|
}
|
|
23
45
|
|
|
46
|
+
function loadRouteModuleOnce(load: () => Promise<unknown>): Promise<unknown> {
|
|
47
|
+
let pending = routeResumeModules.get(load);
|
|
48
|
+
if (!pending) {
|
|
49
|
+
pending = load().catch((error: unknown) => {
|
|
50
|
+
routeResumeModules.delete(load);
|
|
51
|
+
throw error;
|
|
52
|
+
});
|
|
53
|
+
routeResumeModules.set(load, pending);
|
|
54
|
+
}
|
|
55
|
+
return pending;
|
|
56
|
+
}
|
|
57
|
+
|
|
24
58
|
function routeResumeModuleLoader(file: string): (() => Promise<unknown>) | undefined {
|
|
25
59
|
const loaders = file.endsWith('.mdx') ? mdxPageModuleLoaders : tsrxResumeModuleLoaders;
|
|
26
60
|
return loaders[file] ?? loaders[`/${file}`];
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { basename, extname, join, normalize, relative } from "pathe";
|
|
2
|
-
import { joinURL, withLeadingSlash, withoutLeadingSlash } from "ufo";
|
|
3
|
-
//#region src/request-files.ts
|
|
4
|
-
const API_DIR = "api";
|
|
5
|
-
const MIDDLEWARE_DIR = "middleware";
|
|
6
|
-
const REQUEST_FILE_EXTENSION = ".ts";
|
|
7
|
-
const HTTP_METHODS = new Set([
|
|
8
|
-
"connect",
|
|
9
|
-
"delete",
|
|
10
|
-
"get",
|
|
11
|
-
"head",
|
|
12
|
-
"options",
|
|
13
|
-
"patch",
|
|
14
|
-
"post",
|
|
15
|
-
"put",
|
|
16
|
-
"trace"
|
|
17
|
-
]);
|
|
18
|
-
function parseRequestFile(fileId, sourceText) {
|
|
19
|
-
const file = normalizeRequestFileId(fileId);
|
|
20
|
-
const apiFile = apiRelativeFile(file);
|
|
21
|
-
if (apiFile) return parseApiRequestFile(file, apiFile, sourceText);
|
|
22
|
-
if (middlewareRelativeFile(file)) return parseMiddlewareRequestFile(file, sourceText);
|
|
23
|
-
return {
|
|
24
|
-
diagnostics: [],
|
|
25
|
-
file,
|
|
26
|
-
kind: "none"
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function normalizeRequestFileId(fileId) {
|
|
30
|
-
return withoutLeadingSlash(normalize(fileId));
|
|
31
|
-
}
|
|
32
|
-
function transformRequestFileSource(fileId, sourceText) {
|
|
33
|
-
const requestFile = parseRequestFile(fileId, sourceText);
|
|
34
|
-
if (requestFile.kind === "none" || requestFile.diagnostics.length > 0 || !requestFile.defaultExport) return;
|
|
35
|
-
const handler = localizeDefaultExport(sourceText);
|
|
36
|
-
if (!handler) return;
|
|
37
|
-
return {
|
|
38
|
-
code: requestFileWrapperSource(requestFile, handler.code, handler.name),
|
|
39
|
-
file: requestFile.file,
|
|
40
|
-
kind: requestFile.kind
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function parseApiRequestFile(file, apiFile, sourceText) {
|
|
44
|
-
const defaultExport = findDefaultFunctionExport(sourceText);
|
|
45
|
-
const cache = parseCacheMetadata(sourceText);
|
|
46
|
-
const diagnostics = [
|
|
47
|
-
...missingDefaultFunctionDiagnostics("api", defaultExport),
|
|
48
|
-
...httpMethodExportDiagnostics(sourceText),
|
|
49
|
-
...cacheMetadataDiagnostics(sourceText, cache)
|
|
50
|
-
];
|
|
51
|
-
return {
|
|
52
|
-
...cache ? { cache } : {},
|
|
53
|
-
...defaultExport ? { defaultExport } : {},
|
|
54
|
-
diagnostics,
|
|
55
|
-
file,
|
|
56
|
-
kind: "api",
|
|
57
|
-
...apiRouteFromFile(apiFile)
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function parseMiddlewareRequestFile(file, sourceText) {
|
|
61
|
-
const defaultExport = findDefaultFunctionExport(sourceText);
|
|
62
|
-
const diagnostics = missingDefaultFunctionDiagnostics("middleware", defaultExport);
|
|
63
|
-
return {
|
|
64
|
-
...defaultExport ? { defaultExport } : {},
|
|
65
|
-
diagnostics,
|
|
66
|
-
file,
|
|
67
|
-
kind: "middleware"
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function apiRouteFromFile(apiFile) {
|
|
71
|
-
const rawSegments = normalize(join(apiFile.slice(0, -3))).split("/");
|
|
72
|
-
const finalSegment = rawSegments.at(-1);
|
|
73
|
-
const method = methodSuffix(finalSegment);
|
|
74
|
-
if (method !== "all") rawSegments[rawSegments.length - 1] = finalSegment.slice(0, -(method.length + 1));
|
|
75
|
-
if (rawSegments.at(-1) === "index") rawSegments.pop();
|
|
76
|
-
const normalizedSegments = rawSegments.filter(Boolean).map(normalizeRouteSegment);
|
|
77
|
-
const pathnameSegments = normalizedSegments.map((segment) => segment.pathname);
|
|
78
|
-
const patternSegments = normalizedSegments.map((segment) => segment.pattern);
|
|
79
|
-
return {
|
|
80
|
-
method,
|
|
81
|
-
route: {
|
|
82
|
-
params: normalizedSegments.flatMap((segment) => segment.params),
|
|
83
|
-
pathname: requestPathname([API_DIR, ...pathnameSegments]),
|
|
84
|
-
pattern: requestPathname([API_DIR, ...patternSegments])
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function normalizeRouteSegment(segment) {
|
|
89
|
-
const catchAll = segment.match(/^\[\.\.\.([A-Za-z_$][\w$]*)\]$/);
|
|
90
|
-
if (catchAll) return {
|
|
91
|
-
params: [{
|
|
92
|
-
kind: "catch-all",
|
|
93
|
-
name: catchAll[1]
|
|
94
|
-
}],
|
|
95
|
-
pathname: "**",
|
|
96
|
-
pattern: `[...${catchAll[1]}]`
|
|
97
|
-
};
|
|
98
|
-
const dynamic = segment.match(/^\[([A-Za-z_$][\w$]*)\]$/);
|
|
99
|
-
if (dynamic) return {
|
|
100
|
-
params: [{
|
|
101
|
-
kind: "dynamic",
|
|
102
|
-
name: dynamic[1]
|
|
103
|
-
}],
|
|
104
|
-
pathname: `:${dynamic[1]}`,
|
|
105
|
-
pattern: `[${dynamic[1]}]`
|
|
106
|
-
};
|
|
107
|
-
return {
|
|
108
|
-
params: [],
|
|
109
|
-
pathname: segment,
|
|
110
|
-
pattern: segment
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function requestPathname(segments) {
|
|
114
|
-
if (segments.length === 0) return "/";
|
|
115
|
-
const [base, ...rest] = segments;
|
|
116
|
-
return withLeadingSlash(joinURL(base, ...rest));
|
|
117
|
-
}
|
|
118
|
-
function methodSuffix(finalSegment) {
|
|
119
|
-
const suffix = finalSegment.split(".").at(-1);
|
|
120
|
-
return suffix && HTTP_METHODS.has(suffix) ? suffix : "all";
|
|
121
|
-
}
|
|
122
|
-
function findDefaultFunctionExport(sourceText) {
|
|
123
|
-
const directFunction = sourceText.match(/\bexport\s+default\s+(?:async\s+)?function(?:\s+[A-Za-z_$][\w$]*)?\s*\(\s*([A-Za-z_$][\w$]*)?/);
|
|
124
|
-
if (directFunction) return {
|
|
125
|
-
kind: "function",
|
|
126
|
-
parameterName: directFunction[1]
|
|
127
|
-
};
|
|
128
|
-
const directArrow = sourceText.match(/\bexport\s+default\s+(?:async\s+)?(?:\(\s*([A-Za-z_$][\w$]*)?\s*\)|([A-Za-z_$][\w$]*))\s*=>/);
|
|
129
|
-
if (directArrow) return {
|
|
130
|
-
kind: "function",
|
|
131
|
-
parameterName: directArrow[1] ?? directArrow[2]
|
|
132
|
-
};
|
|
133
|
-
const defaultIdentifier = sourceText.match(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;/)?.[1];
|
|
134
|
-
if (!defaultIdentifier) return;
|
|
135
|
-
const escapedIdentifier = escapeRegExp(defaultIdentifier);
|
|
136
|
-
const constArrow = sourceText.match(new RegExp(`\\bconst\\s+${escapedIdentifier}\\s*=\\s*(?:async\\s+)?(?:\\(\\s*([A-Za-z_$][\\w$]*)?\\s*\\)|([A-Za-z_$][\\w$]*))\\s*=>`));
|
|
137
|
-
if (constArrow) return {
|
|
138
|
-
kind: "function",
|
|
139
|
-
parameterName: constArrow[1] ?? constArrow[2]
|
|
140
|
-
};
|
|
141
|
-
const functionDeclaration = sourceText.match(new RegExp(`\\b(?:async\\s+)?function\\s+${escapedIdentifier}\\s*\\(\\s*([A-Za-z_$][\\w$]*)?`));
|
|
142
|
-
if (functionDeclaration) return {
|
|
143
|
-
kind: "function",
|
|
144
|
-
parameterName: functionDeclaration[1]
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
function parseCacheMetadata(sourceText) {
|
|
148
|
-
const objectLiteral = sourceText.match(/\bexport\s+const\s+cache\s*=\s*(\{[\s\S]*?\})\s*;?/)?.[1];
|
|
149
|
-
if (!objectLiteral) return;
|
|
150
|
-
const maxAge = objectLiteral.match(/^\{\s*maxAge\s*:\s*(\d+(?:\.\d+)?)\s*,?\s*\}$/)?.[1];
|
|
151
|
-
return maxAge ? { maxAge: Number(maxAge) } : void 0;
|
|
152
|
-
}
|
|
153
|
-
function missingDefaultFunctionDiagnostics(kind, defaultExport) {
|
|
154
|
-
if (defaultExport) return [];
|
|
155
|
-
return [{
|
|
156
|
-
code: "missing-default-function",
|
|
157
|
-
message: kind === "api" ? "API files must default export a function." : "Middleware files must default export a function."
|
|
158
|
-
}];
|
|
159
|
-
}
|
|
160
|
-
function httpMethodExportDiagnostics(sourceText) {
|
|
161
|
-
const exportedMethods = /* @__PURE__ */ new Set();
|
|
162
|
-
for (const pattern of [
|
|
163
|
-
/\bexport\s+(?:async\s+)?function\s+([A-Z]+)\b/g,
|
|
164
|
-
/\bexport\s+const\s+([A-Z]+)\b/g,
|
|
165
|
-
/\bexport\s*\{([^}]+)\}/g
|
|
166
|
-
]) for (const match of sourceText.matchAll(pattern)) {
|
|
167
|
-
const exportClause = match[1];
|
|
168
|
-
if (!exportClause) continue;
|
|
169
|
-
if (exportClause.includes(",")) {
|
|
170
|
-
for (const exportName of exportClause.split(",")) addHttpMethodExport(exportedMethods, exportName);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
addHttpMethodExport(exportedMethods, exportClause);
|
|
174
|
-
}
|
|
175
|
-
return Array.from(exportedMethods).toSorted().map((method) => ({
|
|
176
|
-
code: "http-method-export",
|
|
177
|
-
message: `Do not export ${method}; the HTTP method comes from the filename.`
|
|
178
|
-
}));
|
|
179
|
-
}
|
|
180
|
-
function addHttpMethodExport(methods, exportName) {
|
|
181
|
-
const method = exportName.trim().split(/\s+as\s+/i).at(-1)?.trim();
|
|
182
|
-
if (method && HTTP_METHODS.has(method.toLowerCase())) methods.add(method.toUpperCase());
|
|
183
|
-
}
|
|
184
|
-
function cacheMetadataDiagnostics(sourceText, cache) {
|
|
185
|
-
if (!/\bexport\s+const\s+cache\b/.test(sourceText) || cache) return [];
|
|
186
|
-
return [{
|
|
187
|
-
code: "invalid-cache-metadata",
|
|
188
|
-
message: "Use export const cache for endpoint cache metadata."
|
|
189
|
-
}];
|
|
190
|
-
}
|
|
191
|
-
function apiRelativeFile(file) {
|
|
192
|
-
if (!isRequestModuleFile(file) || file === API_DIR || !file.startsWith(`${API_DIR}/`)) return;
|
|
193
|
-
return relative(API_DIR, file);
|
|
194
|
-
}
|
|
195
|
-
function middlewareRelativeFile(file) {
|
|
196
|
-
if (!isRequestModuleFile(file) || file === MIDDLEWARE_DIR || !file.startsWith(`${MIDDLEWARE_DIR}/`)) return;
|
|
197
|
-
return relative(MIDDLEWARE_DIR, file);
|
|
198
|
-
}
|
|
199
|
-
function localizeDefaultExport(sourceText) {
|
|
200
|
-
const defaultIdentifierMatch = sourceText.match(/\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;/);
|
|
201
|
-
if (defaultIdentifierMatch) return {
|
|
202
|
-
code: sourceText.replace(defaultIdentifierMatch[0], ""),
|
|
203
|
-
name: defaultIdentifierMatch[1]
|
|
204
|
-
};
|
|
205
|
-
const directDefaultMatch = sourceText.match(/\bexport\s+default\b/);
|
|
206
|
-
if (!directDefaultMatch || directDefaultMatch.index === void 0) return;
|
|
207
|
-
return {
|
|
208
|
-
code: `${sourceText.slice(0, directDefaultMatch.index)}const __markless_request_handler__ =${sourceText.slice(directDefaultMatch.index + directDefaultMatch[0].length)}`,
|
|
209
|
-
name: "__markless_request_handler__"
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
function requestFileWrapperSource(requestFile, sourceText, handlerName) {
|
|
213
|
-
const defineImport = requestFile.kind === "api" && requestFile.cache ? "import { defineCachedHandler as __markless_define_handler__ } from \"nitro/cache\";" : "import { defineHandler as __markless_define_handler__ } from \"nitro\";";
|
|
214
|
-
const wrappedHandler = `(event) =>
|
|
215
|
-
${handlerName}(__markless_create_http_context__(event))`;
|
|
216
|
-
const wrappedDefault = requestFile.kind === "api" && requestFile.cache ? `__markless_define_handler__(${wrappedHandler}, cache)` : `__markless_define_handler__(${wrappedHandler})`;
|
|
217
|
-
return `${defineImport}
|
|
218
|
-
import { __marklessCreateHttpContext as __markless_create_http_context__ } from "@markless/router";
|
|
219
|
-
|
|
220
|
-
${sourceText.trimEnd()}
|
|
221
|
-
|
|
222
|
-
export default ${wrappedDefault};
|
|
223
|
-
`;
|
|
224
|
-
}
|
|
225
|
-
function isRequestModuleFile(file) {
|
|
226
|
-
return extname(file) === REQUEST_FILE_EXTENSION && basename(file) !== REQUEST_FILE_EXTENSION;
|
|
227
|
-
}
|
|
228
|
-
function escapeRegExp(value) {
|
|
229
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
230
|
-
}
|
|
231
|
-
//#endregion
|
|
232
|
-
export { parseRequestFile as n, transformRequestFileSource as r, normalizeRequestFileId as t };
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { extname, join, normalize, relative } from "pathe";
|
|
2
|
-
import { joinURL, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
|
|
3
|
-
//#region src/route-manifest.ts
|
|
4
|
-
const PAGES_DIR = "pages";
|
|
5
|
-
const PAGE_EXTENSIONS = new Set([".tsrx", ".mdx"]);
|
|
6
|
-
function buildRouteManifestFromFileIds(fileIds) {
|
|
7
|
-
const pages = unique(fileIds.map(normalizeRouteFileId)).filter(isPageModuleFile).toSorted((left, right) => left.localeCompare(right)).map(normalizePage);
|
|
8
|
-
const routes = pages.flatMap((page) => page.kind === "route" ? [page.route] : []);
|
|
9
|
-
assertNoRouteConflicts(routes);
|
|
10
|
-
const statusPages = {};
|
|
11
|
-
for (const page of pages) if (page.kind === "status") statusPages[page.status] = page.file;
|
|
12
|
-
return {
|
|
13
|
-
routes: routes.map(toPublicRoute).toSorted(compareRoutes),
|
|
14
|
-
statusPages
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function normalizeRequestPathname(pathname) {
|
|
18
|
-
const normalizedPathname = withoutTrailingSlash(withLeadingSlash(pathname));
|
|
19
|
-
return normalizedPathname === "" ? "/" : normalizedPathname;
|
|
20
|
-
}
|
|
21
|
-
function normalizeRouteFileId(fileId) {
|
|
22
|
-
return withoutLeadingSlash(normalize(fileId));
|
|
23
|
-
}
|
|
24
|
-
function matchRouteManifest(pathname, manifest) {
|
|
25
|
-
const requestPathname = normalizeRequestPathname(pathname);
|
|
26
|
-
for (const route of manifest.routes) {
|
|
27
|
-
const params = matchRoutePathname(route, requestPathname);
|
|
28
|
-
if (params) return {
|
|
29
|
-
route,
|
|
30
|
-
params
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function isPageModuleFile(file) {
|
|
35
|
-
const pageFile = pageRelativeFile(file);
|
|
36
|
-
return pageFile !== void 0 && PAGE_EXTENSIONS.has(extname(pageFile));
|
|
37
|
-
}
|
|
38
|
-
function normalizePage(file) {
|
|
39
|
-
const relativeFile = file;
|
|
40
|
-
const pageFile = pageRelativeFile(file);
|
|
41
|
-
const extension = extname(pageFile);
|
|
42
|
-
const withoutExtension = pageFile.slice(0, -extension.length);
|
|
43
|
-
if (withoutExtension === "api" || withoutExtension.startsWith("api/")) throw new Error(`API routes inside pages/ are not supported. Use top-level api/: ${relativeFile}`);
|
|
44
|
-
const routeFile = normalize(join(withoutExtension));
|
|
45
|
-
const rawSegments = routeFile === "." ? [] : routeFile.split("/");
|
|
46
|
-
if (rawSegments.length === 1 && rawSegments[0] === "404") return {
|
|
47
|
-
kind: "status",
|
|
48
|
-
status: "notFound",
|
|
49
|
-
file: relativeFile
|
|
50
|
-
};
|
|
51
|
-
if (rawSegments.length === 1 && rawSegments[0] === "500") return {
|
|
52
|
-
kind: "status",
|
|
53
|
-
status: "error",
|
|
54
|
-
file: relativeFile
|
|
55
|
-
};
|
|
56
|
-
if (rawSegments.length > 1 && ["404", "500"].includes(rawSegments.at(-1))) throw new Error(`Nested status pages are not supported in v0: ${relativeFile}`);
|
|
57
|
-
if (rawSegments.at(-1) === "index") rawSegments.pop();
|
|
58
|
-
const pattern = routePathname(rawSegments);
|
|
59
|
-
const segments = rawSegments.map((segment, index) => normalizeSegment(segment, index, rawSegments, relativeFile));
|
|
60
|
-
const pathnameSegments = segments.map((segment) => segment.pathname);
|
|
61
|
-
const identitySegments = segments.map((segment) => segment.identity);
|
|
62
|
-
const params = segments.flatMap((segment) => segment.params);
|
|
63
|
-
return {
|
|
64
|
-
kind: "route",
|
|
65
|
-
route: {
|
|
66
|
-
pathname: routePathname(pathnameSegments),
|
|
67
|
-
pattern,
|
|
68
|
-
identity: routePathname(identitySegments),
|
|
69
|
-
params,
|
|
70
|
-
file: relativeFile,
|
|
71
|
-
relativeFile
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function pageRelativeFile(file) {
|
|
76
|
-
if (file === PAGES_DIR || !file.startsWith(`${PAGES_DIR}/`)) return;
|
|
77
|
-
return relative(PAGES_DIR, file);
|
|
78
|
-
}
|
|
79
|
-
function normalizeSegment(segment, index, segments, relativeFile) {
|
|
80
|
-
const catchAll = segment.match(/^\[\.\.\.([A-Za-z_$][\w$]*)\]$/);
|
|
81
|
-
if (catchAll) {
|
|
82
|
-
if (index !== segments.length - 1) throw new Error(`Catch-all route segments must be final: ${relativeFile}`);
|
|
83
|
-
return {
|
|
84
|
-
pathname: "**",
|
|
85
|
-
identity: "**",
|
|
86
|
-
params: [{
|
|
87
|
-
name: catchAll[1],
|
|
88
|
-
kind: "catch-all"
|
|
89
|
-
}]
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
const dynamic = segment.match(/^\[([A-Za-z_$][\w$]*)\]$/);
|
|
93
|
-
if (dynamic) return {
|
|
94
|
-
pathname: `:${dynamic[1]}`,
|
|
95
|
-
identity: ":param",
|
|
96
|
-
params: [{
|
|
97
|
-
name: dynamic[1],
|
|
98
|
-
kind: "dynamic"
|
|
99
|
-
}]
|
|
100
|
-
};
|
|
101
|
-
if (segment.includes("[") || segment.includes("]")) throw new Error(`Unsupported route segment pattern: ${relativeFile}`);
|
|
102
|
-
return {
|
|
103
|
-
pathname: segment,
|
|
104
|
-
identity: segment,
|
|
105
|
-
params: []
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function assertNoRouteConflicts(routes) {
|
|
109
|
-
const routesByIdentity = /* @__PURE__ */ new Map();
|
|
110
|
-
for (const route of routes) {
|
|
111
|
-
const existingRoutes = routesByIdentity.get(route.identity) ?? [];
|
|
112
|
-
existingRoutes.push(route);
|
|
113
|
-
routesByIdentity.set(route.identity, existingRoutes);
|
|
114
|
-
}
|
|
115
|
-
for (const [identity, conflictingRoutes] of routesByIdentity) {
|
|
116
|
-
if (conflictingRoutes.length < 2) continue;
|
|
117
|
-
const files = conflictingRoutes.map((route) => route.relativeFile).toSorted((left, right) => left.localeCompare(right));
|
|
118
|
-
throw new Error([`Route conflict: ${identity} is defined by both:`, ...files.map((file) => `- ${file}`)].join("\n"));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function toPublicRoute(route) {
|
|
122
|
-
return {
|
|
123
|
-
pathname: route.pathname,
|
|
124
|
-
pattern: route.pattern,
|
|
125
|
-
params: route.params,
|
|
126
|
-
file: route.file
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function compareRoutes(left, right) {
|
|
130
|
-
const leftSegments = splitRoutePathname(left.pathname);
|
|
131
|
-
const rightSegments = splitRoutePathname(right.pathname);
|
|
132
|
-
const length = Math.max(leftSegments.length, rightSegments.length);
|
|
133
|
-
for (let index = 0; index < length; index += 1) {
|
|
134
|
-
const leftSegment = leftSegments[index];
|
|
135
|
-
const rightSegment = rightSegments[index];
|
|
136
|
-
if (leftSegment === void 0) return -1;
|
|
137
|
-
if (rightSegment === void 0) return 1;
|
|
138
|
-
const rankDifference = segmentRank(leftSegment) - segmentRank(rightSegment);
|
|
139
|
-
if (rankDifference !== 0) return rankDifference;
|
|
140
|
-
if (segmentRank(leftSegment) === 0 && leftSegment !== rightSegment) return leftSegment.localeCompare(rightSegment);
|
|
141
|
-
}
|
|
142
|
-
return left.pathname.localeCompare(right.pathname);
|
|
143
|
-
}
|
|
144
|
-
function splitRoutePathname(pathname) {
|
|
145
|
-
const route = withoutLeadingSlash(pathname);
|
|
146
|
-
return route === "" ? [] : route.split("/");
|
|
147
|
-
}
|
|
148
|
-
function matchRoutePathname(route, requestPathname) {
|
|
149
|
-
const routeSegments = splitRoutePathname(route.pathname);
|
|
150
|
-
const requestSegments = splitRoutePathname(requestPathname);
|
|
151
|
-
const catchAllIndex = routeSegments.indexOf("**");
|
|
152
|
-
if (catchAllIndex === -1 && routeSegments.length !== requestSegments.length) return;
|
|
153
|
-
if (catchAllIndex !== -1 && requestSegments.length <= catchAllIndex) return;
|
|
154
|
-
const params = {};
|
|
155
|
-
for (let index = 0; index < routeSegments.length; index += 1) {
|
|
156
|
-
const routeSegment = routeSegments[index];
|
|
157
|
-
const requestSegment = requestSegments[index];
|
|
158
|
-
if (routeSegment === "**") {
|
|
159
|
-
const catchAllParam = route.params.find((param) => param.kind === "catch-all");
|
|
160
|
-
if (!catchAllParam) return void 0;
|
|
161
|
-
params[catchAllParam.name] = joinURL(requestSegment, ...requestSegments.slice(index + 1));
|
|
162
|
-
return params;
|
|
163
|
-
}
|
|
164
|
-
if (routeSegment.startsWith(":")) {
|
|
165
|
-
params[routeSegment.slice(1)] = requestSegment;
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
if (routeSegment !== requestSegment) return;
|
|
169
|
-
}
|
|
170
|
-
return params;
|
|
171
|
-
}
|
|
172
|
-
function segmentRank(segment) {
|
|
173
|
-
if (segment === "**") return 2;
|
|
174
|
-
if (segment.startsWith(":")) return 1;
|
|
175
|
-
return 0;
|
|
176
|
-
}
|
|
177
|
-
function unique(values) {
|
|
178
|
-
return [...new Set(values)];
|
|
179
|
-
}
|
|
180
|
-
function routePathname(segments) {
|
|
181
|
-
if (segments.length === 0) return "/";
|
|
182
|
-
const [base, ...rest] = segments;
|
|
183
|
-
return withLeadingSlash(joinURL(base, ...rest));
|
|
184
|
-
}
|
|
185
|
-
//#endregion
|
|
186
|
-
export { normalizeRouteFileId as i, matchRouteManifest as n, normalizeRequestPathname as r, buildRouteManifestFromFileIds as t };
|