@troyio/vite-file-router 0.4.1 → 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/README.md +704 -536
- package/devtools/app.js +833 -51
- package/devtools/index.html +119 -76
- package/devtools/style.css +730 -156
- package/dist/devtools-app.d.ts +40 -2
- package/dist/devtools.d.ts +27 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +577 -43
- package/dist/scanner.d.ts +3 -1
- package/package.json +1 -1
- package/types/index.d.ts +3 -0
package/dist/index.js
CHANGED
|
@@ -11,7 +11,9 @@ var DEFAULT_CONVENTIONS = {
|
|
|
11
11
|
notFound: "not-found",
|
|
12
12
|
error: "error",
|
|
13
13
|
loader: "loader",
|
|
14
|
-
action: "action"
|
|
14
|
+
action: "action",
|
|
15
|
+
middleware: "middleware",
|
|
16
|
+
hydrateFallback: "hydrate-fallback"
|
|
15
17
|
};
|
|
16
18
|
function getRouteSegment(name) {
|
|
17
19
|
if (name.startsWith("[...") && name.endsWith("]")) return "*";
|
|
@@ -68,7 +70,7 @@ function scan(directory, conventions = DEFAULT_CONVENTIONS) {
|
|
|
68
70
|
const [, basename, extension] = match;
|
|
69
71
|
const kind = Object.keys(conventions).find((candidate) => conventions[candidate] === basename);
|
|
70
72
|
if (!kind) continue;
|
|
71
|
-
const isDataModule = kind === "loader" || kind === "action";
|
|
73
|
+
const isDataModule = kind === "loader" || kind === "action" || kind === "middleware";
|
|
72
74
|
if (!(isDataModule ? ["js", "ts"] : ["jsx", "tsx"]).includes(extension)) continue;
|
|
73
75
|
const preferredExtension = isDataModule ? "ts" : "tsx";
|
|
74
76
|
if (!node[kind] || extension === preferredExtension) node[kind] = file;
|
|
@@ -177,8 +179,9 @@ function generatePreload(files, projectRoot, children = []) {
|
|
|
177
179
|
/**
|
|
178
180
|
* Dev-only client for the DevTools Routes panel. It reports the tab's current
|
|
179
181
|
* URL and every loader, action, and `shouldRevalidate` call over Vite's HMR
|
|
180
|
-
* channel. Handler results pass through unchanged;
|
|
181
|
-
* of
|
|
182
|
+
* channel. Handler results pass through unchanged; the panel gets a capped
|
|
183
|
+
* copy of params, submitted form fields, returned data, response details, and
|
|
184
|
+
* errors. Production builds never include this code.
|
|
182
185
|
*/
|
|
183
186
|
var DEVTOOLS_CLIENT = `const devtoolsHot =
|
|
184
187
|
typeof window === "undefined" ? undefined : import.meta.hot;
|
|
@@ -190,33 +193,149 @@ function sendToDevtools(event, payload) {
|
|
|
190
193
|
} catch {}
|
|
191
194
|
}
|
|
192
195
|
|
|
196
|
+
const DEVTOOLS_DATA_LIMIT = 8000;
|
|
197
|
+
const DEVTOOLS_TEXT_LIMIT = 4000;
|
|
198
|
+
|
|
199
|
+
function clipText(text, limit) {
|
|
200
|
+
return text.length > limit ? text.slice(0, limit) + "…" : text;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Serializes loader and action results for the panel. Circular references,
|
|
204
|
+
// Maps, Sets, bigints, and functions don't break serialization.
|
|
205
|
+
function serializeRouteData(value, limit = DEVTOOLS_DATA_LIMIT) {
|
|
206
|
+
const stack = [];
|
|
207
|
+
let text;
|
|
208
|
+
try {
|
|
209
|
+
text = JSON.stringify(value, function (_key, item) {
|
|
210
|
+
if (stack.length > 0) {
|
|
211
|
+
const position = stack.indexOf(this);
|
|
212
|
+
if (position === -1) stack.push(this);
|
|
213
|
+
else stack.splice(position + 1);
|
|
214
|
+
if (item && typeof item === "object" && stack.includes(item)) {
|
|
215
|
+
return "[Circular]";
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
stack.push(item);
|
|
219
|
+
}
|
|
220
|
+
if (typeof item === "bigint") return item.toString() + "n";
|
|
221
|
+
if (typeof item === "function") {
|
|
222
|
+
return "[Function " + (item.name || "anonymous") + "]";
|
|
223
|
+
}
|
|
224
|
+
if (typeof item === "symbol") return item.toString();
|
|
225
|
+
if (item instanceof Map) {
|
|
226
|
+
return Object.fromEntries(
|
|
227
|
+
[...item].map(([key, entry]) => [String(key), entry]),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (item instanceof Set) return [...item];
|
|
231
|
+
return item;
|
|
232
|
+
});
|
|
233
|
+
} catch {}
|
|
234
|
+
if (text === undefined) text = String(value);
|
|
235
|
+
return text.length > limit
|
|
236
|
+
? { data: text.slice(0, limit), dataTruncated: true }
|
|
237
|
+
: { data: text };
|
|
238
|
+
}
|
|
239
|
+
|
|
193
240
|
function previewRouteData(value) {
|
|
241
|
+
return clipText(serializeRouteData(value, 601).data, 600);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function routeParams(params) {
|
|
245
|
+
if (!params || typeof params !== "object") return undefined;
|
|
246
|
+
const entries = Object.entries(params).filter(
|
|
247
|
+
([, value]) => typeof value === "string",
|
|
248
|
+
);
|
|
249
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function describeRequestBody(request) {
|
|
253
|
+
const type = request.headers.get("Content-Type") || "";
|
|
194
254
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
255
|
+
if (
|
|
256
|
+
type.includes("multipart/form-data") ||
|
|
257
|
+
type.includes("application/x-www-form-urlencoded")
|
|
258
|
+
) {
|
|
259
|
+
const form = await request.formData();
|
|
260
|
+
return {
|
|
261
|
+
kind: "form",
|
|
262
|
+
entries: [...form].slice(0, 50).map(([name, value]) => [
|
|
263
|
+
name,
|
|
264
|
+
typeof value === "string"
|
|
265
|
+
? clipText(value, 500)
|
|
266
|
+
: "File " + value.name + " (" + value.size + " bytes)",
|
|
267
|
+
]),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const text = await request.text();
|
|
271
|
+
return text
|
|
272
|
+
? { kind: "text", contentType: type, text: clipText(text, DEVTOOLS_TEXT_LIMIT) }
|
|
273
|
+
: undefined;
|
|
200
274
|
} catch {
|
|
201
|
-
return
|
|
275
|
+
return undefined;
|
|
202
276
|
}
|
|
203
277
|
}
|
|
204
278
|
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
279
|
+
function headerEntries(headers) {
|
|
280
|
+
try {
|
|
281
|
+
return [...new Headers(headers)].slice(0, 40);
|
|
282
|
+
} catch {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Clones a Response synchronously, before React Router reads its body.
|
|
288
|
+
function describeResponse(value, thrown) {
|
|
289
|
+
const location = value.headers.get("Location");
|
|
290
|
+
const base = { status: value.status, headers: headerEntries(value.headers) };
|
|
291
|
+
if (location && value.status >= 300 && value.status < 400) {
|
|
292
|
+
return { ...base, outcome: "redirect", location };
|
|
212
293
|
}
|
|
294
|
+
const outcome = thrown ? "error" : "response";
|
|
295
|
+
if (value.bodyUsed || !value.body) return { ...base, outcome };
|
|
296
|
+
const copy = value.clone();
|
|
297
|
+
return copy
|
|
298
|
+
.text()
|
|
299
|
+
.then((text) => ({
|
|
300
|
+
...base,
|
|
301
|
+
outcome,
|
|
302
|
+
...(text ? { responseBody: clipText(text, DEVTOOLS_TEXT_LIMIT) } : {}),
|
|
303
|
+
}))
|
|
304
|
+
.catch(() => ({ ...base, outcome }));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function describeRouteResult(value, thrown) {
|
|
308
|
+
if (value instanceof Response) return describeResponse(value, thrown);
|
|
213
309
|
if (thrown) {
|
|
310
|
+
if (value instanceof Error) {
|
|
311
|
+
return {
|
|
312
|
+
outcome: "error",
|
|
313
|
+
errorName: value.name,
|
|
314
|
+
message: value.message,
|
|
315
|
+
stack: value.stack ? clipText(value.stack, DEVTOOLS_TEXT_LIMIT) : undefined,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
214
318
|
return {
|
|
215
319
|
outcome: "error",
|
|
216
|
-
message:
|
|
320
|
+
message: previewRouteData(value),
|
|
321
|
+
...serializeRouteData(value),
|
|
217
322
|
};
|
|
218
323
|
}
|
|
219
|
-
|
|
324
|
+
// React Router's data() helper wraps data with a status and headers.
|
|
325
|
+
if (value && value.type === "DataWithResponseInit" && "data" in value) {
|
|
326
|
+
return {
|
|
327
|
+
outcome: "data",
|
|
328
|
+
status: value.init?.status,
|
|
329
|
+
headers: value.init?.headers ? headerEntries(value.init.headers) : undefined,
|
|
330
|
+
preview: previewRouteData(value.data),
|
|
331
|
+
...serializeRouteData(value.data),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
outcome: "data",
|
|
336
|
+
preview: previewRouteData(value),
|
|
337
|
+
...serializeRouteData(value),
|
|
338
|
+
};
|
|
220
339
|
}
|
|
221
340
|
|
|
222
341
|
function instrumentRouteData(type, routeId, handler) {
|
|
@@ -225,16 +344,30 @@ function instrumentRouteData(type, routeId, handler) {
|
|
|
225
344
|
const startedAt = Date.now();
|
|
226
345
|
const started = performance.now();
|
|
227
346
|
const request = args?.request;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
347
|
+
// Read the submitted body from a copy, so the handler can still read it.
|
|
348
|
+
const body =
|
|
349
|
+
request && request.body && request.method !== "GET" && request.method !== "HEAD"
|
|
350
|
+
? describeRequestBody(request.clone())
|
|
351
|
+
: undefined;
|
|
352
|
+
const report = (result, thrown) => {
|
|
353
|
+
const duration = performance.now() - started;
|
|
354
|
+
const described = describeRouteResult(result, thrown);
|
|
355
|
+
Promise.all([described, body])
|
|
356
|
+
.then(([details, requestBody]) =>
|
|
357
|
+
sendToDevtools("vite-file-router:data", {
|
|
358
|
+
type,
|
|
359
|
+
routeId,
|
|
360
|
+
url: request?.url,
|
|
361
|
+
method: request?.method,
|
|
362
|
+
params: routeParams(args?.params),
|
|
363
|
+
body: requestBody,
|
|
364
|
+
startedAt,
|
|
365
|
+
duration,
|
|
366
|
+
...details,
|
|
367
|
+
}),
|
|
368
|
+
)
|
|
369
|
+
.catch(() => {});
|
|
370
|
+
};
|
|
238
371
|
try {
|
|
239
372
|
const result = await handler(args);
|
|
240
373
|
report(result, false);
|
|
@@ -246,6 +379,55 @@ function instrumentRouteData(type, routeId, handler) {
|
|
|
246
379
|
};
|
|
247
380
|
}
|
|
248
381
|
|
|
382
|
+
// Reports the middleware's own time, not counting the handlers next() runs.
|
|
383
|
+
function instrumentMiddleware(routeId, middleware) {
|
|
384
|
+
if (!devtoolsHot) return middleware;
|
|
385
|
+
return async (args, next) => {
|
|
386
|
+
const startedAt = Date.now();
|
|
387
|
+
const started = performance.now();
|
|
388
|
+
let downstream = 0;
|
|
389
|
+
let calledNext = false;
|
|
390
|
+
const timedNext = async () => {
|
|
391
|
+
calledNext = true;
|
|
392
|
+
const nextStarted = performance.now();
|
|
393
|
+
try {
|
|
394
|
+
return await next();
|
|
395
|
+
} finally {
|
|
396
|
+
downstream += performance.now() - nextStarted;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const report = (details) => {
|
|
400
|
+
const duration = performance.now() - started - downstream;
|
|
401
|
+
Promise.resolve(details)
|
|
402
|
+
.then((resolved) =>
|
|
403
|
+
sendToDevtools("vite-file-router:data", {
|
|
404
|
+
type: "middleware",
|
|
405
|
+
routeId,
|
|
406
|
+
url: args?.request?.url,
|
|
407
|
+
method: args?.request?.method,
|
|
408
|
+
params: routeParams(args?.params),
|
|
409
|
+
startedAt,
|
|
410
|
+
duration,
|
|
411
|
+
...resolved,
|
|
412
|
+
}),
|
|
413
|
+
)
|
|
414
|
+
.catch(() => {});
|
|
415
|
+
};
|
|
416
|
+
try {
|
|
417
|
+
const result = await middleware(args, timedNext);
|
|
418
|
+
report(
|
|
419
|
+
result instanceof Response && !calledNext
|
|
420
|
+
? describeRouteResult(result, false)
|
|
421
|
+
: { outcome: "next" },
|
|
422
|
+
);
|
|
423
|
+
return result;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
report(describeRouteResult(error, true));
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
249
431
|
function instrumentRevalidate(routeId, shouldRevalidate) {
|
|
250
432
|
if (!devtoolsHot) return shouldRevalidate;
|
|
251
433
|
return (args) => {
|
|
@@ -254,6 +436,10 @@ function instrumentRevalidate(routeId, shouldRevalidate) {
|
|
|
254
436
|
type: "revalidate",
|
|
255
437
|
routeId,
|
|
256
438
|
url: args?.nextUrl?.href,
|
|
439
|
+
currentUrl: args?.currentUrl?.href,
|
|
440
|
+
params: routeParams(args?.nextParams),
|
|
441
|
+
formMethod: args?.formMethod,
|
|
442
|
+
actionStatus: args?.actionStatus,
|
|
257
443
|
startedAt: Date.now(),
|
|
258
444
|
decision,
|
|
259
445
|
defaultShouldRevalidate: args?.defaultShouldRevalidate,
|
|
@@ -290,6 +476,30 @@ if (devtoolsHot) {
|
|
|
290
476
|
for (const event of ["vite-file-router:navigate", "popstate", "hashchange"]) {
|
|
291
477
|
window.addEventListener(event, reportLocation);
|
|
292
478
|
}
|
|
479
|
+
// The panel's "Open in app" action. React Router's browser router follows
|
|
480
|
+
// popstate events, so pushing the URL and announcing it navigates without a
|
|
481
|
+
// reload. The state matches React Router's so Back and Forward keep working.
|
|
482
|
+
devtoolsHot.on("vite-file-router:navigate", (payload) => {
|
|
483
|
+
const url = payload?.url;
|
|
484
|
+
if (typeof url !== "string" || !url.startsWith("/") || url.startsWith("//")) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (window.location.hash.startsWith("#/")) {
|
|
488
|
+
window.location.hash = url;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const index = window.history.state?.idx;
|
|
492
|
+
window.history.pushState(
|
|
493
|
+
{
|
|
494
|
+
usr: null,
|
|
495
|
+
key: Math.random().toString(36).slice(2, 10),
|
|
496
|
+
idx: typeof index === "number" ? index + 1 : 0,
|
|
497
|
+
},
|
|
498
|
+
"",
|
|
499
|
+
url,
|
|
500
|
+
);
|
|
501
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
|
|
502
|
+
});
|
|
293
503
|
// Report again after the dev server restarts and the socket reconnects.
|
|
294
504
|
devtoolsHot.on("vite:ws:connect", () => {
|
|
295
505
|
reportedUrl = undefined;
|
|
@@ -311,6 +521,17 @@ function generateNode(node, root, context, inheritedBoundary) {
|
|
|
311
521
|
route.ErrorBoundary = `createErrorBoundary(${generateLazyComponent(node.error, context.projectRoot)})`;
|
|
312
522
|
}
|
|
313
523
|
if (node.action) route.action = generateDataHandler(node.action, context.projectRoot, getInstrumentId(node, context));
|
|
524
|
+
if (node.middleware) {
|
|
525
|
+
context.hasMiddleware = true;
|
|
526
|
+
const load = `lazyMiddleware(() => import(${JSON.stringify(getImportPath(node.middleware, context.projectRoot))}))`;
|
|
527
|
+
const instrumentId = getInstrumentId(node, context);
|
|
528
|
+
route.middleware = `[${instrumentId ? `instrumentMiddleware(${JSON.stringify(instrumentId)}, ${load})` : load}]`;
|
|
529
|
+
}
|
|
530
|
+
if (node.hydrateFallback) {
|
|
531
|
+
const fallback = genSafeVariableName(`HydrateFallback${context.nextLoadingId++}`);
|
|
532
|
+
context.loadingImports.push(`import ${fallback} from ${JSON.stringify(getImportPath(node.hydrateFallback, context.projectRoot))};`);
|
|
533
|
+
route.HydrateFallback = fallback;
|
|
534
|
+
}
|
|
314
535
|
let loadingComponent;
|
|
315
536
|
if (node.loading) {
|
|
316
537
|
loadingComponent = genSafeVariableName(`Loading${context.nextLoadingId++}`);
|
|
@@ -358,7 +579,11 @@ function generateNode(node, root, context, inheritedBoundary) {
|
|
|
358
579
|
if (children.length) route.children = genArrayFromRaw(children);
|
|
359
580
|
return {
|
|
360
581
|
route: genObjectFromRaw(route),
|
|
361
|
-
preload: generatePreload([
|
|
582
|
+
preload: generatePreload([
|
|
583
|
+
node.layout,
|
|
584
|
+
node.loader,
|
|
585
|
+
node.middleware
|
|
586
|
+
], context.projectRoot, preloadChildren)
|
|
362
587
|
};
|
|
363
588
|
}
|
|
364
589
|
function generate(node, root = false, projectRoot = process.cwd(), options = {}) {
|
|
@@ -372,6 +597,7 @@ function generate(node, root = false, projectRoot = process.cwd(), options = {})
|
|
|
372
597
|
hasBoundaries: false,
|
|
373
598
|
hasLayouts: false,
|
|
374
599
|
hasErrorBoundaries: false,
|
|
600
|
+
hasMiddleware: false,
|
|
375
601
|
devClient: Boolean(options.devClient)
|
|
376
602
|
};
|
|
377
603
|
const { route: routes, preload: preloads } = generateNode(node, root, context, false);
|
|
@@ -409,6 +635,31 @@ function generate(node, root = false, projectRoot = process.cwd(), options = {})
|
|
|
409
635
|
createElement(Component),
|
|
410
636
|
);
|
|
411
637
|
};
|
|
638
|
+
}`);
|
|
639
|
+
if (context.hasMiddleware) helpers.push(`/**
|
|
640
|
+
* Runs a middleware module's default or \`middleware\` export, which can be a
|
|
641
|
+
* function or an array of functions. Like React Router, it calls \`next()\`
|
|
642
|
+
* after a function that didn't call it.
|
|
643
|
+
*/
|
|
644
|
+
function lazyMiddleware(load) {
|
|
645
|
+
let pending;
|
|
646
|
+
return async (args, next) => {
|
|
647
|
+
pending ??= load();
|
|
648
|
+
const module = await pending;
|
|
649
|
+
const exported = module.middleware ?? module.default;
|
|
650
|
+
const list = (Array.isArray(exported) ? exported : [exported]).filter(
|
|
651
|
+
(item) => typeof item === "function",
|
|
652
|
+
);
|
|
653
|
+
const run = async (index) => {
|
|
654
|
+
if (index >= list.length) return next();
|
|
655
|
+
let downstream;
|
|
656
|
+
const callNext = () => (downstream ??= run(index + 1));
|
|
657
|
+
const result = await list[index](args, callNext);
|
|
658
|
+
const nextResult = await callNext();
|
|
659
|
+
return result === undefined ? nextResult : result;
|
|
660
|
+
};
|
|
661
|
+
return run(0);
|
|
662
|
+
};
|
|
412
663
|
}`);
|
|
413
664
|
if (context.devClient) helpers.push(DEVTOOLS_CLIENT);
|
|
414
665
|
return {
|
|
@@ -474,6 +725,80 @@ function createRouteDeclarations(tree, projectRoot, declarationFile) {
|
|
|
474
725
|
// src/devtools.ts
|
|
475
726
|
var ROUTE_INSPECTOR_PATH = "/__vite-file-router/";
|
|
476
727
|
var HEARTBEAT_INTERVAL = 25e3;
|
|
728
|
+
var EXPORT_PATTERNS = (name) => [
|
|
729
|
+
new RegExp(`export\\s+(?:async\\s+)?function\\s*\\*?\\s*${name}\\b`),
|
|
730
|
+
new RegExp(`export\\s+(?:const|let|var)\\s+${name}\\b`),
|
|
731
|
+
new RegExp(`export\\s*\\{[^}]*\\b${name}\\b[^}]*\\}`),
|
|
732
|
+
/export\s*\*\s*from/
|
|
733
|
+
];
|
|
734
|
+
/** Whether module source code (probably) has a named export. */
|
|
735
|
+
function hasNamedExport(source, name) {
|
|
736
|
+
return EXPORT_PATTERNS(name).some((pattern) => pattern.test(source));
|
|
737
|
+
}
|
|
738
|
+
var MISSING_EXPORT_MESSAGES = {
|
|
739
|
+
loader: `This loader file doesn't export a function named "loader", so the route loads no data.`,
|
|
740
|
+
action: `This action file doesn't export a function named "action", so the route fails when a form is submitted.`,
|
|
741
|
+
middleware: "This middleware file has no default or \"middleware\" export, so no middleware runs for the route."
|
|
742
|
+
};
|
|
743
|
+
var HANDLER_NAMES = {
|
|
744
|
+
loader: "a loader",
|
|
745
|
+
action: "an action",
|
|
746
|
+
middleware: "middleware"
|
|
747
|
+
};
|
|
748
|
+
function listHandlers(kinds) {
|
|
749
|
+
const names = kinds.map((kind) => HANDLER_NAMES[kind]);
|
|
750
|
+
return names.length < 2 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Finds routes that are missing a file they likely need: an error file for a
|
|
754
|
+
* loader, action, or middleware, a root not-found page, or the expected export
|
|
755
|
+
* of a data module. `readSource` reads a project-relative file.
|
|
756
|
+
*/
|
|
757
|
+
function findRouteWarnings(manifest, readSource = () => void 0) {
|
|
758
|
+
const warnings = [];
|
|
759
|
+
if (!manifest.files.notFound) warnings.push({
|
|
760
|
+
kind: "no-not-found",
|
|
761
|
+
severity: manifest.files.error ? "info" : "warning",
|
|
762
|
+
key: "0",
|
|
763
|
+
fullPath: "/",
|
|
764
|
+
message: manifest.files.error ? "There's no root not-found page, so unknown URLs render the root error file with a 404 error." : "There's no root not-found page or error file, so unknown URLs show React Router's default error screen.",
|
|
765
|
+
files: manifest.files.error ? [manifest.files.error] : []
|
|
766
|
+
});
|
|
767
|
+
const visit = (node, key, hasError) => {
|
|
768
|
+
const covered = hasError || Boolean(node.files.error);
|
|
769
|
+
const handlers = [
|
|
770
|
+
"loader",
|
|
771
|
+
"action",
|
|
772
|
+
"middleware"
|
|
773
|
+
].filter((kind) => node.files[kind]);
|
|
774
|
+
if (handlers.length && !covered) warnings.push({
|
|
775
|
+
kind: "no-error-boundary",
|
|
776
|
+
severity: "warning",
|
|
777
|
+
key,
|
|
778
|
+
fullPath: node.fullPath,
|
|
779
|
+
message: `This route has ${listHandlers(handlers)} but no error file at or above it, so errors it throws show React Router's default error screen.`,
|
|
780
|
+
files: handlers.map((kind) => node.files[kind])
|
|
781
|
+
});
|
|
782
|
+
for (const kind of handlers) {
|
|
783
|
+
const file = node.files[kind];
|
|
784
|
+
const source = readSource(file);
|
|
785
|
+
if (source === void 0) continue;
|
|
786
|
+
if (!(hasNamedExport(source, kind) || kind === "middleware" && /export\s+default\b/.test(source))) warnings.push({
|
|
787
|
+
kind: "missing-export",
|
|
788
|
+
severity: "warning",
|
|
789
|
+
key,
|
|
790
|
+
fullPath: node.fullPath,
|
|
791
|
+
message: MISSING_EXPORT_MESSAGES[kind],
|
|
792
|
+
files: [file]
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
node.children.forEach((child, index) => {
|
|
796
|
+
visit(child, `${key}.${index}`, covered);
|
|
797
|
+
});
|
|
798
|
+
};
|
|
799
|
+
visit(manifest, "0", false);
|
|
800
|
+
return warnings;
|
|
801
|
+
}
|
|
477
802
|
/**
|
|
478
803
|
* Mirrors the generated React Router tree: each manifest node becomes a
|
|
479
804
|
* route with an index child for its page and a `*` child for its not-found
|
|
@@ -524,7 +849,9 @@ async function matchRouteManifest(manifest, url) {
|
|
|
524
849
|
...leaf.kind === "notFound" ? { notFound: leaf.node?.files.notFound } : {},
|
|
525
850
|
layouts: collect("layout"),
|
|
526
851
|
loaders: collect("loader"),
|
|
852
|
+
middleware: collect("middleware"),
|
|
527
853
|
...nearest("loading") ? { loading: nearest("loading") } : {},
|
|
854
|
+
...nearest("hydrateFallback") ? { hydrateFallback: nearest("hydrateFallback") } : {},
|
|
528
855
|
...nearest("error") ? { error: nearest("error") } : {}
|
|
529
856
|
};
|
|
530
857
|
}
|
|
@@ -581,8 +908,19 @@ function createRouteEventStream(getState, getSnapshot = () => []) {
|
|
|
581
908
|
/** HMR events the dev-only client in `virtual:routes` sends to the server. */
|
|
582
909
|
var APP_LOCATION_EVENT = "vite-file-router:location";
|
|
583
910
|
var APP_DATA_EVENT = "vite-file-router:data";
|
|
911
|
+
/** Sent from the server to the app: navigate one tab to a URL. */
|
|
912
|
+
var APP_NAVIGATE_EVENT = "vite-file-router:navigate";
|
|
913
|
+
/**
|
|
914
|
+
* Checks a URL the panel asks an app tab to open. Only same-origin paths are
|
|
915
|
+
* allowed, so the panel can't send the app to another site.
|
|
916
|
+
*/
|
|
917
|
+
function isAppPath(url) {
|
|
918
|
+
return typeof url === "string" && url.length <= 2e3 && url.startsWith("/") && !url.startsWith("//") && !url.startsWith("/\\") && ![...url].some((character) => character.charCodeAt(0) < 32);
|
|
919
|
+
}
|
|
584
920
|
var MAX_ACTIVITY = 200;
|
|
585
921
|
var MAX_TEXT = 600;
|
|
922
|
+
var MAX_DATA = 8e3;
|
|
923
|
+
var MAX_DETAIL = 4e3;
|
|
586
924
|
function text(value, limit = MAX_TEXT) {
|
|
587
925
|
if (typeof value !== "string") return void 0;
|
|
588
926
|
return value.length > limit ? `${value.slice(0, limit)}…` : value;
|
|
@@ -590,6 +928,29 @@ function text(value, limit = MAX_TEXT) {
|
|
|
590
928
|
function finite(value) {
|
|
591
929
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
592
930
|
}
|
|
931
|
+
function pairs(value, max, limit) {
|
|
932
|
+
if (!Array.isArray(value)) return void 0;
|
|
933
|
+
return value.filter((entry) => Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "string").slice(0, max).map(([name, item]) => [text(name, 200), text(item, limit)]);
|
|
934
|
+
}
|
|
935
|
+
function stringRecord(value) {
|
|
936
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
937
|
+
const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string").slice(0, 30).map(([name, item]) => [text(name, 100), text(item, 1e3)]);
|
|
938
|
+
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
939
|
+
}
|
|
940
|
+
function requestBody(value) {
|
|
941
|
+
if (!value || typeof value !== "object") return void 0;
|
|
942
|
+
const body = value;
|
|
943
|
+
if (body.kind === "form") return {
|
|
944
|
+
kind: "form",
|
|
945
|
+
entries: pairs(body.entries, 50, 500) ?? []
|
|
946
|
+
};
|
|
947
|
+
const bodyText = text(body.text, MAX_DETAIL);
|
|
948
|
+
if (body.kind === "text" && bodyText !== void 0) return {
|
|
949
|
+
kind: "text",
|
|
950
|
+
contentType: text(body.contentType, 200),
|
|
951
|
+
text: bodyText
|
|
952
|
+
};
|
|
953
|
+
}
|
|
593
954
|
/**
|
|
594
955
|
* Validates an event from the browser. Anything a page sends over the HMR
|
|
595
956
|
* channel is untrusted, so only known fields of the expected types are kept.
|
|
@@ -598,7 +959,7 @@ function sanitizeDataEvent(input, id) {
|
|
|
598
959
|
if (!input || typeof input !== "object") return void 0;
|
|
599
960
|
const event = input;
|
|
600
961
|
const type = event.type;
|
|
601
|
-
if (type !== "loader" && type !== "action" && type !== "revalidate") return;
|
|
962
|
+
if (type !== "loader" && type !== "action" && type !== "middleware" && type !== "revalidate") return;
|
|
602
963
|
const routeId = text(event.routeId, 400);
|
|
603
964
|
const tabId = text(event.tabId, 100);
|
|
604
965
|
if (!routeId || !tabId) return void 0;
|
|
@@ -612,13 +973,24 @@ function sanitizeDataEvent(input, id) {
|
|
|
612
973
|
method: text(event.method, 20),
|
|
613
974
|
startedAt: finite(event.startedAt) ?? Date.now(),
|
|
614
975
|
duration: finite(event.duration),
|
|
615
|
-
...outcome === "data" || outcome === "response" || outcome === "redirect" || outcome === "error" ? { outcome } : {},
|
|
976
|
+
...outcome === "data" || outcome === "response" || outcome === "redirect" || outcome === "error" || outcome === "next" ? { outcome } : {},
|
|
616
977
|
status: finite(event.status),
|
|
617
978
|
location: text(event.location, 2e3),
|
|
618
979
|
message: text(event.message),
|
|
619
980
|
preview: text(event.preview),
|
|
620
981
|
...typeof event.decision === "boolean" ? { decision: event.decision } : {},
|
|
621
|
-
...typeof event.defaultShouldRevalidate === "boolean" ? { defaultShouldRevalidate: event.defaultShouldRevalidate } : {}
|
|
982
|
+
...typeof event.defaultShouldRevalidate === "boolean" ? { defaultShouldRevalidate: event.defaultShouldRevalidate } : {},
|
|
983
|
+
params: stringRecord(event.params),
|
|
984
|
+
body: requestBody(event.body),
|
|
985
|
+
data: text(event.data, MAX_DATA),
|
|
986
|
+
...event.dataTruncated === true ? { dataTruncated: true } : {},
|
|
987
|
+
headers: pairs(event.headers, 40, 1e3),
|
|
988
|
+
responseBody: text(event.responseBody, MAX_DETAIL),
|
|
989
|
+
errorName: text(event.errorName, 200),
|
|
990
|
+
stack: text(event.stack, MAX_DETAIL),
|
|
991
|
+
currentUrl: text(event.currentUrl, 2e3),
|
|
992
|
+
formMethod: text(event.formMethod, 20),
|
|
993
|
+
actionStatus: finite(event.actionStatus)
|
|
622
994
|
};
|
|
623
995
|
}
|
|
624
996
|
/**
|
|
@@ -657,6 +1029,10 @@ function createAppTracker(options) {
|
|
|
657
1029
|
});
|
|
658
1030
|
changed();
|
|
659
1031
|
},
|
|
1032
|
+
/** The HMR client for an app tab, to send it events such as navigation. */
|
|
1033
|
+
clientFor(tabId) {
|
|
1034
|
+
for (const [client, tab] of tabs) if (tab.id === tabId) return client;
|
|
1035
|
+
},
|
|
660
1036
|
disconnect(client) {
|
|
661
1037
|
latestReport.delete(client);
|
|
662
1038
|
if (tabs.delete(client)) changed();
|
|
@@ -688,6 +1064,11 @@ var MANIFEST_MODULE_ID = "virtual:route-manifest";
|
|
|
688
1064
|
var MANIFEST_RESOLVED_ID = `\0${MANIFEST_MODULE_ID}`;
|
|
689
1065
|
var ROUTE_HELPERS_MODULE_ID = "virtual:route-helpers";
|
|
690
1066
|
var ROUTE_HELPERS_RESOLVED_ID = `\0${ROUTE_HELPERS_MODULE_ID}`;
|
|
1067
|
+
var WARNING_KINDS = [
|
|
1068
|
+
"no-error-boundary",
|
|
1069
|
+
"no-not-found",
|
|
1070
|
+
"missing-export"
|
|
1071
|
+
];
|
|
691
1072
|
var DEFAULT_DTS = "src/route-types.d.ts";
|
|
692
1073
|
var TYPESCRIPT_CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
|
|
693
1074
|
function resolveDeclarationFile(dts, projectRoot) {
|
|
@@ -698,7 +1079,7 @@ function resolveDeclarationFile(dts, projectRoot) {
|
|
|
698
1079
|
function resolveOptions(input) {
|
|
699
1080
|
const options = typeof input === "string" ? { directory: input } : input;
|
|
700
1081
|
if (!options || typeof options !== "object" || Array.isArray(options)) throw new TypeError("routes() expects a directory string or options object.");
|
|
701
|
-
for (const key of Object.keys(options)) if (key !== "directory" && key !== "conventions" && key !== "dts") throw new TypeError(`Unknown routes() option "${key}".`);
|
|
1082
|
+
for (const key of Object.keys(options)) if (key !== "directory" && key !== "conventions" && key !== "dts" && key !== "warnings") throw new TypeError(`Unknown routes() option "${key}".`);
|
|
702
1083
|
const directory = options.directory ?? "src/pages";
|
|
703
1084
|
if (typeof directory !== "string" || directory.trim() === "") throw new TypeError("routes() directory must be a non-empty path string.");
|
|
704
1085
|
const conventions = { ...DEFAULT_CONVENTIONS };
|
|
@@ -715,21 +1096,80 @@ function resolveOptions(input) {
|
|
|
715
1096
|
return {
|
|
716
1097
|
directory,
|
|
717
1098
|
conventions,
|
|
718
|
-
dts
|
|
1099
|
+
dts,
|
|
1100
|
+
warnings: resolveWarnings(options.warnings)
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function resolveWarnings(input) {
|
|
1104
|
+
const message = "routes() warnings must be \"warn\", \"error\", false, or { level, ignore }.";
|
|
1105
|
+
if (input === void 0 || input === "warn") return {
|
|
1106
|
+
level: "warn",
|
|
1107
|
+
ignore: []
|
|
1108
|
+
};
|
|
1109
|
+
if (input === "error") return {
|
|
1110
|
+
level: "error",
|
|
1111
|
+
ignore: []
|
|
1112
|
+
};
|
|
1113
|
+
if (input === false) return false;
|
|
1114
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError(message);
|
|
1115
|
+
for (const key of Object.keys(input)) if (key !== "level" && key !== "ignore") throw new TypeError(message);
|
|
1116
|
+
const level = input.level ?? "warn";
|
|
1117
|
+
if (level !== "warn" && level !== "error") throw new TypeError(message);
|
|
1118
|
+
const ignore = input.ignore ?? [];
|
|
1119
|
+
if (!Array.isArray(ignore) || ignore.some((kind) => !WARNING_KINDS.includes(kind))) throw new TypeError(`routes() warnings.ignore must list warning kinds: ${WARNING_KINDS.join(", ")}.`);
|
|
1120
|
+
return {
|
|
1121
|
+
level,
|
|
1122
|
+
ignore
|
|
719
1123
|
};
|
|
720
1124
|
}
|
|
1125
|
+
/** Formats route warnings for the terminal, grouped by route. */
|
|
1126
|
+
function formatRouteWarnings(warnings) {
|
|
1127
|
+
const byRoute = /* @__PURE__ */ new Map();
|
|
1128
|
+
for (const warning of warnings) byRoute.set(warning.fullPath, [...byRoute.get(warning.fullPath) ?? [], warning]);
|
|
1129
|
+
const lines = [`vite-file-router found ${warnings.length} route warning${warnings.length === 1 ? "" : "s"}:`];
|
|
1130
|
+
for (const [fullPath, list] of byRoute) {
|
|
1131
|
+
lines.push("", ` ${fullPath}`);
|
|
1132
|
+
for (const warning of list) {
|
|
1133
|
+
lines.push(` - ${warning.message} [${warning.kind}]`);
|
|
1134
|
+
for (const file of warning.files) lines.push(` ${file}`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
lines.push("", "Ignore a kind with routes({ warnings: { ignore: [...] } }), or turn these off with routes({ warnings: false }).");
|
|
1138
|
+
return lines.join("\n");
|
|
1139
|
+
}
|
|
721
1140
|
function routes(input = "src/pages") {
|
|
722
1141
|
const options = resolveOptions(input);
|
|
723
1142
|
let projectRoot = "";
|
|
724
1143
|
let root = "";
|
|
725
1144
|
let dev = false;
|
|
726
1145
|
let declarationFile;
|
|
1146
|
+
function readSource(file) {
|
|
1147
|
+
try {
|
|
1148
|
+
return fs.readFileSync(path.resolve(projectRoot, file), "utf8");
|
|
1149
|
+
} catch {
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
let reportedWarnings;
|
|
1154
|
+
function reportBuildWarnings(context) {
|
|
1155
|
+
if (dev || !options.warnings) return;
|
|
1156
|
+
const { level, ignore } = options.warnings;
|
|
1157
|
+
const warnings = findRouteWarnings(createRouteManifest(scan(root, options.conventions), projectRoot), readSource).filter((warning) => warning.severity === "warning" && !ignore.includes(warning.kind));
|
|
1158
|
+
if (!warnings.length) return;
|
|
1159
|
+
const message = formatRouteWarnings(warnings);
|
|
1160
|
+
if (level === "error") context.error(message);
|
|
1161
|
+
if (message === reportedWarnings) return;
|
|
1162
|
+
reportedWarnings = message;
|
|
1163
|
+
context.warn(message);
|
|
1164
|
+
}
|
|
727
1165
|
function getInspectorState() {
|
|
728
1166
|
const tree = scan(root, options.conventions);
|
|
1167
|
+
const manifest = createRouteManifest(tree, projectRoot);
|
|
729
1168
|
return {
|
|
730
1169
|
projectRoot,
|
|
731
1170
|
directory: path.relative(projectRoot, root).split(path.sep).join("/"),
|
|
732
|
-
manifest
|
|
1171
|
+
manifest,
|
|
1172
|
+
warnings: findRouteWarnings(manifest, readSource),
|
|
733
1173
|
conflicts: findPageRouteConflicts(tree).map(({ routePath, files }) => ({
|
|
734
1174
|
routePath,
|
|
735
1175
|
files: files.map((file) => path.relative(projectRoot, file).split(path.sep).join("/"))
|
|
@@ -742,6 +1182,44 @@ function routes(input = "src/pages") {
|
|
|
742
1182
|
onActivity: (event) => routeEvents.broadcast("activity", event)
|
|
743
1183
|
});
|
|
744
1184
|
const routeEvents = createRouteEventStream(getInspectorState, () => [["apps", appTracker.tabs()], ["activity-snapshot", appTracker.activity()]]);
|
|
1185
|
+
/**
|
|
1186
|
+
* Every module the plugin generates, for the DevTools Generated code page.
|
|
1187
|
+
* A module that fails to generate (for example on a route conflict)
|
|
1188
|
+
* reports its error instead of failing the others.
|
|
1189
|
+
*/
|
|
1190
|
+
async function getGeneratedCode() {
|
|
1191
|
+
const capture = async (module, create) => {
|
|
1192
|
+
try {
|
|
1193
|
+
return {
|
|
1194
|
+
...module,
|
|
1195
|
+
code: await create()
|
|
1196
|
+
};
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
return {
|
|
1199
|
+
...module,
|
|
1200
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
const typesFile = declarationFile ?? path.resolve(projectRoot, DEFAULT_DTS);
|
|
1205
|
+
return Promise.all([
|
|
1206
|
+
capture({
|
|
1207
|
+
id: ROUTES_MODULE_ID,
|
|
1208
|
+
variant: "dev"
|
|
1209
|
+
}, () => execute(true)),
|
|
1210
|
+
capture({
|
|
1211
|
+
id: ROUTES_MODULE_ID,
|
|
1212
|
+
variant: "build"
|
|
1213
|
+
}, () => execute(false)),
|
|
1214
|
+
capture({ id: MANIFEST_MODULE_ID }, executeManifest),
|
|
1215
|
+
capture({ id: ROUTE_HELPERS_MODULE_ID }, executeRouteHelpers),
|
|
1216
|
+
capture({
|
|
1217
|
+
id: "route-types",
|
|
1218
|
+
file: path.relative(projectRoot, typesFile).split(path.sep).join("/"),
|
|
1219
|
+
written: Boolean(declarationFile)
|
|
1220
|
+
}, () => createRouteDeclarations(scan(root, options.conventions), projectRoot, typesFile))
|
|
1221
|
+
]);
|
|
1222
|
+
}
|
|
745
1223
|
function writeDeclarations() {
|
|
746
1224
|
if (!declarationFile) return;
|
|
747
1225
|
const content = createRouteDeclarations(scan(root, options.conventions), projectRoot, declarationFile);
|
|
@@ -758,12 +1236,13 @@ function routes(input = "src/pages") {
|
|
|
758
1236
|
return leftSource.localeCompare(rightSource);
|
|
759
1237
|
});
|
|
760
1238
|
}
|
|
761
|
-
|
|
1239
|
+
/** `devClient` adds the DevTools client, which only the dev server serves. */
|
|
1240
|
+
async function execute(devClient = dev) {
|
|
762
1241
|
const tree = scan(root, options.conventions);
|
|
763
1242
|
const conflicts = findPageRouteConflicts(tree);
|
|
764
1243
|
if (conflicts.length) {
|
|
765
1244
|
const error = new RouteConflictError(conflicts);
|
|
766
|
-
if (!
|
|
1245
|
+
if (!devClient) throw error;
|
|
767
1246
|
const code = `
|
|
768
1247
|
import { createElement } from "react";
|
|
769
1248
|
|
|
@@ -787,7 +1266,7 @@ function routes(input = "src/pages") {
|
|
|
787
1266
|
`;
|
|
788
1267
|
return format(code, { parser: "typescript" });
|
|
789
1268
|
}
|
|
790
|
-
const generated = generate(tree, true, projectRoot, { devClient
|
|
1269
|
+
const generated = generate(tree, true, projectRoot, { devClient });
|
|
791
1270
|
const sections = [
|
|
792
1271
|
sortImports(generated.imports).join("\n"),
|
|
793
1272
|
generated.helpers.join("\n"),
|
|
@@ -925,6 +1404,13 @@ function routes(input = "src/pages") {
|
|
|
925
1404
|
},
|
|
926
1405
|
buildStart() {
|
|
927
1406
|
writeDeclarations();
|
|
1407
|
+
reportBuildWarnings({
|
|
1408
|
+
warn: (message) => typeof this?.warn === "function" ? this.warn(message) : console.warn(message),
|
|
1409
|
+
error: (message) => {
|
|
1410
|
+
if (typeof this?.error === "function") this.error(message);
|
|
1411
|
+
throw new Error(message);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
928
1414
|
},
|
|
929
1415
|
resolveId: (id) => {
|
|
930
1416
|
if (id === ROUTES_MODULE_ID) return ROUTES_RESOLVED_ID;
|
|
@@ -946,6 +1432,43 @@ function routes(input = "src/pages") {
|
|
|
946
1432
|
type: "iframe",
|
|
947
1433
|
url: ROUTE_INSPECTOR_PATH
|
|
948
1434
|
});
|
|
1435
|
+
const fail = (res, status, message) => {
|
|
1436
|
+
res.statusCode = status;
|
|
1437
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
1438
|
+
res.end(message);
|
|
1439
|
+
};
|
|
1440
|
+
const navigateApp = (req, res) => {
|
|
1441
|
+
if (!req.headers["content-type"]?.startsWith("application/json")) {
|
|
1442
|
+
fail(res, 415, "Expected a JSON body.");
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
let body = "";
|
|
1446
|
+
req.setEncoding?.("utf8");
|
|
1447
|
+
req.on("data", (chunk) => {
|
|
1448
|
+
body += chunk;
|
|
1449
|
+
if (body.length > 8192) req.destroy();
|
|
1450
|
+
});
|
|
1451
|
+
req.on("end", () => {
|
|
1452
|
+
let payload;
|
|
1453
|
+
try {
|
|
1454
|
+
payload = JSON.parse(body);
|
|
1455
|
+
} catch {
|
|
1456
|
+
fail(res, 400, "Invalid JSON.");
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
if (!isAppPath(payload?.url)) {
|
|
1460
|
+
fail(res, 400, "The \"url\" must be a path starting with \"/\".");
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
const client = appTracker.clientFor(String(payload.tabId));
|
|
1464
|
+
if (!client) {
|
|
1465
|
+
fail(res, 404, "That app tab is no longer connected.");
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
client.send(APP_NAVIGATE_EVENT, { url: payload.url });
|
|
1469
|
+
sendJson(res, { sent: true });
|
|
1470
|
+
});
|
|
1471
|
+
};
|
|
949
1472
|
const sendJson = (res, body) => {
|
|
950
1473
|
res.statusCode = 200;
|
|
951
1474
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
@@ -957,17 +1480,28 @@ function routes(input = "src/pages") {
|
|
|
957
1480
|
if (![
|
|
958
1481
|
"/__vite-file-router-data",
|
|
959
1482
|
"/__vite-file-router-events",
|
|
960
|
-
"/__vite-file-router-match"
|
|
1483
|
+
"/__vite-file-router-match",
|
|
1484
|
+
"/__vite-file-router-navigate",
|
|
1485
|
+
"/__vite-file-router-code"
|
|
961
1486
|
].includes(url.pathname)) {
|
|
962
1487
|
next();
|
|
963
1488
|
return;
|
|
964
1489
|
}
|
|
965
|
-
|
|
1490
|
+
const method = url.pathname === "/__vite-file-router-navigate" ? "POST" : "GET";
|
|
1491
|
+
if (req.method !== method) {
|
|
966
1492
|
res.statusCode = 405;
|
|
967
|
-
res.setHeader("Allow",
|
|
1493
|
+
res.setHeader("Allow", method);
|
|
968
1494
|
res.end("Method Not Allowed");
|
|
969
1495
|
return;
|
|
970
1496
|
}
|
|
1497
|
+
if (url.pathname === "/__vite-file-router-code") {
|
|
1498
|
+
getGeneratedCode().then((modules) => sendJson(res, { modules }), next);
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
if (url.pathname === "/__vite-file-router-navigate") {
|
|
1502
|
+
navigateApp(req, res);
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
971
1505
|
if (url.pathname === "/__vite-file-router-data") {
|
|
972
1506
|
sendJson(res, getInspectorState());
|
|
973
1507
|
return;
|
|
@@ -1020,4 +1554,4 @@ function routes(input = "src/pages") {
|
|
|
1020
1554
|
}
|
|
1021
1555
|
};
|
|
1022
1556
|
}
|
|
1023
|
-
export { routes };
|
|
1557
|
+
export { formatRouteWarnings, routes };
|