vitarx-router 4.0.0-beta.2 → 4.0.0-beta.21

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.
Files changed (93) hide show
  1. package/README.md +42 -17
  2. package/dist/{plugin-vite/auto-routes → auto-routes}/handleHotUpdate.d.ts +1 -1
  3. package/dist/components/RouterView.js +5 -4
  4. package/dist/core/common/constant.d.ts +5 -6
  5. package/dist/core/common/constant.js +5 -6
  6. package/dist/core/common/utils.js +2 -1
  7. package/dist/core/router/checkOptions.d.ts +11 -0
  8. package/dist/core/router/checkOptions.js +119 -0
  9. package/dist/core/router/manager.js +27 -23
  10. package/dist/core/router/router.d.ts +154 -1
  11. package/dist/core/router/router.js +303 -230
  12. package/dist/core/router/web.d.ts +13 -0
  13. package/dist/core/router/web.js +35 -4
  14. package/dist/core/shared/link.d.ts +7 -0
  15. package/dist/core/shared/link.js +11 -8
  16. package/dist/core/shared/route.js +1 -2
  17. package/dist/core/shared/router.d.ts +3 -3
  18. package/dist/core/shared/router.js +7 -4
  19. package/dist/core/types/options.d.ts +2 -0
  20. package/dist/file-router/config/index.d.ts +2 -1
  21. package/dist/file-router/config/index.js +2 -1
  22. package/dist/file-router/config/resolve.d.ts +43 -0
  23. package/dist/file-router/config/resolve.js +69 -0
  24. package/dist/file-router/{utils/validateOptions.d.ts → config/validate.d.ts} +11 -10
  25. package/dist/file-router/config/validate.js +280 -0
  26. package/dist/file-router/constants.d.ts +12 -2
  27. package/dist/file-router/constants.js +13 -3
  28. package/dist/file-router/generator/generateRoutes.d.ts +44 -13
  29. package/dist/file-router/generator/generateRoutes.js +159 -80
  30. package/dist/file-router/generator/generateTypes.d.ts +3 -29
  31. package/dist/file-router/generator/generateTypes.js +36 -41
  32. package/dist/file-router/global.d.ts +1 -1
  33. package/dist/file-router/index.d.ts +224 -90
  34. package/dist/file-router/index.js +571 -135
  35. package/dist/file-router/macros/astValueExtractor.d.ts +1 -1
  36. package/dist/file-router/macros/astValueExtractor.js +27 -7
  37. package/dist/file-router/macros/definePage.d.ts +20 -3
  38. package/dist/file-router/macros/definePage.js +120 -40
  39. package/dist/file-router/parser/exportChecker.d.ts +4 -23
  40. package/dist/file-router/parser/exportChecker.js +38 -79
  41. package/dist/file-router/parser/filterUtils.d.ts +25 -0
  42. package/dist/file-router/parser/filterUtils.js +43 -0
  43. package/dist/file-router/parser/index.d.ts +2 -1
  44. package/dist/file-router/parser/index.js +2 -1
  45. package/dist/file-router/parser/parsePage.d.ts +56 -9
  46. package/dist/file-router/parser/parsePage.js +194 -172
  47. package/dist/file-router/parser/routePath.d.ts +22 -0
  48. package/dist/file-router/parser/routePath.js +74 -0
  49. package/dist/file-router/types/hooks.d.ts +52 -0
  50. package/dist/file-router/types/index.d.ts +3 -0
  51. package/dist/file-router/types/index.js +1 -0
  52. package/dist/file-router/types/options.d.ts +279 -0
  53. package/dist/file-router/types/options.js +1 -0
  54. package/dist/file-router/types/route.d.ts +114 -0
  55. package/dist/file-router/types/route.js +1 -0
  56. package/dist/file-router/utils/fileReader.d.ts +11 -0
  57. package/dist/file-router/utils/fileReader.js +22 -0
  58. package/dist/file-router/utils/findRoute.d.ts +8 -0
  59. package/dist/file-router/utils/findRoute.js +22 -0
  60. package/dist/file-router/utils/index.d.ts +4 -2
  61. package/dist/file-router/utils/index.js +4 -2
  62. package/dist/file-router/utils/logger.d.ts +6 -6
  63. package/dist/file-router/utils/logger.js +44 -4
  64. package/dist/file-router/utils/pathStrategy.d.ts +28 -0
  65. package/dist/file-router/utils/{namingStrategy.js → pathStrategy.js} +18 -28
  66. package/dist/file-router/utils/pathUtils.d.ts +31 -0
  67. package/dist/file-router/utils/pathUtils.js +53 -1
  68. package/dist/plugin-vite/constant.d.ts +9 -0
  69. package/dist/plugin-vite/constant.js +9 -0
  70. package/dist/plugin-vite/index.d.ts +4 -24
  71. package/dist/plugin-vite/index.js +4 -94
  72. package/dist/plugin-vite/plugin.d.ts +86 -0
  73. package/dist/plugin-vite/plugin.js +181 -0
  74. package/dist/plugin-vite/watcher.d.ts +15 -0
  75. package/dist/plugin-vite/watcher.js +65 -0
  76. package/package.json +9 -7
  77. package/dist/file-router/config/configUtils.d.ts +0 -54
  78. package/dist/file-router/config/configUtils.js +0 -88
  79. package/dist/file-router/scanner/filterUtils.d.ts +0 -35
  80. package/dist/file-router/scanner/filterUtils.js +0 -188
  81. package/dist/file-router/scanner/index.d.ts +0 -8
  82. package/dist/file-router/scanner/index.js +0 -8
  83. package/dist/file-router/scanner/routeTreeBuilder.d.ts +0 -21
  84. package/dist/file-router/scanner/routeTreeBuilder.js +0 -312
  85. package/dist/file-router/scanner/scanPages.d.ts +0 -48
  86. package/dist/file-router/scanner/scanPages.js +0 -174
  87. package/dist/file-router/types.d.ts +0 -344
  88. package/dist/file-router/utils/namingStrategy.d.ts +0 -57
  89. package/dist/file-router/utils/validateOptions.js +0 -233
  90. /package/dist/{plugin-vite/auto-routes → auto-routes}/handleHotUpdate.js +0 -0
  91. /package/dist/{plugin-vite/auto-routes → auto-routes}/index.d.ts +0 -0
  92. /package/dist/{plugin-vite/auto-routes → auto-routes}/index.js +0 -0
  93. /package/dist/file-router/{types.js → types/hooks.js} +0 -0
@@ -1,12 +1,59 @@
1
- import type { NamingStrategy, ParsedPage } from '../types.js';
1
+ import type { PageParser, PageParseResult } from '../types/index.js';
2
2
  /**
3
- * 解析页面文件
3
+ * 路径解析错误类
4
4
  *
5
- * @param filePath - 文件绝对路径
6
- * @param pagesDir - 页面目录绝对路径
7
- * @param parentPath - 父级路径(用于嵌套路由)
8
- * @param namingStrategy - 命名策略,默认为 'kebab'
9
- * @param pathPrefix - 路由路径前缀,默认为 ''
10
- * @returns 解析后的页面信息,解析失败或无有效导出返回 null
5
+ * 提供详细的错误上下文信息,包括文件路径、错误类型和原始值。
11
6
  */
12
- export declare function parsePageFile(filePath: string, pagesDir: string, parentPath: string, namingStrategy?: NamingStrategy, pathPrefix?: string): ParsedPage | null;
7
+ export declare class PageParseError extends TypeError {
8
+ /** 文件路径上下文 */
9
+ readonly filePath?: string;
10
+ /** 错误字段名称 */
11
+ readonly field?: string;
12
+ /** 原始值 */
13
+ readonly originalValue?: unknown;
14
+ constructor(message: string, options?: {
15
+ filePath?: string;
16
+ field?: string;
17
+ originalValue?: unknown;
18
+ cause?: Error;
19
+ });
20
+ /**
21
+ * 生成详细的错误信息
22
+ */
23
+ toString(): string;
24
+ }
25
+ /**
26
+ * 解析路由路径和视图名称
27
+ *
28
+ * 从文件名中提取路由路径和命名视图名称。
29
+ *
30
+ * @param filePath - 文件路径
31
+ * @param [parser] - 自定义解析器
32
+ * @param [precomputed] - 预计算的文件信息,避免重复解析文件路径
33
+ * @returns 路径解析结果
34
+ * @throws {PageParseError} 当路径解析失败时抛出
35
+ */
36
+ export declare function parsePageFile(filePath: string, parser?: PageParser, precomputed?: FileInfo): PageParseResult;
37
+ /**
38
+ * 文件信息
39
+ *
40
+ * 从文件路径中提取的结构化信息,作为文件名解析的唯一来源。
41
+ */
42
+ export interface FileInfo {
43
+ /** 文件名(不含扩展名),如 home@sidebar → home@sidebar */
44
+ basename: string;
45
+ /** 路由名(@之前的部分),如 home@sidebar → home */
46
+ rawName: string;
47
+ /** 视图名称(@之后的部分),如 home@sidebar → sidebar */
48
+ viewName: string | undefined;
49
+ }
50
+ /**
51
+ * 提取文件信息
52
+ *
53
+ * 从文件路径中提取文件名、路由名和视图名称。
54
+ * 这是文件名解析的唯一入口,其他模块应复用此函数而非重复实现。
55
+ *
56
+ * @param filePath - 文件路径
57
+ * @returns 文件信息
58
+ */
59
+ export declare function extractFileInfo(filePath: string): FileInfo;
@@ -3,209 +3,231 @@
3
3
  *
4
4
  * 负责解析页面文件路径,提取路由信息,包括:
5
5
  * - 路由路径转换
6
- * - 动态参数识别
7
- * - 路由名称生成
8
6
  *
9
7
  * 与构建工具无关,可在任何 Node.js 环境中使用。
10
8
  */
11
9
  import path from 'node:path';
12
- import { parseDefinePage } from '../macros/index.js';
13
- import { warn } from '../utils/logger.js';
14
- import { applyNamingStrategyToName, applyNamingStrategyToPath } from '../utils/namingStrategy.js';
15
- import { normalizePathSeparator } from '../utils/pathUtils.js';
16
- import { checkDefaultExport, shouldCheckExport } from './exportChecker.js';
17
- /** 动态参数匹配正则,如 [id]、[slug]、[param?] */
18
- const DYNAMIC_PARAM_REGEX = /^\[(.+?)(\?)?]$/;
19
10
  /**
20
- * 解析文件名
11
+ * 路径解析错误类
21
12
  *
22
- * 提取文件名中的路由信息和动态参数。
13
+ * 提供详细的错误上下文信息,包括文件路径、错误类型和原始值。
14
+ */
15
+ export class PageParseError extends TypeError {
16
+ constructor(message, options) {
17
+ super(message, { cause: options?.cause });
18
+ /** 文件路径上下文 */
19
+ Object.defineProperty(this, "filePath", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: void 0
24
+ });
25
+ /** 错误字段名称 */
26
+ Object.defineProperty(this, "field", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: void 0
31
+ });
32
+ /** 原始值 */
33
+ Object.defineProperty(this, "originalValue", {
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true,
37
+ value: void 0
38
+ });
39
+ this.name = 'PathParseError';
40
+ this.filePath = options?.filePath;
41
+ this.field = options?.field;
42
+ this.originalValue = options?.originalValue;
43
+ if (Error.captureStackTrace) {
44
+ Error.captureStackTrace(this, PageParseError);
45
+ }
46
+ }
47
+ /**
48
+ * 生成详细的错误信息
49
+ */
50
+ toString() {
51
+ let details = `${this.name}: ${this.message}`;
52
+ if (this.filePath) {
53
+ details += `\n File: ${this.filePath}`;
54
+ }
55
+ if (this.field) {
56
+ details += `\n Field: ${this.field}`;
57
+ }
58
+ if (this.originalValue !== undefined) {
59
+ details += `\n Value: ${String(this.originalValue)}`;
60
+ }
61
+ return details;
62
+ }
63
+ }
64
+ /**
65
+ * 解析路由路径和视图名称
23
66
  *
24
- * @param fileName - 文件名(不含扩展名)
67
+ * 从文件名中提取路由路径和命名视图名称。
68
+ *
69
+ * @param filePath - 文件路径
70
+ * @param [parser] - 自定义解析器
71
+ * @param [precomputed] - 预计算的文件信息,避免重复解析文件路径
72
+ * @returns 路径解析结果
73
+ * @throws {PageParseError} 当路径解析失败时抛出
74
+ */
75
+ export function parsePageFile(filePath, parser, precomputed) {
76
+ const info = precomputed ?? extractFileInfo(filePath);
77
+ if (!parser) {
78
+ return defaultPageParser(info.rawName, info.viewName);
79
+ }
80
+ const result = parser(info.basename, filePath);
81
+ return parseCustomResult(result, filePath);
82
+ }
83
+ /**
84
+ * 提取文件信息
85
+ *
86
+ * 从文件路径中提取文件名、路由名和视图名称。
87
+ * 这是文件名解析的唯一入口,其他模块应复用此函数而非重复实现。
88
+ *
89
+ * @param filePath - 文件路径
90
+ * @returns 文件信息
91
+ */
92
+ export function extractFileInfo(filePath) {
93
+ const ext = path.extname(filePath);
94
+ const basename = path.basename(filePath, ext);
95
+ const [rawName, viewName] = basename.split('@', 2);
96
+ return { basename, rawName, viewName };
97
+ }
98
+ /**
99
+ * 解析默认路由路径(无自定义解析器)
100
+ *
101
+ * @param rawName - 路由名(@之前的部分)
102
+ * @param viewName - 视图名称(@之后的部分)
25
103
  * @returns 解析结果
26
104
  */
27
- function parseFileName(fileName) {
28
- const params = [];
29
- let isDynamic = false;
30
- // 匹配动态参数模式
31
- const dynamicMatch = fileName.match(DYNAMIC_PARAM_REGEX);
32
- if (dynamicMatch) {
33
- isDynamic = true;
34
- const paramName = dynamicMatch[1];
35
- // 检查是否为可选参数(以 ? 结尾)
36
- const isOptional = !!dynamicMatch[2];
37
- params.push(paramName);
38
- // 生成路由参数格式:{param} 或 {param?}
39
- return { name: `{${paramName}${isOptional ? '?' : ''}}`, params, isDynamic };
105
+ function defaultPageParser(rawName, viewName) {
106
+ const routePath = normalizeRoutePath(rawName);
107
+ if (!routePath) {
108
+ throw new PageParseError('PageParser returned empty path', {
109
+ filePath: rawName,
110
+ originalValue: rawName,
111
+ field: 'path'
112
+ });
40
113
  }
41
- return { name: fileName, params, isDynamic };
114
+ return {
115
+ path: routePath,
116
+ viewName
117
+ };
42
118
  }
43
119
  /**
44
- * 生成路由名称
120
+ * 解析自定义解析器结果
45
121
  *
46
- * @param relativePath - 相对于页面目录的路径
47
- * @param baseName - 文件基础名
48
- * @param pathPrefix - 路径前缀
49
- * @returns 路由名称
122
+ * @param result - 解析器返回的结果
123
+ * @param filePath - 文件路径(用于错误上下文)
124
+ * @returns 解析结果
125
+ * @throws {PageParseError} 当结果无效时抛出
50
126
  */
51
- function generateRouteName(relativePath, baseName, pathPrefix = '') {
52
- const dirPath = path.dirname(relativePath);
53
- const segments = [];
54
- // 处理路径前缀
55
- if (pathPrefix) {
56
- const prefixName = pathPrefix
57
- .trim()
58
- .replace(/^\/+/, '')
59
- .replace(/[-\/]+$/, '')
60
- .trim();
61
- if (prefixName) {
62
- segments.push(prefixName);
63
- }
127
+ function parseCustomResult(result, filePath) {
128
+ if (typeof result === 'string') {
129
+ const [rawName, viewName] = result.split('@', 2);
130
+ return defaultPageParser(rawName, viewName);
64
131
  }
65
- // 处理目录路径
66
- if (dirPath !== '.') {
67
- segments.push(...normalizePathSeparator(dirPath).split('/'));
132
+ if (result && typeof result === 'object' && !Array.isArray(result)) {
133
+ return parseObjectResult(result, filePath);
68
134
  }
69
- // 处理文件名
70
- if (baseName !== 'index' || dirPath === '.') {
71
- if (baseName === 'index' && dirPath === '.' && !pathPrefix) {
72
- segments.push('index');
73
- }
74
- else if (baseName !== 'index') {
75
- // 处理动态参数
76
- const dynamicMatch = baseName.match(DYNAMIC_PARAM_REGEX);
77
- if (dynamicMatch) {
78
- segments.push(dynamicMatch[1]);
79
- }
80
- else {
81
- segments.push(baseName);
82
- }
83
- }
84
- }
85
- return segments.join('-');
135
+ throw new PageParseError('PageParser returned invalid result type', {
136
+ filePath,
137
+ originalValue: result
138
+ });
86
139
  }
87
140
  /**
88
- * 构建路由路径
141
+ * 解析对象类型的结果
89
142
  *
90
- * @param isIndex - 是否为索引页面
91
- * @param dirPath - 目录路径
92
- * @param routeName - 路由名称
93
- * @param pathPrefix - 路径前缀
94
- * @returns 完整的路由路径
143
+ * @param result - 对象结果
144
+ * @param filePath - 文件路径
145
+ * @returns 解析结果
146
+ * @throws {PageParseError} 当结果无效时抛出
95
147
  */
96
- function buildRoutePath(isIndex, dirPath, routeName, pathPrefix) {
97
- let routePath;
98
- // 构建基础路由路径
99
- if (isIndex) {
100
- if (dirPath === '.') {
101
- routePath = '/';
102
- }
103
- else {
104
- routePath = '/' + normalizePathSeparator(dirPath);
105
- }
148
+ function parseObjectResult(result, filePath) {
149
+ const { path: rawRoutePath, viewName, options } = result;
150
+ validateRoutePathType(rawRoutePath, filePath);
151
+ const routePath = normalizeRoutePath(rawRoutePath);
152
+ if (!routePath) {
153
+ throw new PageParseError('PageParser returned empty path after normalization', {
154
+ filePath,
155
+ originalValue: rawRoutePath,
156
+ field: 'path'
157
+ });
106
158
  }
107
- else {
108
- if (dirPath === '.') {
109
- routePath = '/' + routeName;
110
- }
111
- else {
112
- routePath = '/' + normalizePathSeparator(dirPath) + '/' + routeName;
113
- }
159
+ validateViewName(viewName, filePath);
160
+ validateOptions(options, filePath);
161
+ return {
162
+ ...result,
163
+ path: routePath
164
+ };
165
+ }
166
+ /**
167
+ * 验证路由路径类型
168
+ *
169
+ * @param routePath - 路由路径
170
+ * @param filePath - 文件路径(用于错误上下文)
171
+ * @throws {PageParseError} 当类型无效时抛出
172
+ */
173
+ function validateRoutePathType(routePath, filePath) {
174
+ if (typeof routePath !== 'string') {
175
+ throw new PageParseError('PageParser returned non-string path', {
176
+ filePath,
177
+ originalValue: routePath,
178
+ field: 'path'
179
+ });
114
180
  }
115
- // 处理路径前缀
116
- pathPrefix = pathPrefix.trim() === '/' ? '' : pathPrefix.trim();
117
- if (pathPrefix) {
118
- const normalizedPrefix = pathPrefix.startsWith('/') ? pathPrefix : `/${pathPrefix}`;
119
- if (routePath === '/') {
120
- routePath = normalizedPrefix.replace(/\/+$/, '');
121
- }
122
- else {
123
- routePath = normalizedPrefix + routePath.slice(1);
124
- }
181
+ if (!routePath.trim()) {
182
+ throw new PageParseError('PageParser returned empty or whitespace-only path', {
183
+ filePath,
184
+ originalValue: routePath,
185
+ field: 'path'
186
+ });
125
187
  }
126
- return routePath;
127
188
  }
128
189
  /**
129
- * 解析视图名称
190
+ * 验证视图名称
130
191
  *
131
- * 从文件名中提取命名视图名称。
132
- *
133
- * @param baseName - 文件基础名
134
- * @returns 视图名称和去除视图名的基础名
192
+ * @param viewName - 视图名称
193
+ * @param filePath - 文件路径(用于错误上下文)
194
+ * @throws {PageParseError} 当视图名称无效时抛出
135
195
  */
136
- function parseViewName(baseName) {
137
- // 查找 @ 分隔符
138
- const viewSeparatorIndex = baseName.indexOf('@');
139
- if (viewSeparatorIndex > -1) {
140
- return {
141
- baseWithoutView: baseName.slice(0, viewSeparatorIndex),
142
- viewName: baseName.slice(viewSeparatorIndex + 1)
143
- };
196
+ function validateViewName(viewName, filePath) {
197
+ if (viewName !== undefined && typeof viewName !== 'string') {
198
+ throw new PageParseError('PageParser returned non-string viewName', {
199
+ filePath,
200
+ originalValue: viewName,
201
+ field: 'viewName'
202
+ });
144
203
  }
145
- return { viewName: null, baseWithoutView: baseName };
146
204
  }
147
205
  /**
148
- * 解析页面文件
206
+ * 验证选项
149
207
  *
150
- * @param filePath - 文件绝对路径
151
- * @param pagesDir - 页面目录绝对路径
152
- * @param parentPath - 父级路径(用于嵌套路由)
153
- * @param namingStrategy - 命名策略,默认为 'kebab'
154
- * @param pathPrefix - 路由路径前缀,默认为 ''
155
- * @returns 解析后的页面信息,解析失败或无有效导出返回 null
208
+ * @param options - 选项
209
+ * @param filePath - 文件路径
156
210
  */
157
- export function parsePageFile(filePath, pagesDir, parentPath, namingStrategy = 'kebab', pathPrefix = '') {
158
- // 提取文件名和扩展名
159
- const fileName = path.basename(filePath);
160
- const ext = path.extname(fileName);
161
- const baseName = fileName.slice(0, -ext.length);
162
- // 解析视图名称
163
- const { viewName, baseWithoutView } = parseViewName(baseName);
164
- // 检查文件导出
165
- if (shouldCheckExport(ext)) {
166
- const exportCheck = checkDefaultExport(filePath);
167
- if (exportCheck.warning) {
168
- const pageOptions = parseDefinePage(filePath);
169
- // 检查是否有默认导出或 redirect 配置
170
- if (!exportCheck.hasDefaultExport || !exportCheck.isFunctionOrClass) {
171
- if (!pageOptions?.redirect) {
172
- warn('页面文件缺少有效导出且未定义 redirect', `${filePath}\n ${exportCheck.warning}`);
173
- return null;
174
- }
175
- }
176
- }
211
+ function validateOptions(options, filePath) {
212
+ if (options !== undefined && Object.prototype.toString.call(options) !== '[object Object]') {
213
+ throw new PageParseError('PageParser returned invalid options type', {
214
+ filePath,
215
+ originalValue: options,
216
+ field: 'options'
217
+ });
177
218
  }
178
- // 计算相对路径
179
- const relativePath = path.relative(pagesDir, filePath);
180
- const dirPath = path.dirname(relativePath);
181
- // 检查是否为索引页面
182
- const isIndex = baseWithoutView === 'index';
183
- // 解析文件名,提取动态参数
184
- const { name: routeName, params, isDynamic } = parseFileName(baseWithoutView);
185
- // 构建完整路由路径
186
- const routePath = buildRoutePath(isIndex, dirPath, routeName, pathPrefix);
187
- // 解析 definePage 配置
188
- const pageOptions = parseDefinePage(filePath);
189
- // 生成路由名称
190
- const name = pageOptions?.name || generateRouteName(relativePath, baseWithoutView, pathPrefix);
191
- // 应用命名策略
192
- const finalPath = applyNamingStrategyToPath(routePath, namingStrategy);
193
- const finalName = applyNamingStrategyToName(name, namingStrategy);
194
- // 返回解析结果
195
- return {
196
- path: finalPath,
197
- filePath,
198
- name: finalName,
199
- params,
200
- isIndex,
201
- isDynamic,
202
- children: [],
203
- meta: pageOptions?.meta,
204
- pattern: pageOptions?.pattern,
205
- customName: pageOptions?.name,
206
- parentPath,
207
- redirect: pageOptions?.redirect,
208
- alias: pageOptions?.alias,
209
- viewName
210
- };
219
+ }
220
+ /**
221
+ * 标准化路由路径
222
+ *
223
+ * 去除路径首尾空白和首尾的斜杠,将 . # 等不利于 URL 的字符替换为 -。
224
+ *
225
+ * @param routePath - 原始路由路径
226
+ * @returns 标准化后的路由路径
227
+ */
228
+ function normalizeRoutePath(routePath) {
229
+ return routePath
230
+ .trim()
231
+ .replace(/^\/+|\/+$/g, '')
232
+ .replace(/[.#]/g, '-');
211
233
  }
@@ -0,0 +1,22 @@
1
+ import { type PageDirConfig } from '../config/resolve.js';
2
+ import type { PageParser, PathStrategy, ScanNode } from '../types/index.js';
3
+ import { type FileInfo } from './parsePage.js';
4
+ interface RouteFullPathContext {
5
+ fileMap: Map<string, ScanNode>;
6
+ pages: readonly PageDirConfig[];
7
+ pageParser?: PageParser;
8
+ pathStrategy: PathStrategy;
9
+ }
10
+ /**
11
+ * 计算文件的完整路由路径
12
+ *
13
+ * 判断文件是否为页面文件,如果是则计算其最终生成的路由 fullPath。
14
+ * 非页面文件(布局文件、配置文件等)返回 null。
15
+ *
16
+ * @param filePath - 文件绝对路径
17
+ * @param fileInfo - 文件信息
18
+ * @param context - 路由计算上下文
19
+ * @returns 完整路由路径,非页面文件返回 null
20
+ */
21
+ export declare function computeRouteFullPath(filePath: string, fileInfo: FileInfo, context: RouteFullPathContext): string | null;
22
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @fileoverview 路由路径计算辅助函数
3
+ *
4
+ * 从文件路径计算最终生成的路由 fullPath,
5
+ * 支持已跟踪(在路由树中)和未跟踪(尚未扫描)两种场景。
6
+ */
7
+ import nodePath from 'node:path';
8
+ import { applyPathStrategy } from '../utils/pathStrategy.js';
9
+ import { normalizeRoutePath, resolvePathVariable } from '../utils/pathUtils.js';
10
+ import { isPageFileInDirs } from './filterUtils.js';
11
+ import { parsePageFile } from './parsePage.js';
12
+ /**
13
+ * 应用路径策略(命名转换 + 动态参数转换)
14
+ *
15
+ * @param path - 路径
16
+ * @param strategy - 路径策略
17
+ * @returns 转换后的路径
18
+ */
19
+ function applyFullPathStrategy(path, strategy) {
20
+ return resolvePathVariable(applyPathStrategy(path, strategy));
21
+ }
22
+ /**
23
+ * 从节点树计算完整路由路径
24
+ *
25
+ * 沿 parent 链向上拼接所有节点的 path,与生成阶段 fullPath 的计算逻辑一致。
26
+ *
27
+ * @param node - 路由节点
28
+ * @returns 完整路由路径
29
+ */
30
+ function computeNodeFullPath(node) {
31
+ const segments = [];
32
+ let current = node;
33
+ while (current) {
34
+ segments.unshift(current.path);
35
+ current = current.parent;
36
+ }
37
+ return normalizeRoutePath(segments.join('/'));
38
+ }
39
+ /**
40
+ * 计算文件的完整路由路径
41
+ *
42
+ * 判断文件是否为页面文件,如果是则计算其最终生成的路由 fullPath。
43
+ * 非页面文件(布局文件、配置文件等)返回 null。
44
+ *
45
+ * @param filePath - 文件绝对路径
46
+ * @param fileInfo - 文件信息
47
+ * @param context - 路由计算上下文
48
+ * @returns 完整路由路径,非页面文件返回 null
49
+ */
50
+ export function computeRouteFullPath(filePath, fileInfo, context) {
51
+ const { fileMap, pages, pageParser, pathStrategy } = context;
52
+ const node = fileMap.get(filePath);
53
+ if (node) {
54
+ return computeNodeFullPath(node);
55
+ }
56
+ const page = isPageFileInDirs(filePath, pages);
57
+ if (!page)
58
+ return null;
59
+ const parsed = parsePageFile(filePath, pageParser, fileInfo);
60
+ const pathSegment = parsed.path === 'index' ? '' : applyFullPathStrategy(parsed.path, pathStrategy);
61
+ const segments = pathSegment ? [pathSegment] : [];
62
+ let dirPath = nodePath.dirname(filePath);
63
+ while (dirPath.length > page.dir.length) {
64
+ const dirNode = fileMap.get(dirPath);
65
+ if (dirNode) {
66
+ const parentFullPath = computeNodeFullPath(dirNode);
67
+ return normalizeRoutePath(parentFullPath + '/' + segments.join('/'));
68
+ }
69
+ segments.unshift(applyFullPathStrategy(nodePath.basename(dirPath), pathStrategy));
70
+ dirPath = nodePath.dirname(dirPath);
71
+ }
72
+ const prefix = page.prefix ? applyFullPathStrategy(page.prefix, pathStrategy) : '';
73
+ return normalizeRoutePath(prefix + '/' + segments.join('/'));
74
+ }
@@ -0,0 +1,52 @@
1
+ import type { RouteNode } from './route.js';
2
+ /**
3
+ * 扩展路由的钩子
4
+ *
5
+ * 支持对 route 的数据直接进行修改,
6
+ * 但此时分组路由的children还未赋值!
7
+ * 特殊需求可通过BeforeWriteRoutesHook钩子处理。
8
+ *
9
+ * @param route - 生成的路由节点
10
+ * @param parsed - 解析的路由节点
11
+ *
12
+ * @example
13
+ * ```js
14
+ * {
15
+ * extendRoute(route) {
16
+ * if (route.path === '/home') {
17
+ * route.meta ??= { auth: true }
18
+ * }
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ export type ExtendRouteHook = (route: RouteNode) => void;
24
+ /**
25
+ * 写入路由文件前的钩子
26
+ *
27
+ * 支持对 routes 直接进行修改亦可以返回新的路由数组。
28
+ *
29
+ * @param routes - 路由数组
30
+ *
31
+ * @example
32
+ * ```js
33
+ * {
34
+ * beforeWriteRoutes(routes) {
35
+ * routes.forEach(route => {
36
+ * if (route.path === '/') {
37
+ * route.path = '/home'
38
+ * }
39
+ * })
40
+ * }
41
+ * }
42
+ * ```
43
+ */
44
+ export type BeforeWriteRoutesHook = (routes: RouteNode[]) => void | RouteNode[];
45
+ /**
46
+ * 代码转换函数
47
+ *
48
+ * @param content - 代码内容
49
+ * @param file - 文件路径
50
+ * @returns - 转换后的代码内容
51
+ */
52
+ export type CodeTransformHook = (content: string, file: string) => string;
@@ -0,0 +1,3 @@
1
+ export type * from './options.js';
2
+ export type * from './route.js';
3
+ export type * from './hooks.js';
@@ -0,0 +1 @@
1
+ export {};