@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 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 hasMatchingDirectory = dirSet.has(potentialDirPath);
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
- return `[\n${routes.map((route) => generateSingleRouteRecord(route, ctx, innerPad, indent)).join(",\n")}\n${pad}]`;
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 routeDefName = ctx.routeDefImportMap.get(route.filePath);
1025
- const metaProps = [];
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 hasMatchingDirectory = dirSet.has(potentialDirPath);
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 hostLayoutRoute = layoutRoutesByPath.get(relativePath);
1701
- if (hostLayoutRoute) nestLayerUnderHost(layerRoute, hostLayoutRoute);
1702
- else addLayerAsSibling(layerRoute, relativePath, rootLayout, layoutRoutesByPath);
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
- for (const child of children) if (child.routePath && !child.routePath.includes(":")) {
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
- map.set(normalizedPath, child);
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
- return map;
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 === "") return -1;
1740
- if (b.routePath === "") return 1;
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 extractRouteDefinition(filePath);
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$1(appRoutes, layerResult.routes, config);
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.23",
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
  }