@kimesh/router-generator 0.2.23 → 0.2.24
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/index.d.mts +47 -16
- package/dist/index.mjs +401 -292
- package/package.json +7 -1
package/dist/index.d.mts
CHANGED
|
@@ -95,6 +95,12 @@ interface ParsedRoute {
|
|
|
95
95
|
isLayout: boolean;
|
|
96
96
|
/** Raw route script content */
|
|
97
97
|
routeScriptContent: string | null;
|
|
98
|
+
/** beforeLoad function exists */
|
|
99
|
+
hasBeforeLoad: boolean;
|
|
100
|
+
/** pendingComponent is defined */
|
|
101
|
+
hasPendingComponent: boolean;
|
|
102
|
+
/** head configuration exists */
|
|
103
|
+
hasHead: boolean;
|
|
98
104
|
}
|
|
99
105
|
/**
|
|
100
106
|
* Generated routes output
|
|
@@ -164,21 +170,6 @@ declare class RouteScanner {
|
|
|
164
170
|
* Process a single route file
|
|
165
171
|
*/
|
|
166
172
|
private processFile;
|
|
167
|
-
/**
|
|
168
|
-
* Parse filename to determine route type and path
|
|
169
|
-
*
|
|
170
|
-
* Kimesh file-based routing conventions:
|
|
171
|
-
* - __root.vue → root layout (wraps everything)
|
|
172
|
-
* - index.vue → index route
|
|
173
|
-
* - about.vue → regular page (/about)
|
|
174
|
-
* - $id.vue → dynamic param (/users/:id)
|
|
175
|
-
* - $.vue → catch-all (splat route)
|
|
176
|
-
* - _auth.vue → pathless layout (underscore prefix, no URL segment)
|
|
177
|
-
* - posts.vue + posts/ → layout route (file + matching directory)
|
|
178
|
-
* - posts.index.vue → flat route notation (dot separator)
|
|
179
|
-
*/
|
|
180
|
-
private parseFileName;
|
|
181
|
-
private parseDotNotationRoute;
|
|
182
173
|
/**
|
|
183
174
|
* Generate a valid JavaScript variable name from file path
|
|
184
175
|
*/
|
|
@@ -230,6 +221,22 @@ interface ExtendedParsedRoute extends ParsedRoute {
|
|
|
230
221
|
keepAlive?: boolean;
|
|
231
222
|
meta?: Record<string, unknown>;
|
|
232
223
|
};
|
|
224
|
+
/** beforeLoad function options */
|
|
225
|
+
beforeLoadOptions?: {
|
|
226
|
+
isAsync: boolean;
|
|
227
|
+
hasContextParam: boolean;
|
|
228
|
+
};
|
|
229
|
+
/** pendingComponent configuration */
|
|
230
|
+
pendingOptions?: {
|
|
231
|
+
hasPendingComponent: boolean;
|
|
232
|
+
pendingMs?: number;
|
|
233
|
+
pendingMinMs?: number;
|
|
234
|
+
};
|
|
235
|
+
/** head configuration type */
|
|
236
|
+
headOptions?: {
|
|
237
|
+
isFunction: boolean;
|
|
238
|
+
isStatic: boolean;
|
|
239
|
+
};
|
|
233
240
|
}
|
|
234
241
|
/**
|
|
235
242
|
* Parse a Vue SFC file to extract route definition information using OXC
|
|
@@ -370,6 +377,30 @@ declare function createCatchAllParam(): RouteParam;
|
|
|
370
377
|
* Build a glob pattern for route file scanning
|
|
371
378
|
*/
|
|
372
379
|
declare function buildGlobPattern(extensions: string[]): string;
|
|
380
|
+
/**
|
|
381
|
+
* Check if a route is a pathless layout (no URL segment contribution)
|
|
382
|
+
* Pathless layouts have routePath '' or '/' and rawSegment starting with '_'
|
|
383
|
+
*/
|
|
384
|
+
declare function isPathlessLayout(route: RouteNode): boolean;
|
|
385
|
+
/**
|
|
386
|
+
* Parse filename to determine route type and path
|
|
387
|
+
*
|
|
388
|
+
* File-based routing conventions:
|
|
389
|
+
* - __root.vue -> root layout (wraps all routes)
|
|
390
|
+
* - index.vue -> index route
|
|
391
|
+
* - route.vue -> alternative index route
|
|
392
|
+
* - about.vue -> regular page
|
|
393
|
+
* - $id.vue -> dynamic param
|
|
394
|
+
* - $.vue -> catch-all (splat route)
|
|
395
|
+
* - (group)/ -> pathless group folder (route group)
|
|
396
|
+
* - _pathless.vue + _pathless/ -> pathless layout (underscore prefix)
|
|
397
|
+
* - posts.vue + posts/ -> layout route
|
|
398
|
+
* - posts.index.vue -> flat route notation (dot separator)
|
|
399
|
+
* - posts_/ -> layout escape (underscore suffix)
|
|
400
|
+
* - -utils.vue -> excluded from routing (dash prefix)
|
|
401
|
+
* - [x] -> escape special characters (e.g., script[.]js.vue -> /script.js)
|
|
402
|
+
*/
|
|
403
|
+
declare function parseFileName(fileName: string, dirPath: string, hasMatchingDirectory?: boolean): ParsedFileName;
|
|
373
404
|
//#endregion
|
|
374
405
|
//#region src/layer-collector.d.ts
|
|
375
406
|
/**
|
|
@@ -619,4 +650,4 @@ declare function generateMiddlewareTypes(result: MiddlewareScanResult): string;
|
|
|
619
650
|
*/
|
|
620
651
|
declare function generateMiddlewareModule(file: MiddlewareFile): string;
|
|
621
652
|
//#endregion
|
|
622
|
-
export { type ExtendedParsedRoute, type GeneratedOutput, type KimeshRouterConfig, type KimeshRouterPluginOptions, type LayerMiddlewareConfig, type LayerRouteConfig, type LayerRouteNode, type LayerRouteSource, type LayerRoutesResult, type LayoutChain, LayoutResolver, type MiddlewareFile, type MiddlewareScanOptions, type MiddlewareScanResult, MiddlewareScanner, type ParsedFileName, type ParsedRoute, type ResolvedLayout, type ResolvedRouterConfig, type RouteNode, type RouteNodeType, type RouteParam, RouteScanner, RouteTreeBuilder, type TreeNode, buildGlobPattern, buildRoutePath, buildRouteTree, collectLayerRoutes, createCatchAllParam, createDynamicParam, createEmptyParsedRoute, createScanner, createTreeBuilder, extractDynamicParam, generateLayoutStructure, generateMiddlewareModule, generateMiddlewareRegistry, generateMiddlewareTypes, generateRouteTypes, generateRoutes, isDotNotationRoute, isRootDir, kimeshRouterGenerator, mergeRoutes, parseRouteFile, processDirectoryPath, processPathSegment, scanLayerMiddleware, scanMiddlewareDir, scanMiddlewareFiles, unescapeBrackets };
|
|
653
|
+
export { type ExtendedParsedRoute, type GeneratedOutput, type KimeshRouterConfig, type KimeshRouterPluginOptions, type LayerMiddlewareConfig, type LayerRouteConfig, type LayerRouteNode, type LayerRouteSource, type LayerRoutesResult, type LayoutChain, LayoutResolver, type MiddlewareFile, type MiddlewareScanOptions, type MiddlewareScanResult, MiddlewareScanner, type ParsedFileName, type ParsedRoute, type ResolvedLayout, type ResolvedRouterConfig, type RouteNode, type RouteNodeType, type RouteParam, RouteScanner, RouteTreeBuilder, type TreeNode, buildGlobPattern, buildRoutePath, buildRouteTree, collectLayerRoutes, createCatchAllParam, createDynamicParam, createEmptyParsedRoute, createScanner, createTreeBuilder, extractDynamicParam, generateLayoutStructure, generateMiddlewareModule, generateMiddlewareRegistry, generateMiddlewareTypes, generateRouteTypes, generateRoutes, isDotNotationRoute, isPathlessLayout, isRootDir, kimeshRouterGenerator, mergeRoutes, parseFileName, parseRouteFile, processDirectoryPath, processPathSegment, scanLayerMiddleware, scanMiddlewareDir, scanMiddlewareFiles, unescapeBrackets };
|
package/dist/index.mjs
CHANGED
|
@@ -1,50 +1,15 @@
|
|
|
1
|
-
import * as path from "pathe";
|
|
1
|
+
import * as path$1 from "pathe";
|
|
2
2
|
import { basename, extname, join } from "pathe";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { watch } from "chokidar";
|
|
6
6
|
import fg from "fast-glob";
|
|
7
|
+
import path from "path";
|
|
7
8
|
import { parse } from "@vue/compiler-sfc";
|
|
8
9
|
import { parseSync } from "oxc-parser";
|
|
9
10
|
import { consola } from "consola";
|
|
10
11
|
import { readdir } from "node:fs/promises";
|
|
11
12
|
|
|
12
|
-
//#region rolldown:runtime
|
|
13
|
-
var __defProp = Object.defineProperty;
|
|
14
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
15
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
17
|
-
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
-
var __exportAll = (all, symbols) => {
|
|
19
|
-
let target = {};
|
|
20
|
-
for (var name in all) {
|
|
21
|
-
__defProp(target, name, {
|
|
22
|
-
get: all[name],
|
|
23
|
-
enumerable: true
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
if (symbols) {
|
|
27
|
-
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
28
|
-
}
|
|
29
|
-
return target;
|
|
30
|
-
};
|
|
31
|
-
var __copyProps = (to, from, except, desc) => {
|
|
32
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
33
|
-
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
34
|
-
key = keys[i];
|
|
35
|
-
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
36
|
-
__defProp(to, key, {
|
|
37
|
-
get: ((k) => from[k]).bind(null, key),
|
|
38
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return to;
|
|
44
|
-
};
|
|
45
|
-
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
46
|
-
|
|
47
|
-
//#endregion
|
|
48
13
|
//#region src/parser.ts
|
|
49
14
|
/**
|
|
50
15
|
* @kimesh/router-generator - OXC-Powered Route Parser
|
|
@@ -63,7 +28,7 @@ function parseRouteFile(code, filePath) {
|
|
|
63
28
|
logger$2.debug(`SFC parse errors in ${filePath}:`, errors);
|
|
64
29
|
return createEmptyParsedRoute(filePath);
|
|
65
30
|
}
|
|
66
|
-
const isLayout = filePath.includes("_layout") || filePath.includes("layout.vue");
|
|
31
|
+
const isLayout = filePath.includes("_layout") || filePath.includes("layout.vue") || isLayoutFilename(filePath);
|
|
67
32
|
const scriptContent = descriptor.script?.content || "";
|
|
68
33
|
const result = parseWithOxc(scriptContent, descriptor.scriptSetup?.content || "", filePath);
|
|
69
34
|
return {
|
|
@@ -74,9 +39,15 @@ function parseRouteFile(code, filePath) {
|
|
|
74
39
|
hasValidateSearch: result.hasValidateSearch ?? false,
|
|
75
40
|
isLayout,
|
|
76
41
|
routeScriptContent: result.hasRouteDefinition ? scriptContent : null,
|
|
42
|
+
hasBeforeLoad: result.hasBeforeLoad ?? false,
|
|
43
|
+
hasPendingComponent: result.hasPendingComponent ?? false,
|
|
44
|
+
hasHead: result.hasHead ?? false,
|
|
77
45
|
loaderOptions: result.loaderOptions,
|
|
78
46
|
middleware: result.middleware,
|
|
79
|
-
pageMeta: result.pageMeta
|
|
47
|
+
pageMeta: result.pageMeta,
|
|
48
|
+
beforeLoadOptions: result.beforeLoadOptions,
|
|
49
|
+
pendingOptions: result.pendingOptions,
|
|
50
|
+
headOptions: result.headOptions
|
|
80
51
|
};
|
|
81
52
|
} catch (error) {
|
|
82
53
|
logger$2.debug(`Failed to parse ${filePath}:`, error);
|
|
@@ -92,7 +63,10 @@ function parseWithOxc(scriptContent, scriptSetupContent, filePath) {
|
|
|
92
63
|
routePath: null,
|
|
93
64
|
hasLoader: false,
|
|
94
65
|
hasMeta: false,
|
|
95
|
-
hasValidateSearch: false
|
|
66
|
+
hasValidateSearch: false,
|
|
67
|
+
hasBeforeLoad: false,
|
|
68
|
+
hasPendingComponent: false,
|
|
69
|
+
hasHead: false
|
|
96
70
|
};
|
|
97
71
|
if (scriptContent) try {
|
|
98
72
|
const parsed = parseSync(filePath + ".ts", scriptContent, { sourceType: "module" });
|
|
@@ -136,6 +110,24 @@ function extractFromAST(program, result) {
|
|
|
136
110
|
case "validateSearch":
|
|
137
111
|
result.hasValidateSearch = true;
|
|
138
112
|
break;
|
|
113
|
+
case "beforeLoad":
|
|
114
|
+
result.hasBeforeLoad = true;
|
|
115
|
+
result.beforeLoadOptions = extractBeforeLoadOptions(prop.value);
|
|
116
|
+
break;
|
|
117
|
+
case "pendingComponent":
|
|
118
|
+
result.hasPendingComponent = true;
|
|
119
|
+
extractPendingOptions(routeOptions, result);
|
|
120
|
+
break;
|
|
121
|
+
case "pendingMs":
|
|
122
|
+
case "pendingMinMs":
|
|
123
|
+
if (!result.pendingOptions) result.pendingOptions = { hasPendingComponent: false };
|
|
124
|
+
if (prop.key.name === "pendingMs" && prop.value?.type === "Literal") result.pendingOptions.pendingMs = prop.value.value;
|
|
125
|
+
if (prop.key.name === "pendingMinMs" && prop.value?.type === "Literal") result.pendingOptions.pendingMinMs = prop.value.value;
|
|
126
|
+
break;
|
|
127
|
+
case "head":
|
|
128
|
+
result.hasHead = true;
|
|
129
|
+
result.headOptions = extractHeadOptions(prop.value);
|
|
130
|
+
break;
|
|
139
131
|
}
|
|
140
132
|
}
|
|
141
133
|
}
|
|
@@ -381,6 +373,54 @@ function extractIdentifierValue(node) {
|
|
|
381
373
|
return NOT_SERIALIZABLE;
|
|
382
374
|
}
|
|
383
375
|
/**
|
|
376
|
+
* Check if filename indicates a layout route
|
|
377
|
+
*/
|
|
378
|
+
function isLayoutFilename(filePath) {
|
|
379
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
380
|
+
return fileName === "__root" || fileName.startsWith("_") && !fileName.startsWith("__");
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Extract beforeLoad options
|
|
384
|
+
*/
|
|
385
|
+
function extractBeforeLoadOptions(value) {
|
|
386
|
+
const options = {
|
|
387
|
+
isAsync: false,
|
|
388
|
+
hasContextParam: false
|
|
389
|
+
};
|
|
390
|
+
if (isFunctionExpression(value)) {
|
|
391
|
+
options.isAsync = value.async === true;
|
|
392
|
+
const firstParam = value.params?.[0];
|
|
393
|
+
if (firstParam?.type === "ObjectPattern") {
|
|
394
|
+
for (const prop of firstParam.properties || []) if (prop.type === "Property" && prop.key?.name === "context") {
|
|
395
|
+
options.hasContextParam = true;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return options;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Extract pending options from route config
|
|
404
|
+
*/
|
|
405
|
+
function extractPendingOptions(routeOptions, result) {
|
|
406
|
+
if (!result.pendingOptions) result.pendingOptions = { hasPendingComponent: false };
|
|
407
|
+
result.pendingOptions.hasPendingComponent = true;
|
|
408
|
+
for (const prop of routeOptions.properties || []) {
|
|
409
|
+
if (prop.type !== "Property" || prop.key?.type !== "Identifier") continue;
|
|
410
|
+
if (prop.key.name === "pendingMs" && prop.value?.type === "Literal") result.pendingOptions.pendingMs = prop.value.value;
|
|
411
|
+
if (prop.key.name === "pendingMinMs" && prop.value?.type === "Literal") result.pendingOptions.pendingMinMs = prop.value.value;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Extract head options
|
|
416
|
+
*/
|
|
417
|
+
function extractHeadOptions(value) {
|
|
418
|
+
return {
|
|
419
|
+
isFunction: isFunctionExpression(value),
|
|
420
|
+
isStatic: value?.type === "ObjectExpression"
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
384
424
|
* Create an empty parsed route
|
|
385
425
|
*/
|
|
386
426
|
function createEmptyParsedRoute(filePath) {
|
|
@@ -390,8 +430,11 @@ function createEmptyParsedRoute(filePath) {
|
|
|
390
430
|
hasLoader: false,
|
|
391
431
|
hasMeta: false,
|
|
392
432
|
hasValidateSearch: false,
|
|
393
|
-
isLayout: filePath.includes("_layout") || filePath.includes("layout.vue"),
|
|
394
|
-
routeScriptContent: null
|
|
433
|
+
isLayout: filePath.includes("_layout") || filePath.includes("layout.vue") || isLayoutFilename(filePath),
|
|
434
|
+
routeScriptContent: null,
|
|
435
|
+
hasBeforeLoad: false,
|
|
436
|
+
hasPendingComponent: false,
|
|
437
|
+
hasHead: false
|
|
395
438
|
};
|
|
396
439
|
}
|
|
397
440
|
|
|
@@ -529,6 +572,120 @@ function buildGlobPattern(extensions) {
|
|
|
529
572
|
if (exts.length === 1) return `**/*${exts[0]}`;
|
|
530
573
|
return `**/*.{${exts.map((e) => e.slice(1)).join(",")}}`;
|
|
531
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Check if a route is a pathless layout (no URL segment contribution)
|
|
577
|
+
* Pathless layouts have routePath '' or '/' and rawSegment starting with '_'
|
|
578
|
+
*/
|
|
579
|
+
function isPathlessLayout(route) {
|
|
580
|
+
if (route.type !== "layout") return false;
|
|
581
|
+
const hasEmptyPath = route.routePath === "" || route.routePath === "/";
|
|
582
|
+
const hasPathlessSegment = route.rawSegment?.startsWith("_") ?? false;
|
|
583
|
+
return hasEmptyPath && hasPathlessSegment;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Parse filename to determine route type and path
|
|
587
|
+
*
|
|
588
|
+
* File-based routing conventions:
|
|
589
|
+
* - __root.vue -> root layout (wraps all routes)
|
|
590
|
+
* - index.vue -> index route
|
|
591
|
+
* - route.vue -> alternative index route
|
|
592
|
+
* - about.vue -> regular page
|
|
593
|
+
* - $id.vue -> dynamic param
|
|
594
|
+
* - $.vue -> catch-all (splat route)
|
|
595
|
+
* - (group)/ -> pathless group folder (route group)
|
|
596
|
+
* - _pathless.vue + _pathless/ -> pathless layout (underscore prefix)
|
|
597
|
+
* - posts.vue + posts/ -> layout route
|
|
598
|
+
* - posts.index.vue -> flat route notation (dot separator)
|
|
599
|
+
* - posts_/ -> layout escape (underscore suffix)
|
|
600
|
+
* - -utils.vue -> excluded from routing (dash prefix)
|
|
601
|
+
* - [x] -> escape special characters (e.g., script[.]js.vue -> /script.js)
|
|
602
|
+
*/
|
|
603
|
+
function parseFileName(fileName, dirPath, hasMatchingDirectory = false) {
|
|
604
|
+
let rawSegment = fileName;
|
|
605
|
+
if (fileName.endsWith(".lazy")) {
|
|
606
|
+
fileName = fileName.slice(0, -5);
|
|
607
|
+
rawSegment = fileName;
|
|
608
|
+
}
|
|
609
|
+
if (fileName.startsWith("-")) return {
|
|
610
|
+
type: "page",
|
|
611
|
+
routePath: "",
|
|
612
|
+
params: [],
|
|
613
|
+
rawSegment
|
|
614
|
+
};
|
|
615
|
+
if (fileName === "__root") return {
|
|
616
|
+
type: "layout",
|
|
617
|
+
routePath: "/",
|
|
618
|
+
params: [],
|
|
619
|
+
rawSegment
|
|
620
|
+
};
|
|
621
|
+
if (fileName.startsWith("_") && !fileName.startsWith("__")) return {
|
|
622
|
+
type: "layout",
|
|
623
|
+
routePath: buildRoutePath(dirPath, ""),
|
|
624
|
+
params: [],
|
|
625
|
+
rawSegment,
|
|
626
|
+
isPathlessLayout: true
|
|
627
|
+
};
|
|
628
|
+
if (fileName === "index" || fileName === "route") return {
|
|
629
|
+
type: "index",
|
|
630
|
+
routePath: buildRoutePath(dirPath, ""),
|
|
631
|
+
params: [],
|
|
632
|
+
rawSegment
|
|
633
|
+
};
|
|
634
|
+
if (isDotNotationRoute(fileName)) return parseDotNotationRoute(fileName, dirPath, rawSegment);
|
|
635
|
+
if (fileName === "$") return {
|
|
636
|
+
type: "catch-all",
|
|
637
|
+
routePath: buildRoutePath(dirPath, ":pathMatch(.*)*"),
|
|
638
|
+
params: [createCatchAllParam()],
|
|
639
|
+
rawSegment
|
|
640
|
+
};
|
|
641
|
+
const dynamicParam = extractDynamicParam(fileName);
|
|
642
|
+
if (dynamicParam) return {
|
|
643
|
+
type: "dynamic",
|
|
644
|
+
routePath: buildRoutePath(dirPath, `:${dynamicParam}`),
|
|
645
|
+
params: [createDynamicParam(dynamicParam)],
|
|
646
|
+
rawSegment
|
|
647
|
+
};
|
|
648
|
+
if (hasMatchingDirectory) return {
|
|
649
|
+
type: "layout",
|
|
650
|
+
routePath: buildRoutePath(dirPath, fileName),
|
|
651
|
+
params: [],
|
|
652
|
+
rawSegment
|
|
653
|
+
};
|
|
654
|
+
return {
|
|
655
|
+
type: "page",
|
|
656
|
+
routePath: buildRoutePath(dirPath, unescapeBrackets(fileName)),
|
|
657
|
+
params: [],
|
|
658
|
+
rawSegment
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Parse dot notation route (e.g., posts.index.vue, posts.$postId.vue)
|
|
663
|
+
*/
|
|
664
|
+
function parseDotNotationRoute(fileName, dirPath, rawSegment) {
|
|
665
|
+
const segments = fileName.split(".");
|
|
666
|
+
const lastSegment = segments[segments.length - 1];
|
|
667
|
+
const parentSegments = segments.slice(0, -1);
|
|
668
|
+
const virtualDir = isRootDir(dirPath) ? parentSegments.join("/") : `${dirPath}/${parentSegments.join("/")}`;
|
|
669
|
+
if (lastSegment === "index") return {
|
|
670
|
+
type: "index",
|
|
671
|
+
routePath: buildRoutePath(virtualDir, ""),
|
|
672
|
+
params: [],
|
|
673
|
+
rawSegment
|
|
674
|
+
};
|
|
675
|
+
const dynamicParam = extractDynamicParam(lastSegment);
|
|
676
|
+
if (dynamicParam) return {
|
|
677
|
+
type: "dynamic",
|
|
678
|
+
routePath: buildRoutePath(virtualDir, `:${dynamicParam}`),
|
|
679
|
+
params: [createDynamicParam(dynamicParam)],
|
|
680
|
+
rawSegment
|
|
681
|
+
};
|
|
682
|
+
return {
|
|
683
|
+
type: "page",
|
|
684
|
+
routePath: buildRoutePath(virtualDir, unescapeBrackets(lastSegment)),
|
|
685
|
+
params: [],
|
|
686
|
+
rawSegment
|
|
687
|
+
};
|
|
688
|
+
}
|
|
532
689
|
|
|
533
690
|
//#endregion
|
|
534
691
|
//#region src/scanner.ts
|
|
@@ -568,12 +725,11 @@ var RouteScanner = class {
|
|
|
568
725
|
* Process a single route file
|
|
569
726
|
*/
|
|
570
727
|
async processFile(filePath, dirSet) {
|
|
571
|
-
const fullPath = path.join(this.config.routesDirPath, filePath);
|
|
572
|
-
const fileName = path.basename(filePath, path.extname(filePath));
|
|
573
|
-
const dirPath = path.dirname(filePath);
|
|
728
|
+
const fullPath = path$1.join(this.config.routesDirPath, filePath);
|
|
729
|
+
const fileName = path$1.basename(filePath, path$1.extname(filePath));
|
|
730
|
+
const dirPath = path$1.dirname(filePath);
|
|
574
731
|
const potentialDirPath = isRootDir(dirPath) ? fileName : `${dirPath}/${fileName}`;
|
|
575
|
-
const
|
|
576
|
-
const { type, routePath, params, rawSegment } = this.parseFileName(fileName, dirPath, hasMatchingDirectory);
|
|
732
|
+
const { type, routePath, params, rawSegment } = parseFileName(fileName, dirPath, dirSet.has(potentialDirPath));
|
|
577
733
|
let content = fs.readFileSync(fullPath, "utf-8");
|
|
578
734
|
if (needsScaffolding(content)) {
|
|
579
735
|
const isLayout = type === "layout";
|
|
@@ -604,91 +760,6 @@ var RouteScanner = class {
|
|
|
604
760
|
};
|
|
605
761
|
}
|
|
606
762
|
/**
|
|
607
|
-
* Parse filename to determine route type and path
|
|
608
|
-
*
|
|
609
|
-
* Kimesh file-based routing conventions:
|
|
610
|
-
* - __root.vue → root layout (wraps everything)
|
|
611
|
-
* - index.vue → index route
|
|
612
|
-
* - about.vue → regular page (/about)
|
|
613
|
-
* - $id.vue → dynamic param (/users/:id)
|
|
614
|
-
* - $.vue → catch-all (splat route)
|
|
615
|
-
* - _auth.vue → pathless layout (underscore prefix, no URL segment)
|
|
616
|
-
* - posts.vue + posts/ → layout route (file + matching directory)
|
|
617
|
-
* - posts.index.vue → flat route notation (dot separator)
|
|
618
|
-
*/
|
|
619
|
-
parseFileName(fileName, dirPath, hasMatchingDirectory = false) {
|
|
620
|
-
const rawSegment = fileName;
|
|
621
|
-
if (fileName === "__root") return {
|
|
622
|
-
type: "layout",
|
|
623
|
-
routePath: "/",
|
|
624
|
-
params: [],
|
|
625
|
-
rawSegment
|
|
626
|
-
};
|
|
627
|
-
if (fileName.startsWith("_")) return {
|
|
628
|
-
type: "layout",
|
|
629
|
-
routePath: buildRoutePath(dirPath, ""),
|
|
630
|
-
params: [],
|
|
631
|
-
rawSegment
|
|
632
|
-
};
|
|
633
|
-
if (fileName === "index") return {
|
|
634
|
-
type: "index",
|
|
635
|
-
routePath: buildRoutePath(dirPath, ""),
|
|
636
|
-
params: [],
|
|
637
|
-
rawSegment
|
|
638
|
-
};
|
|
639
|
-
if (isDotNotationRoute(fileName)) return this.parseDotNotationRoute(fileName, dirPath, rawSegment);
|
|
640
|
-
if (fileName === "$") return {
|
|
641
|
-
type: "catch-all",
|
|
642
|
-
routePath: buildRoutePath(dirPath, ":pathMatch(.*)*"),
|
|
643
|
-
params: [createCatchAllParam()],
|
|
644
|
-
rawSegment
|
|
645
|
-
};
|
|
646
|
-
const dynamicParam = extractDynamicParam(fileName);
|
|
647
|
-
if (dynamicParam) return {
|
|
648
|
-
type: "dynamic",
|
|
649
|
-
routePath: buildRoutePath(dirPath, `:${dynamicParam}`),
|
|
650
|
-
params: [createDynamicParam(dynamicParam)],
|
|
651
|
-
rawSegment
|
|
652
|
-
};
|
|
653
|
-
if (hasMatchingDirectory) return {
|
|
654
|
-
type: "layout",
|
|
655
|
-
routePath: buildRoutePath(dirPath, fileName),
|
|
656
|
-
params: [],
|
|
657
|
-
rawSegment
|
|
658
|
-
};
|
|
659
|
-
return {
|
|
660
|
-
type: "page",
|
|
661
|
-
routePath: buildRoutePath(dirPath, fileName),
|
|
662
|
-
params: [],
|
|
663
|
-
rawSegment
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
parseDotNotationRoute(fileName, dirPath, rawSegment) {
|
|
667
|
-
const segments = fileName.split(".");
|
|
668
|
-
const lastSegment = segments[segments.length - 1];
|
|
669
|
-
const parentSegments = segments.slice(0, -1);
|
|
670
|
-
const virtualDir = isRootDir(dirPath) ? parentSegments.join("/") : `${dirPath}/${parentSegments.join("/")}`;
|
|
671
|
-
if (lastSegment === "index") return {
|
|
672
|
-
type: "index",
|
|
673
|
-
routePath: buildRoutePath(virtualDir, ""),
|
|
674
|
-
params: [],
|
|
675
|
-
rawSegment
|
|
676
|
-
};
|
|
677
|
-
const dynamicParam = extractDynamicParam(lastSegment);
|
|
678
|
-
if (dynamicParam) return {
|
|
679
|
-
type: "dynamic",
|
|
680
|
-
routePath: buildRoutePath(virtualDir, `:${dynamicParam}`),
|
|
681
|
-
params: [createDynamicParam(dynamicParam)],
|
|
682
|
-
rawSegment
|
|
683
|
-
};
|
|
684
|
-
return {
|
|
685
|
-
type: "page",
|
|
686
|
-
routePath: buildRoutePath(virtualDir, lastSegment),
|
|
687
|
-
params: [],
|
|
688
|
-
rawSegment
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
763
|
* Generate a valid JavaScript variable name from file path
|
|
693
764
|
*/
|
|
694
765
|
generateVariableName(filePath) {
|
|
@@ -766,8 +837,8 @@ var RouteTreeBuilder = class {
|
|
|
766
837
|
};
|
|
767
838
|
}
|
|
768
839
|
registerLayout(node) {
|
|
769
|
-
const fileName = path.basename(node.filePath, path.extname(node.filePath));
|
|
770
|
-
const dir = path.dirname(node.filePath);
|
|
840
|
+
const fileName = path$1.basename(node.filePath, path$1.extname(node.filePath));
|
|
841
|
+
const dir = path$1.dirname(node.filePath);
|
|
771
842
|
if (fileName === "__root") {
|
|
772
843
|
this.layoutNodes.set("", node);
|
|
773
844
|
return;
|
|
@@ -978,12 +1049,12 @@ function collectImports(routes, config, imports, importMap) {
|
|
|
978
1049
|
}
|
|
979
1050
|
}
|
|
980
1051
|
/**
|
|
981
|
-
* Collect route definition imports for routes with loaders/meta
|
|
1052
|
+
* Collect route definition imports for routes with loaders/meta/layout features
|
|
982
1053
|
* Uses ?route query to extract route definitions at build time (Vite 8/rolldown compatible)
|
|
983
1054
|
*/
|
|
984
1055
|
function collectRouteDefinitionImports(routes, config, imports, routeDefImportMap) {
|
|
985
1056
|
for (const route of routes) {
|
|
986
|
-
if (route.parsed.hasRouteDefinition) {
|
|
1057
|
+
if (route.parsed.hasRouteDefinition || route.parsed.hasLoader || route.parsed.hasBeforeLoad || route.parsed.hasPendingComponent || route.parsed.hasHead) {
|
|
987
1058
|
const importPath = getImportPath(route, config);
|
|
988
1059
|
const defName = `${route.variableName}_def`;
|
|
989
1060
|
imports.push(`import ${defName} from '${importPath}?route'`);
|
|
@@ -998,7 +1069,7 @@ function collectRouteDefinitionImports(routes, config, imports, routeDefImportMa
|
|
|
998
1069
|
function getImportPath(route, config) {
|
|
999
1070
|
const fromPath = config.generatedDirPath;
|
|
1000
1071
|
const toPath = route.fullPath;
|
|
1001
|
-
let relativePath = path.relative(fromPath, toPath);
|
|
1072
|
+
let relativePath = path$1.relative(fromPath, toPath);
|
|
1002
1073
|
if (!relativePath.startsWith(".")) relativePath = "./" + relativePath;
|
|
1003
1074
|
return relativePath;
|
|
1004
1075
|
}
|
|
@@ -1013,7 +1084,58 @@ function generateRouteRecords(routes, config, importMap, routeDefImportMap, inde
|
|
|
1013
1084
|
};
|
|
1014
1085
|
const pad = " ".repeat(indent);
|
|
1015
1086
|
const innerPad = " ".repeat(indent + 1);
|
|
1016
|
-
|
|
1087
|
+
const records = [];
|
|
1088
|
+
for (const route of routes) if (isPathlessLayoutWithoutIndex(route)) {
|
|
1089
|
+
const hoistedRecords = generateHoistedLayoutRoutes(route, ctx, innerPad, indent);
|
|
1090
|
+
records.push(...hoistedRecords);
|
|
1091
|
+
} else records.push(generateSingleRouteRecord(route, ctx, innerPad, indent));
|
|
1092
|
+
return `[\n${records.join(",\n")}\n${pad}]`;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Check if route is a pathless layout without an index route
|
|
1096
|
+
* Such layouts should not consume the empty path
|
|
1097
|
+
*/
|
|
1098
|
+
function isPathlessLayoutWithoutIndex(route) {
|
|
1099
|
+
if (!isPathlessLayout(route)) return false;
|
|
1100
|
+
return !route.children.some((child) => child.routePath === "" || child.routePath === route.routePath || child.type === "index");
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Generate routes for a pathless layout by hoisting each child
|
|
1104
|
+
* Each child route gets wrapped by the layout component
|
|
1105
|
+
*/
|
|
1106
|
+
function generateHoistedLayoutRoutes(layout, ctx, innerPad, indent) {
|
|
1107
|
+
const records = [];
|
|
1108
|
+
for (const child of layout.children) {
|
|
1109
|
+
const lines = [`${innerPad}{`];
|
|
1110
|
+
const childPath = child.parent ? getRelativePath(child) : child.routePath;
|
|
1111
|
+
lines.push(`${innerPad} path: '${childPath}',`);
|
|
1112
|
+
lines.push(generateComponentLine(layout, ctx, innerPad));
|
|
1113
|
+
const layoutMetaLine = generateMetaLine(layout, ctx, innerPad);
|
|
1114
|
+
if (layoutMetaLine) lines.push(layoutMetaLine);
|
|
1115
|
+
const childRecord = generateChildAsIndexRoute(child, ctx, indent + 2);
|
|
1116
|
+
lines.push(`${innerPad} children: [\n${childRecord}\n${innerPad} ],`);
|
|
1117
|
+
lines.push(`${innerPad}}`);
|
|
1118
|
+
records.push(lines.join("\n"));
|
|
1119
|
+
}
|
|
1120
|
+
return records;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Generate a child route as an index route (path: '')
|
|
1124
|
+
*/
|
|
1125
|
+
function generateChildAsIndexRoute(route, ctx, indent) {
|
|
1126
|
+
const innerPad = " ".repeat(indent + 1);
|
|
1127
|
+
const lines = [`${innerPad}{`];
|
|
1128
|
+
lines.push(`${innerPad} path: '',`);
|
|
1129
|
+
if (shouldIncludeName(route)) lines.push(`${innerPad} name: '${route.routeName}',`);
|
|
1130
|
+
lines.push(generateComponentLine(route, ctx, innerPad));
|
|
1131
|
+
const metaLine = generateMetaLine(route, ctx, innerPad);
|
|
1132
|
+
if (metaLine) lines.push(metaLine);
|
|
1133
|
+
if (route.children.length > 0) {
|
|
1134
|
+
const childRecords = generateRouteRecords(route.children, ctx.config, ctx.importMap, ctx.routeDefImportMap, indent + 2);
|
|
1135
|
+
lines.push(`${innerPad} children: ${childRecords},`);
|
|
1136
|
+
}
|
|
1137
|
+
lines.push(`${innerPad}}`);
|
|
1138
|
+
return lines.join("\n");
|
|
1017
1139
|
}
|
|
1018
1140
|
function generateSingleRouteRecord(route, ctx, innerPad, indent) {
|
|
1019
1141
|
const lines = [`${innerPad}{`];
|
|
@@ -1021,11 +1143,8 @@ function generateSingleRouteRecord(route, ctx, innerPad, indent) {
|
|
|
1021
1143
|
lines.push(`${innerPad} path: '${routePath}',`);
|
|
1022
1144
|
if (shouldIncludeName(route)) lines.push(`${innerPad} name: '${route.routeName}',`);
|
|
1023
1145
|
lines.push(generateComponentLine(route, ctx, innerPad));
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
if (routeDefName) metaProps.push(`__kimesh: ${routeDefName}`);
|
|
1027
|
-
if (route.layer) metaProps.push(`__kimeshLayer: '${route.layer}'`);
|
|
1028
|
-
if (metaProps.length > 0) lines.push(`${innerPad} meta: { ${metaProps.join(", ")} },`);
|
|
1146
|
+
const metaLine = generateMetaLine(route, ctx, innerPad);
|
|
1147
|
+
if (metaLine) lines.push(metaLine);
|
|
1029
1148
|
if (route.children.length > 0) {
|
|
1030
1149
|
const childRecords = generateRouteRecords(route.children, ctx.config, ctx.importMap, ctx.routeDefImportMap, indent + 2);
|
|
1031
1150
|
lines.push(`${innerPad} children: ${childRecords},`);
|
|
@@ -1041,6 +1160,30 @@ function generateComponentLine(route, ctx, innerPad) {
|
|
|
1041
1160
|
return `${innerPad} component: ${ctx.importMap.get(route.filePath) || route.variableName},`;
|
|
1042
1161
|
}
|
|
1043
1162
|
/**
|
|
1163
|
+
* Generate meta line with route definition and layout-specific markers
|
|
1164
|
+
*/
|
|
1165
|
+
function generateMetaLine(route, ctx, innerPad) {
|
|
1166
|
+
const metaProps = [];
|
|
1167
|
+
const routeDefName = ctx.routeDefImportMap.get(route.filePath);
|
|
1168
|
+
if (routeDefName) metaProps.push(`__kimesh: ${routeDefName}`);
|
|
1169
|
+
if (route.layer) metaProps.push(`__kimeshLayer: '${route.layer}'`);
|
|
1170
|
+
if (route.type === "layout") metaProps.push(`__kimeshIsLayout: true`);
|
|
1171
|
+
if (route.parsed.hasPendingComponent) {
|
|
1172
|
+
metaProps.push(`__kimeshHasPending: true`);
|
|
1173
|
+
const pendingOptions = route.parsed.pendingOptions;
|
|
1174
|
+
if (pendingOptions?.pendingMs !== void 0) metaProps.push(`__kimeshPendingMs: ${pendingOptions.pendingMs}`);
|
|
1175
|
+
if (pendingOptions?.pendingMinMs !== void 0) metaProps.push(`__kimeshPendingMinMs: ${pendingOptions.pendingMinMs}`);
|
|
1176
|
+
}
|
|
1177
|
+
if (route.parsed.hasBeforeLoad) metaProps.push(`__kimeshHasBeforeLoad: true`);
|
|
1178
|
+
if (route.parsed.hasHead) {
|
|
1179
|
+
metaProps.push(`__kimeshHasHead: true`);
|
|
1180
|
+
const headOptions = route.parsed.headOptions;
|
|
1181
|
+
if (headOptions?.isFunction !== void 0) metaProps.push(`__kimeshHeadIsFunction: ${headOptions.isFunction}`);
|
|
1182
|
+
}
|
|
1183
|
+
if (metaProps.length === 0) return null;
|
|
1184
|
+
return `${innerPad} meta: { ${metaProps.join(", ")} },`;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1044
1187
|
* Get path relative to parent route
|
|
1045
1188
|
*/
|
|
1046
1189
|
function getRelativePath(route) {
|
|
@@ -1106,16 +1249,16 @@ export type RoutePaths = ${routePathsUnion}
|
|
|
1106
1249
|
*/
|
|
1107
1250
|
function collectAllRoutes(routes) {
|
|
1108
1251
|
const infos = [];
|
|
1109
|
-
collectRoutesRecursively(routes, "", infos);
|
|
1252
|
+
collectRoutesRecursively$1(routes, "", infos);
|
|
1110
1253
|
return infos;
|
|
1111
1254
|
}
|
|
1112
|
-
function collectRoutesRecursively(nodes, parentPath, infos) {
|
|
1255
|
+
function collectRoutesRecursively$1(nodes, parentPath, infos) {
|
|
1113
1256
|
for (const route of nodes) {
|
|
1114
1257
|
const fullPath = computeFullPath(route.routePath, parentPath);
|
|
1115
1258
|
if (route.type === "layout") {
|
|
1116
1259
|
if (route.children.length > 0) {
|
|
1117
1260
|
const childParentPath = fullPath === "/" ? "" : fullPath;
|
|
1118
|
-
collectRoutesRecursively(route.children, childParentPath, infos);
|
|
1261
|
+
collectRoutesRecursively$1(route.children, childParentPath, infos);
|
|
1119
1262
|
}
|
|
1120
1263
|
continue;
|
|
1121
1264
|
}
|
|
@@ -1125,7 +1268,7 @@ function collectRoutesRecursively(nodes, parentPath, infos) {
|
|
|
1125
1268
|
params: collectParamsFromPath(fullPath),
|
|
1126
1269
|
children: collectChildPaths(route.children, fullPath)
|
|
1127
1270
|
});
|
|
1128
|
-
if (route.children.length > 0) collectRoutesRecursively(route.children, fullPath, infos);
|
|
1271
|
+
if (route.children.length > 0) collectRoutesRecursively$1(route.children, fullPath, infos);
|
|
1129
1272
|
}
|
|
1130
1273
|
}
|
|
1131
1274
|
function computeFullPath(routePath, parentPath) {
|
|
@@ -1218,7 +1361,7 @@ function extractRouteDefinition(filePath) {
|
|
|
1218
1361
|
if (!match) return EMPTY_EXPORT;
|
|
1219
1362
|
const routeObject = extractBalancedObject(code, match.index + match[0].length);
|
|
1220
1363
|
if (!routeObject) return EMPTY_EXPORT;
|
|
1221
|
-
return `${extractUsedImports(code, routeObject, path.dirname(filePath))}\nexport default ${routeObject}`;
|
|
1364
|
+
return `${extractUsedImports(code, routeObject, path$1.dirname(filePath))}\nexport default ${routeObject}`;
|
|
1222
1365
|
}
|
|
1223
1366
|
/**
|
|
1224
1367
|
* Extract a balanced object starting from a position in the code
|
|
@@ -1334,7 +1477,7 @@ function shouldSkipImport(source) {
|
|
|
1334
1477
|
return false;
|
|
1335
1478
|
}
|
|
1336
1479
|
function resolveImportSource(source, fileDir) {
|
|
1337
|
-
if (source.startsWith(".")) return path.resolve(fileDir, source);
|
|
1480
|
+
if (source.startsWith(".")) return path$1.resolve(fileDir, source);
|
|
1338
1481
|
return source;
|
|
1339
1482
|
}
|
|
1340
1483
|
function addUsedNamedImports(namedImportStr, source, routeObject, namedImports) {
|
|
@@ -1486,18 +1629,12 @@ async function scanLayerRoutes(source, config) {
|
|
|
1486
1629
|
* When a file like posts.vue has a matching posts/ directory, it becomes a layout
|
|
1487
1630
|
*/
|
|
1488
1631
|
async function processLayerFile(filePath, routesDir, source, config, dirSet) {
|
|
1489
|
-
const fullPath = path.join(routesDir, filePath);
|
|
1490
|
-
const fileName = path.basename(filePath, path.extname(filePath));
|
|
1491
|
-
const dirPath = path.dirname(filePath);
|
|
1632
|
+
const fullPath = path$1.join(routesDir, filePath);
|
|
1633
|
+
const fileName = path$1.basename(filePath, path$1.extname(filePath));
|
|
1634
|
+
const dirPath = path$1.dirname(filePath);
|
|
1492
1635
|
if (fileName.startsWith("-")) return null;
|
|
1493
1636
|
const potentialDirPath = isRootDir(dirPath) ? fileName : `${dirPath}/${fileName}`;
|
|
1494
|
-
const
|
|
1495
|
-
let { type, routePath, params, rawSegment } = parseFileName(fileName, dirPath);
|
|
1496
|
-
if (hasMatchingDirectory && type === "page") {
|
|
1497
|
-
type = "layout";
|
|
1498
|
-
routePath = buildRoutePath(dirPath, fileName);
|
|
1499
|
-
logger$1.debug(`Layout route detected: ${fileName} -> ${routePath}`);
|
|
1500
|
-
}
|
|
1637
|
+
const { type, routePath, params, rawSegment } = parseFileName(fileName, dirPath, dirSet.has(potentialDirPath));
|
|
1501
1638
|
let content;
|
|
1502
1639
|
try {
|
|
1503
1640
|
content = fs.readFileSync(fullPath, "utf-8");
|
|
@@ -1521,16 +1658,14 @@ async function processLayerFile(filePath, routesDir, source, config, dirSet) {
|
|
|
1521
1658
|
const parsed = parseRouteFile(content, fullPath);
|
|
1522
1659
|
const variableName = generateVariableName(filePath, source.layer);
|
|
1523
1660
|
const finalRoutePath = applyBasePath(routePath, source.basePath);
|
|
1524
|
-
const routeName = generateRouteName(finalRoutePath, source.layer);
|
|
1525
|
-
const isLazy = fileName.endsWith(".lazy") || shouldBeLazy(filePath, config);
|
|
1526
1661
|
return {
|
|
1527
1662
|
filePath,
|
|
1528
1663
|
fullPath,
|
|
1529
1664
|
routePath,
|
|
1530
1665
|
variableName,
|
|
1531
1666
|
type,
|
|
1532
|
-
routeName,
|
|
1533
|
-
isLazy,
|
|
1667
|
+
routeName: generateRouteName(finalRoutePath, source.layer),
|
|
1668
|
+
isLazy: fileName.endsWith(".lazy") || shouldBeLazy(filePath, config),
|
|
1534
1669
|
params,
|
|
1535
1670
|
parsed,
|
|
1536
1671
|
children: [],
|
|
@@ -1542,101 +1677,6 @@ async function processLayerFile(filePath, routesDir, source, config, dirSet) {
|
|
|
1542
1677
|
};
|
|
1543
1678
|
}
|
|
1544
1679
|
/**
|
|
1545
|
-
* Parse filename to determine route type and path
|
|
1546
|
-
*
|
|
1547
|
-
* File-based routing conventions:
|
|
1548
|
-
* - __root.vue → root layout (wraps all routes)
|
|
1549
|
-
* - index.vue → index route
|
|
1550
|
-
* - route.vue → alternative index route
|
|
1551
|
-
* - about.vue → regular page
|
|
1552
|
-
* - $id.vue → dynamic param
|
|
1553
|
-
* - $.vue → catch-all (splat route)
|
|
1554
|
-
* - (group)/ → pathless group folder (route group)
|
|
1555
|
-
* - _pathless.vue + _pathless/ → pathless layout (underscore prefix)
|
|
1556
|
-
* - posts.vue + posts/ → layout route
|
|
1557
|
-
* - posts.index.vue → flat route notation (dot separator)
|
|
1558
|
-
* - posts_/ → layout escape (underscore suffix)
|
|
1559
|
-
* - -utils.vue → excluded from routing (dash prefix)
|
|
1560
|
-
* - [x] → escape special characters (e.g., script[.]js.vue → /script.js)
|
|
1561
|
-
*/
|
|
1562
|
-
function parseFileName(fileName, dirPath) {
|
|
1563
|
-
let rawSegment = fileName;
|
|
1564
|
-
if (fileName.endsWith(".lazy")) {
|
|
1565
|
-
fileName = fileName.slice(0, -5);
|
|
1566
|
-
rawSegment = fileName;
|
|
1567
|
-
}
|
|
1568
|
-
if (fileName.startsWith("-")) return {
|
|
1569
|
-
type: "page",
|
|
1570
|
-
routePath: "",
|
|
1571
|
-
params: [],
|
|
1572
|
-
rawSegment
|
|
1573
|
-
};
|
|
1574
|
-
if (fileName === "__root") return {
|
|
1575
|
-
type: "layout",
|
|
1576
|
-
routePath: "/",
|
|
1577
|
-
params: [],
|
|
1578
|
-
rawSegment
|
|
1579
|
-
};
|
|
1580
|
-
if (fileName.startsWith("_") && !fileName.startsWith("__")) return {
|
|
1581
|
-
type: "layout",
|
|
1582
|
-
routePath: buildRoutePath(dirPath, ""),
|
|
1583
|
-
params: [],
|
|
1584
|
-
rawSegment,
|
|
1585
|
-
isPathlessLayout: true
|
|
1586
|
-
};
|
|
1587
|
-
if (fileName === "index" || fileName === "route") return {
|
|
1588
|
-
type: "index",
|
|
1589
|
-
routePath: buildRoutePath(dirPath, ""),
|
|
1590
|
-
params: [],
|
|
1591
|
-
rawSegment
|
|
1592
|
-
};
|
|
1593
|
-
if (isDotNotationRoute(fileName)) return parseDotNotationRoute(fileName, dirPath, rawSegment);
|
|
1594
|
-
if (fileName === "$") return {
|
|
1595
|
-
type: "catch-all",
|
|
1596
|
-
routePath: buildRoutePath(dirPath, ":pathMatch(.*)*"),
|
|
1597
|
-
params: [createCatchAllParam()],
|
|
1598
|
-
rawSegment
|
|
1599
|
-
};
|
|
1600
|
-
const dynamicParam = extractDynamicParam(fileName);
|
|
1601
|
-
if (dynamicParam) return {
|
|
1602
|
-
type: "dynamic",
|
|
1603
|
-
routePath: buildRoutePath(dirPath, `:${dynamicParam}`),
|
|
1604
|
-
params: [createDynamicParam(dynamicParam)],
|
|
1605
|
-
rawSegment
|
|
1606
|
-
};
|
|
1607
|
-
return {
|
|
1608
|
-
type: "page",
|
|
1609
|
-
routePath: buildRoutePath(dirPath, unescapeBrackets(fileName)),
|
|
1610
|
-
params: [],
|
|
1611
|
-
rawSegment
|
|
1612
|
-
};
|
|
1613
|
-
}
|
|
1614
|
-
function parseDotNotationRoute(fileName, dirPath, rawSegment) {
|
|
1615
|
-
const segments = fileName.split(".");
|
|
1616
|
-
const lastSegment = segments[segments.length - 1];
|
|
1617
|
-
const parentSegments = segments.slice(0, -1);
|
|
1618
|
-
const virtualDir = isRootDir(dirPath) ? parentSegments.join("/") : `${dirPath}/${parentSegments.join("/")}`;
|
|
1619
|
-
if (lastSegment === "index") return {
|
|
1620
|
-
type: "index",
|
|
1621
|
-
routePath: buildRoutePath(virtualDir, ""),
|
|
1622
|
-
params: [],
|
|
1623
|
-
rawSegment
|
|
1624
|
-
};
|
|
1625
|
-
const dynamicParam = extractDynamicParam(lastSegment);
|
|
1626
|
-
if (dynamicParam) return {
|
|
1627
|
-
type: "dynamic",
|
|
1628
|
-
routePath: buildRoutePath(virtualDir, `:${dynamicParam}`),
|
|
1629
|
-
params: [createDynamicParam(dynamicParam)],
|
|
1630
|
-
rawSegment
|
|
1631
|
-
};
|
|
1632
|
-
return {
|
|
1633
|
-
type: "page",
|
|
1634
|
-
routePath: buildRoutePath(virtualDir, unescapeBrackets(lastSegment)),
|
|
1635
|
-
params: [],
|
|
1636
|
-
rawSegment
|
|
1637
|
-
};
|
|
1638
|
-
}
|
|
1639
|
-
/**
|
|
1640
1680
|
* Apply basePath prefix to route path
|
|
1641
1681
|
*/
|
|
1642
1682
|
function applyBasePath(routePath, basePath) {
|
|
@@ -1678,7 +1718,6 @@ function shouldBeLazy(filePath, config) {
|
|
|
1678
1718
|
|
|
1679
1719
|
//#endregion
|
|
1680
1720
|
//#region src/route-merger.ts
|
|
1681
|
-
var route_merger_exports = /* @__PURE__ */ __exportAll({ mergeRoutes: () => mergeRoutes });
|
|
1682
1721
|
/**
|
|
1683
1722
|
* Merge app routes with layer routes
|
|
1684
1723
|
* App routes (priority 0) take precedence over layer routes
|
|
@@ -1697,20 +1736,53 @@ function mergeWithRootLayout(appRoutes, layerRoutes, rootLayout) {
|
|
|
1697
1736
|
const layoutRoutesByPath = buildLayoutRoutesMap(rootLayout.children);
|
|
1698
1737
|
for (const layerRoute of layerRoutes) {
|
|
1699
1738
|
const relativePath = normalizeRoutePath(layerRoute.routePath);
|
|
1700
|
-
const
|
|
1701
|
-
if (
|
|
1702
|
-
else
|
|
1739
|
+
const hostRouteInfo = layoutRoutesByPath.get(relativePath);
|
|
1740
|
+
if (hostRouteInfo) nestLayerUnderHost(layerRoute, hostRouteInfo.route);
|
|
1741
|
+
else {
|
|
1742
|
+
const pathlessParent = findPathlessLayoutForLayerRoute(rootLayout.children, relativePath, layerRoute);
|
|
1743
|
+
if (pathlessParent) addLayerUnderPathlessLayout(layerRoute, relativePath, pathlessParent, layoutRoutesByPath);
|
|
1744
|
+
else addLayerAsSibling(layerRoute, relativePath, rootLayout, layoutRoutesByPath);
|
|
1745
|
+
}
|
|
1703
1746
|
}
|
|
1704
1747
|
sortRouteChildren(rootLayout.children);
|
|
1705
1748
|
return appRoutes;
|
|
1706
1749
|
}
|
|
1707
1750
|
function buildLayoutRoutesMap(children) {
|
|
1708
1751
|
const map = /* @__PURE__ */ new Map();
|
|
1709
|
-
|
|
1752
|
+
collectRoutesRecursively(children, map, void 0);
|
|
1753
|
+
return map;
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Recursively collect routes from children, including routes nested under pathless layouts.
|
|
1757
|
+
* Pathless layouts have empty routePath ('') and their children should be accessible
|
|
1758
|
+
* at the same level as their siblings.
|
|
1759
|
+
*/
|
|
1760
|
+
function collectRoutesRecursively(children, map, pathlessParent) {
|
|
1761
|
+
for (const child of children) {
|
|
1710
1762
|
const normalizedPath = normalizeRoutePath(child.routePath);
|
|
1711
|
-
|
|
1763
|
+
if (isPathlessLayout(child) && child.children && child.children.length > 0) collectRoutesRecursively(child.children, map, child);
|
|
1764
|
+
if (child.routePath && !child.routePath.includes(":")) map.set(normalizedPath, {
|
|
1765
|
+
route: child,
|
|
1766
|
+
pathlessParent
|
|
1767
|
+
});
|
|
1712
1768
|
}
|
|
1713
|
-
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Find a pathless layout that should wrap a layer route based on its structure.
|
|
1772
|
+
* A pathless layout should wrap layer routes if the layer's file path starts with
|
|
1773
|
+
* the pathless layout's rawSegment (e.g., '_dashboard/blog' matches '_dashboard')
|
|
1774
|
+
*/
|
|
1775
|
+
function findPathlessLayoutForLayerRoute(children, _relativePath, layerRoute) {
|
|
1776
|
+
if (layerRoute?.filePath) {
|
|
1777
|
+
for (const child of children) if (isPathlessLayout(child) && child.rawSegment) {
|
|
1778
|
+
if (layerRoute.filePath.startsWith(child.rawSegment + "/")) return child;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
for (const child of children) if (isPathlessLayout(child) && child.children && child.children.length > 0) {
|
|
1782
|
+
const nestedPathless = findPathlessLayoutForLayerRoute(child.children, _relativePath, layerRoute);
|
|
1783
|
+
if (nestedPathless) return nestedPathless;
|
|
1784
|
+
}
|
|
1785
|
+
return null;
|
|
1714
1786
|
}
|
|
1715
1787
|
function normalizeRoutePath(routePath) {
|
|
1716
1788
|
return routePath.startsWith("/") ? routePath.slice(1) : routePath;
|
|
@@ -1725,6 +1797,21 @@ function nestLayerUnderHost(layerRoute, hostLayoutRoute) {
|
|
|
1725
1797
|
};
|
|
1726
1798
|
hostLayoutRoute.children.push(layerWrapper);
|
|
1727
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Add layer route under a pathless layout
|
|
1802
|
+
*/
|
|
1803
|
+
function addLayerUnderPathlessLayout(layerRoute, relativePath, pathlessLayout, layoutRoutesByPath) {
|
|
1804
|
+
const nestedRoute = {
|
|
1805
|
+
...layerRoute,
|
|
1806
|
+
routePath: relativePath,
|
|
1807
|
+
parent: pathlessLayout
|
|
1808
|
+
};
|
|
1809
|
+
pathlessLayout.children.push(nestedRoute);
|
|
1810
|
+
layoutRoutesByPath.set(relativePath, {
|
|
1811
|
+
route: nestedRoute,
|
|
1812
|
+
pathlessParent: pathlessLayout
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1728
1815
|
function addLayerAsSibling(layerRoute, relativePath, rootLayout, layoutRoutesByPath) {
|
|
1729
1816
|
const nestedRoute = {
|
|
1730
1817
|
...layerRoute,
|
|
@@ -1732,12 +1819,19 @@ function addLayerAsSibling(layerRoute, relativePath, rootLayout, layoutRoutesByP
|
|
|
1732
1819
|
parent: rootLayout
|
|
1733
1820
|
};
|
|
1734
1821
|
rootLayout.children.push(nestedRoute);
|
|
1735
|
-
layoutRoutesByPath.set(relativePath, nestedRoute);
|
|
1822
|
+
layoutRoutesByPath.set(relativePath, { route: nestedRoute });
|
|
1736
1823
|
}
|
|
1737
1824
|
function sortRouteChildren(children) {
|
|
1738
1825
|
children.sort((a, b) => {
|
|
1739
|
-
if (a.routePath === "")
|
|
1740
|
-
|
|
1826
|
+
if ((a.routePath === "" || a.routePath === "/") && (b.routePath === "" || b.routePath === "/")) {
|
|
1827
|
+
const aHasIndex = a.children?.some((c) => c.routePath === "" && c.type === "page");
|
|
1828
|
+
const bHasIndex = b.children?.some((c) => c.routePath === "" && c.type === "page");
|
|
1829
|
+
if (aHasIndex && !bHasIndex) return 1;
|
|
1830
|
+
if (!aHasIndex && bHasIndex) return -1;
|
|
1831
|
+
return (a.rawSegment || "").localeCompare(b.rawSegment || "");
|
|
1832
|
+
}
|
|
1833
|
+
if (a.routePath === "" || a.routePath === "/") return -1;
|
|
1834
|
+
if (b.routePath === "" || b.routePath === "/") return 1;
|
|
1741
1835
|
return a.routePath.localeCompare(b.routePath);
|
|
1742
1836
|
});
|
|
1743
1837
|
}
|
|
@@ -1751,7 +1845,6 @@ function mergeWithoutRootLayout(appRoutes, layerRoutes) {
|
|
|
1751
1845
|
merged.sort((a, b) => a.routePath.localeCompare(b.routePath));
|
|
1752
1846
|
return merged;
|
|
1753
1847
|
}
|
|
1754
|
-
var init_route_merger = __esmMin((() => {}));
|
|
1755
1848
|
|
|
1756
1849
|
//#endregion
|
|
1757
1850
|
//#region src/plugin.ts
|
|
@@ -1795,8 +1888,8 @@ function kimeshRouterGenerator(options = {}) {
|
|
|
1795
1888
|
config = {
|
|
1796
1889
|
...mergedConfig,
|
|
1797
1890
|
root,
|
|
1798
|
-
routesDirPath: path.resolve(root, mergedConfig.srcDir, mergedConfig.routesDir),
|
|
1799
|
-
generatedDirPath: path.resolve(root, mergedConfig.generatedDir)
|
|
1891
|
+
routesDirPath: path$1.resolve(root, mergedConfig.srcDir, mergedConfig.routesDir),
|
|
1892
|
+
generatedDirPath: path$1.resolve(root, mergedConfig.generatedDir)
|
|
1800
1893
|
};
|
|
1801
1894
|
scanner = new RouteScanner(config);
|
|
1802
1895
|
log("Config resolved:", {
|
|
@@ -1825,7 +1918,7 @@ function kimeshRouterGenerator(options = {}) {
|
|
|
1825
1918
|
if (id === "virtual:kimesh-routes" || id === "~kimesh/routes") return VIRTUAL_ROUTES_ID;
|
|
1826
1919
|
if (isRouteQuery(id)) {
|
|
1827
1920
|
const basePath = getBasePath(id);
|
|
1828
|
-
return VIRTUAL_ROUTE_PREFIX + (importer ? path.resolve(path.dirname(importer.replace("\0", "")), basePath) : basePath);
|
|
1921
|
+
return VIRTUAL_ROUTE_PREFIX + (importer ? path$1.resolve(path$1.dirname(importer.replace("\0", "")), basePath) : basePath);
|
|
1829
1922
|
}
|
|
1830
1923
|
return null;
|
|
1831
1924
|
},
|
|
@@ -1834,9 +1927,36 @@ function kimeshRouterGenerator(options = {}) {
|
|
|
1834
1927
|
if (id.startsWith(VIRTUAL_ROUTE_PREFIX)) {
|
|
1835
1928
|
const filePath = id.slice(14);
|
|
1836
1929
|
log("Extracting route definition from:", filePath);
|
|
1837
|
-
return
|
|
1930
|
+
return {
|
|
1931
|
+
code: extractRouteDefinition(filePath),
|
|
1932
|
+
map: null
|
|
1933
|
+
};
|
|
1838
1934
|
}
|
|
1839
1935
|
return null;
|
|
1936
|
+
},
|
|
1937
|
+
async transform(code, id) {
|
|
1938
|
+
if (!id.startsWith(VIRTUAL_ROUTE_PREFIX)) return null;
|
|
1939
|
+
try {
|
|
1940
|
+
const { transform } = await import("esbuild");
|
|
1941
|
+
const result = await transform(code, {
|
|
1942
|
+
loader: "ts",
|
|
1943
|
+
target: "esnext",
|
|
1944
|
+
format: "esm",
|
|
1945
|
+
minify: false,
|
|
1946
|
+
tsconfigRaw: { compilerOptions: {
|
|
1947
|
+
importsNotUsedAsValues: "remove",
|
|
1948
|
+
verbatimModuleSyntax: false
|
|
1949
|
+
} }
|
|
1950
|
+
});
|
|
1951
|
+
log("Transformed route module:", id.slice(14).slice(-60));
|
|
1952
|
+
return {
|
|
1953
|
+
code: result.code,
|
|
1954
|
+
map: result.map || null
|
|
1955
|
+
};
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
log("esbuild transform failed for:", id, error);
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1840
1960
|
}
|
|
1841
1961
|
};
|
|
1842
1962
|
async function generateRoutesOnce() {
|
|
@@ -1859,7 +1979,7 @@ function kimeshRouterGenerator(options = {}) {
|
|
|
1859
1979
|
priority: l.priority
|
|
1860
1980
|
})), config);
|
|
1861
1981
|
log("Collected layer routes:", layerResult.routes.length);
|
|
1862
|
-
routes = mergeRoutes
|
|
1982
|
+
routes = mergeRoutes(appRoutes, layerResult.routes, config);
|
|
1863
1983
|
} else routes = appRoutes;
|
|
1864
1984
|
log("Final merged routes:", routes.length, routes.map((r) => r.routePath));
|
|
1865
1985
|
await writeGeneratedFiles();
|
|
@@ -1871,20 +1991,20 @@ function kimeshRouterGenerator(options = {}) {
|
|
|
1871
1991
|
async function writeGeneratedFiles() {
|
|
1872
1992
|
if (!fs.existsSync(config.generatedDirPath)) fs.mkdirSync(config.generatedDirPath, { recursive: true });
|
|
1873
1993
|
const routesCode = generateRoutes(routes, config);
|
|
1874
|
-
const routesPath = path.join(config.generatedDirPath, "routes.gen.ts");
|
|
1994
|
+
const routesPath = path$1.join(config.generatedDirPath, "routes.gen.ts");
|
|
1875
1995
|
fs.writeFileSync(routesPath, routesCode, "utf-8");
|
|
1876
1996
|
const typesCode = generateRouteTypes(routes);
|
|
1877
|
-
const typesPath = path.join(config.generatedDirPath, "typed-routes.d.ts");
|
|
1997
|
+
const typesPath = path$1.join(config.generatedDirPath, "typed-routes.d.ts");
|
|
1878
1998
|
fs.writeFileSync(typesPath, typesCode, "utf-8");
|
|
1879
1999
|
const layerRoutes = resolveLayerRoutes();
|
|
1880
2000
|
for (const layer of layerRoutes) {
|
|
1881
|
-
let layerRoot = layer.layerPath || path.dirname(layer.routesDir);
|
|
2001
|
+
let layerRoot = layer.layerPath || path$1.dirname(layer.routesDir);
|
|
1882
2002
|
try {
|
|
1883
2003
|
layerRoot = fs.realpathSync(layerRoot);
|
|
1884
2004
|
} catch {}
|
|
1885
|
-
const layerKimeshDir = path.join(layerRoot, ".kimesh");
|
|
2005
|
+
const layerKimeshDir = path$1.join(layerRoot, ".kimesh");
|
|
1886
2006
|
if (!fs.existsSync(layerKimeshDir)) fs.mkdirSync(layerKimeshDir, { recursive: true });
|
|
1887
|
-
const layerTypesPath = path.join(layerKimeshDir, "typed-routes.d.ts");
|
|
2007
|
+
const layerTypesPath = path$1.join(layerKimeshDir, "typed-routes.d.ts");
|
|
1888
2008
|
fs.writeFileSync(layerTypesPath, typesCode, "utf-8");
|
|
1889
2009
|
log("Generated layer types:", {
|
|
1890
2010
|
layer: layer.layerName,
|
|
@@ -1909,20 +2029,9 @@ function kimeshRouterGenerator(options = {}) {
|
|
|
1909
2029
|
}
|
|
1910
2030
|
}
|
|
1911
2031
|
}
|
|
1912
|
-
/**
|
|
1913
|
-
* Merge app routes with layer routes
|
|
1914
|
-
* App routes (priority 0) take precedence over layer routes
|
|
1915
|
-
* Layer routes are nested under the host's root layout if one exists
|
|
1916
|
-
* If app has a layout route for a path, layer routes become its children
|
|
1917
|
-
*/
|
|
1918
|
-
function mergeRoutes$1(appRoutes, layerRoutes, _config) {
|
|
1919
|
-
const { mergeRoutes: mergeRoutesImpl } = (init_route_merger(), __toCommonJS(route_merger_exports));
|
|
1920
|
-
return mergeRoutesImpl(appRoutes, layerRoutes, _config);
|
|
1921
|
-
}
|
|
1922
2032
|
|
|
1923
2033
|
//#endregion
|
|
1924
2034
|
//#region src/layout-resolver.ts
|
|
1925
|
-
init_route_merger();
|
|
1926
2035
|
/**
|
|
1927
2036
|
* @kimesh/router-generator - Layout Resolver
|
|
1928
2037
|
*
|
|
@@ -2053,7 +2162,7 @@ var LayoutResolver = class {
|
|
|
2053
2162
|
* _layout.vue → undefined
|
|
2054
2163
|
*/
|
|
2055
2164
|
extractLayoutName(filePath) {
|
|
2056
|
-
const basename = path.basename(filePath, path.extname(filePath));
|
|
2165
|
+
const basename = path$1.basename(filePath, path$1.extname(filePath));
|
|
2057
2166
|
if (basename.startsWith("_layout.") && basename !== "_layout") return basename.replace("_layout.", "");
|
|
2058
2167
|
}
|
|
2059
2168
|
/**
|
|
@@ -2359,4 +2468,4 @@ export { default } from '${normalizeImportPath(file.filePath)}';
|
|
|
2359
2468
|
}
|
|
2360
2469
|
|
|
2361
2470
|
//#endregion
|
|
2362
|
-
export { LayoutResolver, MiddlewareScanner, RouteScanner, RouteTreeBuilder, buildGlobPattern, buildRoutePath, buildRouteTree, collectLayerRoutes, createCatchAllParam, createDynamicParam, createEmptyParsedRoute, createScanner, createTreeBuilder, extractDynamicParam, generateLayoutStructure, generateMiddlewareModule, generateMiddlewareRegistry, generateMiddlewareTypes, generateRouteTypes, generateRoutes, isDotNotationRoute, isRootDir, kimeshRouterGenerator, mergeRoutes, parseRouteFile, processDirectoryPath, processPathSegment, scanLayerMiddleware, scanMiddlewareDir, scanMiddlewareFiles, unescapeBrackets };
|
|
2471
|
+
export { LayoutResolver, MiddlewareScanner, RouteScanner, RouteTreeBuilder, buildGlobPattern, buildRoutePath, buildRouteTree, collectLayerRoutes, createCatchAllParam, createDynamicParam, createEmptyParsedRoute, createScanner, createTreeBuilder, extractDynamicParam, generateLayoutStructure, generateMiddlewareModule, generateMiddlewareRegistry, generateMiddlewareTypes, generateRouteTypes, generateRoutes, isDotNotationRoute, isPathlessLayout, isRootDir, kimeshRouterGenerator, mergeRoutes, parseFileName, parseRouteFile, processDirectoryPath, processPathSegment, scanLayerMiddleware, scanMiddlewareDir, scanMiddlewareFiles, unescapeBrackets };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kimesh/router-generator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "File-based route generator for Kimesh",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,7 +41,13 @@
|
|
|
41
41
|
"vitest": "^4.0.17"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
+
"esbuild": "*",
|
|
44
45
|
"vite": "^8.0.0-beta.8",
|
|
45
46
|
"vue-router": "^4.6.4"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"esbuild": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
}
|