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,92 +1,80 @@
1
- /**
2
- * @fileoverview 文件路由管理器
3
- *
4
- * 封装文件路由的核心流程,可在不同构建工具中复用。
5
- * 支持 Vite、Webpack、Rollup 等构建工具,或在 Node.js 脚本中直接使用。
6
- */
7
- import fs from 'node:fs';
8
- import path from 'node:path';
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _FileRouter_nodeTree, _FileRouter_fileMap, _FileRouter_generateResult;
13
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
14
+ import nodePath from 'node:path';
9
15
  import { resolveConfig } from './config/index.js';
10
- import { generateFullDtsFile, generateRoutes } from './generator/index.js';
11
- import { removeDefinePage } from './macros/index.js';
12
- import { buildRouteTree, isPageFileInDirs, scanMultiplePages } from './scanner/index.js';
13
- import { validateOptions } from './utils/index.js';
16
+ import { generateRoutes } from './generator/index.js';
17
+ import { isEqualPageOptions, mergePageOptions, parseDefinePage, removeDefinePage } from './macros/index.js';
18
+ import { checkDefaultExport, isPageFile, isPageFileInDirs } from './parser/index.js';
19
+ import { extractFileInfo, parsePageFile } from './parser/parsePage.js';
20
+ import { computeRouteFullPath } from './parser/routePath.js';
21
+ import { applyPathStrategy, info, normalizePathSeparator, readFileContent, resolvePathVariable, validateOptions, warn } from './utils/index.js';
22
+ export { resolvePageConfigs } from './config/resolve.js';
23
+ export * from './generator/index.js';
24
+ export { mergePageOptions } from './macros/definePage.js';
25
+ export { findRoute } from './utils/findRoute.js';
14
26
  export * from './utils/logger.js';
15
- export * from './types.js';
16
27
  /**
17
28
  * 文件路由管理器
18
- *
19
- * 封装文件路由的核心流程,提供统一的 API 供不同构建工具使用。
20
- *
21
- * @example
22
- * ```typescript
23
- * // 基本使用
24
- * const router = new FileRouter({
25
- * pagesDir: 'src/pages',
26
- * extensions: ['.tsx', '.ts']
27
- * })
28
- *
29
- * router.setRoot('/project/root')
30
- * router.scan()
31
- *
32
- * // 生成路由代码
33
- * const routes = await router.generateRoutes()
34
- *
35
- * // 生成类型定义
36
- * const dts = router.generateDts()
37
- * ```
38
- *
39
- * @example
40
- * ```typescript
41
- * // 在 Vite 插件中使用
42
- * const router = new FileRouter(options)
43
- *
44
- * return {
45
- * configResolved(config) {
46
- * router.setRoot(config.root)
47
- * router.scan()
48
- * },
49
- * async load(id) {
50
- * if (id === VIRTUAL_ID) {
51
- * return (await router.generateRoutes()).code
52
- * }
53
- * }
54
- * }
55
- * ```
56
29
  */
57
30
  export class FileRouter {
58
31
  /**
59
32
  * 创建文件路由管理器
60
33
  *
61
34
  * @param options - 配置选项
35
+ * @param [init = true] - 是否初始化加载
62
36
  */
63
- constructor(options = {}) {
37
+ constructor(options = {}, init = true) {
38
+ /**
39
+ * 配置项
40
+ */
64
41
  Object.defineProperty(this, "config", {
65
42
  enumerable: true,
66
43
  configurable: true,
67
44
  writable: true,
68
45
  value: void 0
69
46
  });
70
- Object.defineProperty(this, "_pages", {
71
- enumerable: true,
72
- configurable: true,
73
- writable: true,
74
- value: []
75
- });
76
- Object.defineProperty(this, "_routeTree", {
77
- enumerable: true,
78
- configurable: true,
79
- writable: true,
80
- value: []
81
- });
82
- Object.defineProperty(this, "_cachedRoutesPromise", {
83
- enumerable: true,
84
- configurable: true,
85
- writable: true,
86
- value: null
87
- });
47
+ /**
48
+ * 扫描的节点树
49
+ *
50
+ * @private
51
+ */
52
+ _FileRouter_nodeTree.set(this, void 0);
53
+ /**
54
+ * 文件映射表
55
+ * @private
56
+ */
57
+ _FileRouter_fileMap.set(this, new Map()
58
+ /**
59
+ * 生成结果
60
+ * @private
61
+ */
62
+ );
63
+ /**
64
+ * 生成结果
65
+ * @private
66
+ */
67
+ _FileRouter_generateResult.set(this, null
68
+ /**
69
+ * 创建文件路由管理器
70
+ *
71
+ * @param options - 配置选项
72
+ * @param [init = true] - 是否初始化加载
73
+ */
74
+ );
88
75
  validateOptions(options);
89
76
  this.config = resolveConfig(options);
77
+ __classPrivateFieldSet(this, _FileRouter_nodeTree, init ? this.scanPages() : [], "f");
90
78
  }
91
79
  /**
92
80
  * 获取项目根目录
@@ -94,116 +82,564 @@ export class FileRouter {
94
82
  get root() {
95
83
  return this.config.root;
96
84
  }
85
+ /**
86
+ * 获取节点树
87
+ */
88
+ get nodeTree() {
89
+ return __classPrivateFieldGet(this, _FileRouter_nodeTree, "f");
90
+ }
91
+ /**
92
+ * 获取文件映射表
93
+ *
94
+ * 键为文件或目录路径,值为对应的节点对象
95
+ */
96
+ get fileMap() {
97
+ return __classPrivateFieldGet(this, _FileRouter_fileMap, "f");
98
+ }
99
+ /**
100
+ * 加载/重新加载文件路由管理器
101
+ *
102
+ * @returns {FileRouter} 文件路由管理器实例
103
+ */
104
+ reload() {
105
+ this.clearGenerateResult();
106
+ __classPrivateFieldGet(this, _FileRouter_fileMap, "f").clear();
107
+ __classPrivateFieldSet(this, _FileRouter_nodeTree, this.scanPages(), "f");
108
+ return this;
109
+ }
110
+ /**
111
+ * 构建路由数组
112
+ *
113
+ * @returns 扫描到的页面文件列表
114
+ */
115
+ scanPages() {
116
+ const pages = [];
117
+ for (const page of this.config.pages) {
118
+ if (!existsSync(page.dir)) {
119
+ warn(`Directory ${page.dir} does not exist, please check your configuration.`);
120
+ continue;
121
+ }
122
+ if (page.group && page.prefix) {
123
+ const route = {
124
+ isGroup: true,
125
+ filePath: page.dir,
126
+ path: this.applyPathStrategy(page.prefix)
127
+ };
128
+ route.children = this.scanPageDir({ ...page, prefix: '' }, route);
129
+ // 如果有子路由则添加到页面列表中
130
+ if (route.children.size > 0) {
131
+ pages.push(route);
132
+ this.fileMap.set(route.filePath, route);
133
+ }
134
+ continue;
135
+ }
136
+ const children = this.scanPageDir(page);
137
+ pages.push(...children.values());
138
+ }
139
+ return pages;
140
+ }
97
141
  /**
98
142
  * 扫描页面目录
99
143
  *
100
- * 扫描所有配置的页面目录,解析页面文件并构建路由树。
101
- * 扫描后会清除路由代码缓存。
144
+ * @param page - 页面配置
145
+ * @param parent - 父路由
146
+ * @protected
102
147
  */
103
- scan() {
104
- this._pages = scanMultiplePages({
105
- pages: this.config.pages,
106
- extensions: this.config.extensions,
107
- namingStrategy: this.config.namingStrategy
108
- });
109
- this._routeTree = buildRouteTree(this._pages);
110
- this._cachedRoutesPromise = null;
148
+ scanPageDir(page, parent) {
149
+ const entries = readdirSync(page.dir, { withFileTypes: true });
150
+ // 直接子路由映射,键为文件名,不包含@视图命名,用于合并命名视图到同一个路由对象
151
+ const pageMapping = new Map();
152
+ const children = new Set();
153
+ for (const dirent of entries) {
154
+ const filePath = normalizePathSeparator(nodePath.resolve(dirent.parentPath, dirent.name));
155
+ let route = null;
156
+ if (dirent.isDirectory()) {
157
+ // 处理嵌套子目录
158
+ route = this.processDir(filePath, dirent.name, page, parent);
159
+ }
160
+ else {
161
+ // 处理文件
162
+ route = this.processFile(filePath, page, pageMapping, parent);
163
+ }
164
+ if (route) {
165
+ children.add(route);
166
+ this.fileMap.set(filePath, route);
167
+ }
168
+ }
169
+ // 返回子路由集合
170
+ return children;
111
171
  }
112
172
  /**
113
- * 清除缓存
173
+ * 处理目录
114
174
  *
115
- * 清除路由代码缓存,下次调用 generateRoutes() 会重新生成。
175
+ * @param filePath - 目录路径
176
+ * @param fileName - 目录名
177
+ * @param page - 页面配置
178
+ * @param parent - 父节点
179
+ * @private
116
180
  */
117
- invalidate() {
118
- this._cachedRoutesPromise = null;
181
+ processDir(filePath, fileName, page, parent) {
182
+ const { routePath, options } = this.parseGroupResult(fileName, filePath);
183
+ const pathPrefix = parent ? '' : page.prefix;
184
+ const route = {
185
+ isGroup: true,
186
+ parent,
187
+ filePath,
188
+ path: this.applyPathStrategy(pathPrefix + routePath)
189
+ };
190
+ if (options)
191
+ route.options = options;
192
+ route.children = this.scanPageDir({ ...page, dir: filePath, prefix: '' }, route);
193
+ return route.children.size > 0 ? route : null;
119
194
  }
120
195
  /**
121
- * 检查文件是否为页面文件
196
+ * 解析分组目录的自定义路径和选项
122
197
  *
123
- * @param file - 文件绝对路径
124
- * @returns {boolean} - 是否为页面文件
198
+ * 通过 groupParser 解析目录名,支持返回字符串路径或包含路径与选项的对象。
199
+ *
200
+ * @param fileName - 目录名
201
+ * @param filePath - 目录完整路径
202
+ * @returns 解析后的路径和选项
203
+ * @private
125
204
  */
126
- isPageFile(file) {
127
- return isPageFileInDirs(file, this.config.pages, this.config.extensions);
205
+ parseGroupResult(fileName, filePath) {
206
+ if (!this.config.groupParser) {
207
+ return { routePath: fileName };
208
+ }
209
+ const result = this.config.groupParser(fileName, filePath);
210
+ if (typeof result === 'string') {
211
+ return { routePath: result };
212
+ }
213
+ return { routePath: result.path, options: result.options };
128
214
  }
129
215
  /**
130
- * 获取页面列表
216
+ * 处理文件
217
+ *
218
+ * 根据文件类型分发到对应的处理器。
131
219
  *
132
- * @returns - 解析后的页面列表
220
+ * @param filePath - 文件路径
221
+ * @param page - 页面配置
222
+ * @param pageMapping - 同路径路由映射
223
+ * @param parent - 父节点
224
+ * @private
133
225
  */
134
- getPages() {
135
- return this._pages;
226
+ processFile(filePath, page, pageMapping, parent) {
227
+ const { fileInfo, fileType } = this.resolveFile(filePath, page);
228
+ switch (fileType) {
229
+ case 'ignore':
230
+ return null;
231
+ case 'config':
232
+ this.processConfigFile(filePath, parent);
233
+ return null;
234
+ case 'layout':
235
+ this.processLayoutFile(filePath, fileInfo, parent);
236
+ return null;
237
+ case 'page':
238
+ return this.processPageFile(filePath, fileInfo, page, pageMapping, parent);
239
+ }
136
240
  }
137
241
  /**
138
- * 获取路由树
242
+ * 解析文件信息与类型
139
243
  *
140
- * @returns - 路由树结构
244
+ * 统一入口,避免多处重复调用 extractFileInfo + getPageType。
245
+ *
246
+ * @param filePath - 文件路径
247
+ * @param page - 页面配置
248
+ * @returns 文件信息与类型
141
249
  */
142
- getRouteTree() {
143
- return this._routeTree;
250
+ resolveFile(filePath, page) {
251
+ const fileInfo = extractFileInfo(filePath);
252
+ const fileType = this.getPageType(filePath, fileInfo.rawName, page);
253
+ return { fileInfo, fileType };
144
254
  }
145
255
  /**
146
- * 生成路由代码
256
+ * 处理分组配置文件
147
257
  *
148
- * @returns - 路由生成结果
258
+ * 解析 definePage 宏并合并到父路由选项中。
259
+ *
260
+ * @param filePath - 文件路径
261
+ * @param parent - 父节点
149
262
  */
150
- async generateRoutes() {
151
- if (!this._cachedRoutesPromise) {
152
- this._cachedRoutesPromise = generateRoutes(this._routeTree, {
153
- importMode: this.config.importMode,
154
- extendRoute: this.config.extendRoute,
155
- imports: this.config.injectImports,
156
- namingStrategy: this.config.namingStrategy
157
- });
158
- }
159
- const code = await this._cachedRoutesPromise;
160
- return {
161
- code,
162
- pages: this._pages,
163
- routeTree: this._routeTree
263
+ processConfigFile(filePath, parent) {
264
+ if (!parent)
265
+ return;
266
+ const content = this.readFile(filePath);
267
+ const pageOptions = parseDefinePage(content, filePath);
268
+ if (pageOptions) {
269
+ parent.options = mergePageOptions(parent.options, pageOptions);
270
+ parent.dirConfigFile = filePath;
271
+ this.fileMap.set(filePath, parent);
272
+ }
273
+ }
274
+ /**
275
+ * 处理分组布局文件
276
+ *
277
+ * 将布局组件注册到父路由的 components 中。
278
+ *
279
+ * @param filePath - 文件路径
280
+ * @param fileInfo - 文件信息
281
+ * @param parent - 父节点
282
+ */
283
+ processLayoutFile(filePath, fileInfo, parent) {
284
+ if (!parent)
285
+ return;
286
+ const content = this.readFile(filePath);
287
+ if (checkDefaultExport(content, filePath)) {
288
+ parent.components ?? (parent.components = {});
289
+ parent.components[fileInfo.viewName ?? 'default'] = filePath;
290
+ }
291
+ this.fileMap.set(filePath, parent);
292
+ }
293
+ /**
294
+ * 处理页面文件
295
+ *
296
+ * 解析路由路径、视图命名和页面选项,创建或合并路由节点。
297
+ *
298
+ * @param filePath - 文件路径
299
+ * @param fileInfo - 文件信息
300
+ * @param page - 页面配置
301
+ * @param pageMapping - 同路径路由映射
302
+ * @param parent - 父节点
303
+ * @param [precomputedParsed] - 预计算的解析结果,避免重复调用 parsePageFile
304
+ * @returns 新创建的路由节点,或 null(合并到已有路由时)
305
+ */
306
+ processPageFile(filePath, fileInfo, page, pageMapping, parent, precomputedParsed) {
307
+ const parsed = precomputedParsed ?? parsePageFile(filePath, this.config.pageParser, fileInfo);
308
+ const viewName = parsed.viewName ?? 'default';
309
+ const content = this.readFile(filePath);
310
+ const sameRoute = pageMapping.get(parsed.path);
311
+ if (sameRoute) {
312
+ if (!checkDefaultExport(content, filePath))
313
+ return null;
314
+ sameRoute.components[viewName] = filePath;
315
+ this.fileMap.set(filePath, sameRoute);
316
+ return null;
317
+ }
318
+ const definePageOptions = parseDefinePage(content, filePath);
319
+ const pageOptions = mergePageOptions(parsed.options, definePageOptions);
320
+ if (!pageOptions.redirect && !checkDefaultExport(content, filePath))
321
+ return null;
322
+ const finalPath = this.applyPathStrategy((parent ? '' : page.prefix) + (parsed.path === 'index' ? '' : parsed.path));
323
+ const route = {
324
+ isGroup: false,
325
+ parent,
326
+ filePath,
327
+ path: finalPath,
328
+ components: {
329
+ [viewName]: filePath
330
+ }
164
331
  };
332
+ if (Object.keys(pageOptions).length)
333
+ route.options = pageOptions;
334
+ pageMapping.set(parsed.path, route);
335
+ return route;
336
+ }
337
+ /**
338
+ * 在已有路由树中查找同路径路由
339
+ *
340
+ * 用于 addPage 场景:新增文件时需要检查是否已存在同路径的路由节点,
341
+ * 以便将命名视图合并到已有路由而非创建重复路由。
342
+ *
343
+ * @param pathKey - 标准化后的路由路径
344
+ * @param prefix - 路径前缀
345
+ * @param parent - 父节点
346
+ * @returns 同路径的路由节点,未找到返回 null
347
+ */
348
+ findSameRoute(pathKey, prefix, parent) {
349
+ const newRoutePath = this.applyPathStrategy(prefix + pathKey);
350
+ const pages = parent ? parent.children : this.nodeTree;
351
+ for (const route of pages) {
352
+ if (route.path === newRoutePath)
353
+ return route;
354
+ }
355
+ return null;
356
+ }
357
+ /**
358
+ * 应用路径策略
359
+ *
360
+ * @param path - 路径
361
+ * @protected
362
+ */
363
+ applyPathStrategy(path) {
364
+ return resolvePathVariable(applyPathStrategy(path, this.config.pathStrategy));
365
+ }
366
+ /**
367
+ * 读取文件内容
368
+ *
369
+ * @param file - 文件绝对路径
370
+ */
371
+ readFile(file) {
372
+ return readFileContent(file, this.config.transform);
373
+ }
374
+ /**
375
+ * 获取文件类型
376
+ *
377
+ * @param file - 文件绝对路径
378
+ * @param rawName - 文件名(不含扩展名和 @视图命名)
379
+ * @param pages - 页面配置
380
+ * @returns 文件类型
381
+ */
382
+ getPageType(file, rawName, pages) {
383
+ if (rawName === this.config.layoutFileName) {
384
+ return 'layout';
385
+ }
386
+ if (rawName === this.config.configFileName && (file.endsWith('.ts') || file.endsWith('.js'))) {
387
+ return 'config';
388
+ }
389
+ if (this.isPageFile(file, pages)) {
390
+ return 'page';
391
+ }
392
+ return 'ignore';
165
393
  }
166
394
  /**
167
- * 生成类型定义
395
+ * 检查文件是否为页面文件
396
+ *
397
+ * @param file - 文件绝对路径
398
+ * @param filter - 过滤配置,默认为 `config.pages`
399
+ * @returns {boolean} - 是否为页面文件
400
+ */
401
+ isPageFile(file, filter) {
402
+ if (filter) {
403
+ if (Array.isArray(filter)) {
404
+ return !!isPageFileInDirs(file, filter);
405
+ }
406
+ else {
407
+ return isPageFile(file, filter);
408
+ }
409
+ }
410
+ return !!isPageFileInDirs(file, this.config.pages);
411
+ }
412
+ /**
413
+ * 获取文件的完整路由路径
414
+ *
415
+ * 判断文件是否为页面文件,如果是则计算其最终生成的路由 fullPath。
416
+ * 非页面文件(布局文件、配置文件等)返回 null。
168
417
  *
169
- * @returns - 类型定义内容
418
+ * @param filePath - 文件绝对路径
419
+ * @returns 完整路由路径,非页面文件返回 null
170
420
  */
171
- generateDts() {
172
- return generateFullDtsFile(this._routeTree);
421
+ getRouteFullPath(filePath) {
422
+ if (!this.isPageFile(filePath))
423
+ return null;
424
+ const fileInfo = extractFileInfo(filePath);
425
+ const fileType = this.getPageType(filePath, fileInfo.rawName);
426
+ if (fileType !== 'page')
427
+ return null;
428
+ return computeRouteFullPath(filePath, fileInfo, {
429
+ fileMap: this.fileMap,
430
+ pages: this.config.pages,
431
+ pageParser: this.config.pageParser,
432
+ pathStrategy: this.config.pathStrategy
433
+ });
173
434
  }
174
435
  /**
175
436
  * 写入类型定义文件
437
+ */
438
+ writeDts(content) {
439
+ const dtsPath = this.config.dts;
440
+ if (!dtsPath)
441
+ return void 0;
442
+ const absolutePath = nodePath.isAbsolute(dtsPath)
443
+ ? dtsPath
444
+ : nodePath.resolve(this.root, dtsPath);
445
+ const dtsDir = nodePath.dirname(absolutePath);
446
+ if (!existsSync(dtsDir)) {
447
+ mkdirSync(dtsDir, { recursive: true });
448
+ }
449
+ writeFileSync(absolutePath, content, 'utf-8');
450
+ }
451
+ /**
452
+ * 生成路由
176
453
  *
177
- * @param dtsPath - 类型定义文件路径,可以是绝对路径或相对于项目根目录的相对路径
178
- * @returns 写入结果信息
454
+ * @returns {GenerateResult} 生成结果,包含routes、dts、code
179
455
  */
180
- writeDts(dtsPath) {
181
- const absolutePath = path.isAbsolute(dtsPath) ? dtsPath : path.resolve(this.root, dtsPath);
182
- const dtsDir = path.dirname(absolutePath);
183
- if (!fs.existsSync(dtsDir)) {
184
- fs.mkdirSync(dtsDir, { recursive: true });
456
+ generate() {
457
+ if (!__classPrivateFieldGet(this, _FileRouter_generateResult, "f")) {
458
+ __classPrivateFieldSet(this, _FileRouter_generateResult, generateRoutes(this.nodeTree, {
459
+ imports: this.config.injectImports,
460
+ extendRoute: this.config.extendRoute,
461
+ beforeWriteRoutes: this.config.beforeWriteRoutes,
462
+ importMode: this.config.importMode,
463
+ dts: !!this.config.dts
464
+ }), "f");
465
+ this.writeDts(__classPrivateFieldGet(this, _FileRouter_generateResult, "f").dts);
185
466
  }
186
- const content = this.generateDts();
187
- fs.writeFileSync(absolutePath, content, 'utf-8');
188
- return {
189
- content,
190
- path: absolutePath
191
- };
467
+ return __classPrivateFieldGet(this, _FileRouter_generateResult, "f");
468
+ }
469
+ /**
470
+ * 清空生成结果
471
+ */
472
+ clearGenerateResult() {
473
+ __classPrivateFieldSet(this, _FileRouter_generateResult, null, "f");
192
474
  }
193
475
  /**
194
476
  * 移除 definePage 宏
195
477
  *
196
- * 在构建模式下移除页面文件中的 definePage 宏调用。
197
- * definePage 作为全局宏使用,无需导入。
478
+ * 移除页面文件中的 definePage 宏调用。
479
+ * definePage 在客户端无法运行,必须移除。
198
480
  *
199
481
  * @param code - 源代码
200
482
  * @param filePath - 文件路径
201
483
  * @returns 转换后的代码,无需转换返回 null
202
484
  */
203
485
  removeDefinePage(code, filePath) {
204
- if (!this.isPageFile(filePath)) {
205
- return null;
206
- }
207
486
  return removeDefinePage(code, filePath);
208
487
  }
488
+ /**
489
+ * 添加页面文件
490
+ *
491
+ * 根据文件类型直接调用对应处理器,避免重复类型判断和文件解析。
492
+ *
493
+ * @param filePath - 文件路径
494
+ * @returns 是否创建了新的路由节点
495
+ */
496
+ addPage(filePath) {
497
+ const page = isPageFileInDirs(filePath, this.config.pages);
498
+ if (!page)
499
+ return false;
500
+ const dirPath = nodePath.dirname(filePath);
501
+ const parent = this.fileMap.get(dirPath);
502
+ const prefix = parent ? '' : page.prefix;
503
+ const { fileInfo, fileType } = this.resolveFile(filePath, page);
504
+ const pageConfig = {
505
+ dir: dirPath,
506
+ include: page.include,
507
+ exclude: page.exclude,
508
+ prefix
509
+ };
510
+ switch (fileType) {
511
+ case 'ignore':
512
+ return false;
513
+ case 'config':
514
+ this.processConfigFile(filePath, parent);
515
+ return false;
516
+ case 'layout':
517
+ this.processLayoutFile(filePath, fileInfo, parent);
518
+ return false;
519
+ case 'page': {
520
+ const parsed = parsePageFile(filePath, this.config.pageParser, fileInfo);
521
+ const pageMapping = new Map();
522
+ const sameRoute = this.findSameRoute(parsed.path, prefix, parent);
523
+ if (sameRoute) {
524
+ pageMapping.set(parsed.path, sameRoute);
525
+ }
526
+ const route = this.processPageFile(filePath, fileInfo, pageConfig, pageMapping, parent, parsed);
527
+ if (route) {
528
+ this.fileMap.set(filePath, route);
529
+ return true;
530
+ }
531
+ return false;
532
+ }
533
+ }
534
+ }
535
+ /**
536
+ * 移除指定的文件或目录
537
+ *
538
+ * 通常用于在开发模式下,文件内容改变时,移除旧的路由映射,重新生成
539
+ *
540
+ * 移除成功后续手动调用 `clearGenerateResult` 方法确保下一次获取新的生成结果!
541
+ *
542
+ * @param filePath - 文件/目录路径
543
+ * @returns {boolean} - 存在则移除并返回 true,不存在则返回 false
544
+ */
545
+ removePage(filePath) {
546
+ const route = this.fileMap.get(filePath);
547
+ if (route) {
548
+ // 移除组件映射,其中包含页面文件自身以及命名文件
549
+ if (route.components) {
550
+ Object.values(route.components).forEach(file => {
551
+ this.fileMap.delete(file);
552
+ });
553
+ }
554
+ // 移除配置文件映射
555
+ if (route.dirConfigFile) {
556
+ this.fileMap.delete(route.dirConfigFile);
557
+ }
558
+ // 递归移除子文件
559
+ if (route.children) {
560
+ // 分组路由需要移除自身的映射
561
+ this.fileMap.delete(filePath);
562
+ route.children.forEach(child => {
563
+ this.removePage(child.filePath);
564
+ });
565
+ }
566
+ // 从父级移除
567
+ route.parent?.children?.delete(route);
568
+ return true;
569
+ }
570
+ return false;
571
+ }
572
+ /**
573
+ * 更新文件
574
+ *
575
+ * 如果文件未被扫描,则会自动添加。
576
+ *
577
+ * @param filePath - 文件路径
578
+ * @returns {boolean} - 是否更新了文件
579
+ */
580
+ updatePage(filePath) {
581
+ const route = this.fileMap.get(filePath);
582
+ if (route) {
583
+ const content = this.readFile(filePath);
584
+ // 解析页面选项
585
+ const newOptions = parseDefinePage(content, filePath);
586
+ // 忽略不具备默认导出,且无重定向配置的文件
587
+ if (!newOptions?.redirect && !checkDefaultExport(content, filePath)) {
588
+ // 移除文件
589
+ this.removePage(filePath);
590
+ }
591
+ if (isEqualPageOptions(route.options, newOptions)) {
592
+ return false;
593
+ }
594
+ if (newOptions) {
595
+ route.options = newOptions;
596
+ }
597
+ else {
598
+ delete route.options;
599
+ }
600
+ return true;
601
+ }
602
+ return this.addPage(filePath);
603
+ }
604
+ /**
605
+ * 处理文件变化
606
+ *
607
+ * @param eventName - 文件变化事件名
608
+ * @param path - 文件路径
609
+ * @returns {boolean} - 是否影响了路由
610
+ * @example
611
+ * ```typescript
612
+ * router.handleChange('change', '/path/to/file.ts')
613
+ * // 在vite中使用
614
+ * server.watcher.on('all', (event,path) => {
615
+ * const mod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_ID)
616
+ * if (mod) {
617
+ * const result = router.handleChange(event, path)
618
+ * // 如果 handleChange 返回 true,则表示路由受到影响,需要更新模块
619
+ * if(result) server.moduleGraph.invalidateModule(mod)
620
+ * }
621
+ * })
622
+ * ```
623
+ */
624
+ handleChange(eventName, path) {
625
+ let result;
626
+ switch (eventName) {
627
+ case 'unlinkDir':
628
+ case 'unlink':
629
+ result = this.removePage(path);
630
+ break;
631
+ case 'change':
632
+ result = this.updatePage(path);
633
+ break;
634
+ default:
635
+ result = false;
636
+ }
637
+ if (result) {
638
+ this.clearGenerateResult();
639
+ const relativePath = nodePath.relative(this.root, path);
640
+ info(`✨ Route updated: ${relativePath}`);
641
+ }
642
+ return result;
643
+ }
209
644
  }
645
+ _FileRouter_nodeTree = new WeakMap(), _FileRouter_fileMap = new WeakMap(), _FileRouter_generateResult = new WeakMap();