vitarx-router 4.0.0-beta.27 → 4.0.0-beta.28

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.
@@ -1,4 +1,5 @@
1
1
  import type { Route, Router } from '../core/index.js';
2
+ export { default as routes } from 'virtual:vitarx-router:routes';
2
3
  /**
3
4
  * 处理路由热更新函数
4
5
  *
@@ -1,3 +1,4 @@
1
+ export { default as routes } from 'virtual:vitarx-router:routes';
1
2
  /**
2
3
  * 处理路由热更新函数
3
4
  *
@@ -32,9 +33,10 @@
32
33
  */
33
34
  export function handleHotUpdate(router, onRoutesUpdated) {
34
35
  if (import.meta.hot) {
35
- import.meta.hot.on('vitarx-router:routes-change', async () => {
36
- const modules = await import('virtual:vitarx-router:routes');
37
- const routes = modules.default;
36
+ import.meta.hot.accept('virtual:vitarx-router:routes', newModule => {
37
+ if (!newModule)
38
+ return;
39
+ const routes = newModule.default;
38
40
  if (routes && typeof routes === 'object') {
39
41
  router.manager.clearRoutes();
40
42
  for (const route of routes) {
@@ -44,6 +46,15 @@ export function handleHotUpdate(router, onRoutesUpdated) {
44
46
  if (onRoutesUpdated) {
45
47
  onRoutesUpdated(routes);
46
48
  }
49
+ // 强制重新导航到当前路由
50
+ router
51
+ .replace({
52
+ index: router.route.matched.at(-1)?.name || router.route.path,
53
+ params: { ...router.route.params },
54
+ query: { ...router.route.query },
55
+ hash: router.route.hash
56
+ })
57
+ .then();
47
58
  }
48
59
  });
49
60
  }
@@ -15,5 +15,4 @@
15
15
  * }
16
16
  * ```
17
17
  */
18
- export { default as routes } from 'virtual:vitarx-router:routes';
19
- export { handleHotUpdate } from './handleHotUpdate.js';
18
+ export { handleHotUpdate, routes } from './handleHotUpdate.js';
@@ -15,5 +15,4 @@
15
15
  * }
16
16
  * ```
17
17
  */
18
- export { default as routes } from 'virtual:vitarx-router:routes';
19
- export { handleHotUpdate } from './handleHotUpdate.js';
18
+ export { handleHotUpdate, routes } from './handleHotUpdate.js';
@@ -19,8 +19,8 @@ import { extractFileInfo } from '../parser/parsePage.js';
19
19
  import { computeRouteFullPath } from '../parser/routePath.js';
20
20
  import { applyPathStrategy, info, readFileContent, resolvePathVariable, validateOptions } from '../utils/index.js';
21
21
  import { checkIsPageFile, getPageType } from './file-classifier.js';
22
- import { scanPages } from './scanner.js';
23
22
  import { addPage, removePage, updatePage } from './route-updater.js';
23
+ import { scanPages } from './scanner.js';
24
24
  /**
25
25
  * 文件路由管理器
26
26
  *
@@ -1,5 +1,5 @@
1
- import { type ProcessorContext } from './file-processor.js';
2
1
  import type { ScanNode } from '../types/index.js';
2
+ import { type ProcessorContext } from './file-processor.js';
3
3
  /**
4
4
  * 增量更新上下文
5
5
  *
@@ -9,11 +9,11 @@
9
9
  * 仅修改受影响的路由节点,保持其余路由不变。
10
10
  */
11
11
  import nodePath from 'node:path';
12
- import { processConfigFile, processLayoutFile, processPageFile } from './file-processor.js';
13
- import { resolveFile } from './file-classifier.js';
14
12
  import { isEqualPageOptions, parseDefinePage } from '../macros/index.js';
15
13
  import { checkDefaultExport, isPageFileInDirs } from '../parser/index.js';
16
14
  import { parsePageFile } from '../parser/parsePage.js';
15
+ import { resolveFile } from './file-classifier.js';
16
+ import { processConfigFile, processLayoutFile, processPageFile } from './file-processor.js';
17
17
  /**
18
18
  * 添加页面文件到路由树
19
19
  *
@@ -185,7 +185,7 @@ export function updatePage(filePath, context) {
185
185
  return true;
186
186
  }
187
187
  // 选项未变化,无需更新
188
- if (isEqualPageOptions(route.options, newOptions)) {
188
+ if (isEqualPageOptions(route.options || null, newOptions)) {
189
189
  return false;
190
190
  }
191
191
  // 更新路由选项
@@ -1,4 +1,4 @@
1
1
  import vitarxRouter from './plugin.js';
2
2
  export * from './constant.js';
3
- export * from './watcher.js';
3
+ export * from './utils.js';
4
4
  export default vitarxRouter;
@@ -1,4 +1,4 @@
1
1
  import vitarxRouter from './plugin.js';
2
2
  export * from './constant.js';
3
- export * from './watcher.js';
3
+ export * from './utils.js';
4
4
  export default vitarxRouter;
@@ -1,6 +1,6 @@
1
1
  import { FileRouter, info, warn } from '../file-router/index.js';
2
2
  import { RESOLVED_ROUTES_ID, VIRTUAL_ROUTES_ID } from './constant.js';
3
- import { setupWatcher } from './watcher.js';
3
+ import { invalidateVirtualModule, setupWatcher } from './utils.js';
4
4
  /** 插件名称,用于日志输出和调试 */
5
5
  const PLUGIN_NAME = 'vite-plugin-vitarx-router';
6
6
  /**
@@ -155,14 +155,7 @@ export default function vitarxRouter(options = {}) {
155
155
  const isRouteAffected = router.handleChange(event, file);
156
156
  if (!isRouteAffected)
157
157
  return;
158
- // 1. 获取虚拟模块在依赖图中的节点
159
- const virtualMod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_ID);
160
- if (!virtualMod || virtualMod.importers.size === 0)
161
- return;
162
- // 2. 使虚拟模块缓存失效
163
- server.moduleGraph.invalidateModule(virtualMod);
164
- // 3. 向客户端发送自定义 HMR 更新信号
165
- server.ws.send({ type: 'custom', event: 'vitarx-router:routes-change' });
158
+ invalidateVirtualModule(server, VIRTUAL_ROUTES_ID);
166
159
  }
167
160
  catch (error) {
168
161
  warn(`Failed to handle file change for ${file}:`, error);
@@ -0,0 +1,47 @@
1
+ import type { ModuleNode, ViteDevServer } from 'vite';
2
+ import { FileRouter, type FileWatcherEvent } from '../file-router/index.js';
3
+ /**
4
+ * 设置文件监听器
5
+ *
6
+ * 为开发服务器配置文件监听,监听页面目录下的文件变化。
7
+ * 当文件发生变化时,自动更新路由配置并触发 HMR。
8
+ *
9
+ * @param router - 文件路由管理器实例
10
+ * @param server - Vite 开发服务器实例
11
+ * @param handleFileChange - 文件变化处理函数
12
+ * @param delay - 节流延迟时间(毫秒)
13
+ * @returns 文件监听器处理函数,用于后续清理
14
+ */
15
+ export declare function setupWatcher(router: FileRouter, server: ViteDevServer, handleFileChange: (event: FileWatcherEvent, file: string) => void, delay?: number): (event: FileWatcherEvent, file: string) => void;
16
+ /**
17
+ * 递归收集模块的所有物理文件依赖者
18
+ *
19
+ * 从指定模块出发,沿着 importer 链向上递归遍历,
20
+ * 收集所有物理文件模块(排除虚拟模块和带查询参数的模块)。
21
+ * 用于 HMR 更新时找到所有需要接收更新通知的物理文件。
22
+ *
23
+ * 例如对于依赖链:`router/index.ts → auto-routes/index.ts → virtual:routes`
24
+ * 从 `virtual:routes` 出发,会收集到 `auto-routes/index.ts` 和 `router/index.ts`
25
+ *
26
+ * @param mod - 起始模块节点
27
+ * @param [visited] - 已访问模块集合,防止循环引用
28
+ * @returns 物理文件模块节点数组
29
+ */
30
+ export declare function collectPhysicalImporters(mod: ModuleNode, visited?: Set<ModuleNode>): ModuleNode[];
31
+ /**
32
+ * 触发虚拟路由模块的 HMR 更新
33
+ *
34
+ * 使虚拟模块缓存失效,并递归收集所有物理文件依赖者,
35
+ * 向它们发送 HMR 更新信号,使客户端能接收到路由变化。
36
+ *
37
+ * 处理流程:
38
+ * 1. 获取虚拟模块在依赖图中的节点
39
+ * 2. 使虚拟模块缓存失效
40
+ * 3. 递归收集所有物理文件依赖者(支持 barrel 文件 re-export 场景)
41
+ * 4. 向每个物理文件依赖者发送 js-update 信号
42
+ *
43
+ * @param server - Vite 开发服务器实例
44
+ * @param virtualId - 虚拟模块的原始 ID(如 `virtual:vitarx-router:routes`)
45
+ * @returns 是否成功发送了 HMR 更新
46
+ */
47
+ export declare function invalidateVirtualModule(server: ViteDevServer, virtualId: string): boolean;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * 创建批量节流处理函数
3
+ *
4
+ * 在指定时间窗口内收集所有变化事件,然后批量处理。
5
+ * 同一文件的多次变更会被合并,只保留最后一次事件。
6
+ * 这可以避免在短时间内多次触发路由重新生成,同时确保
7
+ * 所有文件变化都被正确处理,不会丢失任何更新。
8
+ *
9
+ * @param callback - 需要节流的回调函数
10
+ * @param delay - 节流延迟时间(毫秒)
11
+ * @returns 节流后的处理函数
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const batchHandler = createBatchThrottledHandler(
16
+ * (event, file) => console.log(event, file),
17
+ * 100
18
+ * )
19
+ *
20
+ * // 快速调用多次,同文件变更会被合并
21
+ * batchHandler('change', 'a.ts')
22
+ * batchHandler('change', 'b.ts')
23
+ * batchHandler('unlink', 'a.ts') // 覆盖之前的 a.ts change
24
+ * // 100ms 后,只处理 a.ts unlink 和 b.ts change
25
+ * ```
26
+ */
27
+ function createBatchThrottledHandler(callback, delay) {
28
+ let pending = false;
29
+ const pendingChanges = new Map();
30
+ return (event, file) => {
31
+ pendingChanges.set(file, event);
32
+ if (pending)
33
+ return;
34
+ pending = true;
35
+ setTimeout(() => {
36
+ pending = false;
37
+ const changes = new Map(pendingChanges);
38
+ pendingChanges.clear();
39
+ for (const [f, e] of changes) {
40
+ callback(e, f);
41
+ }
42
+ }, delay);
43
+ };
44
+ }
45
+ /**
46
+ * 设置文件监听器
47
+ *
48
+ * 为开发服务器配置文件监听,监听页面目录下的文件变化。
49
+ * 当文件发生变化时,自动更新路由配置并触发 HMR。
50
+ *
51
+ * @param router - 文件路由管理器实例
52
+ * @param server - Vite 开发服务器实例
53
+ * @param handleFileChange - 文件变化处理函数
54
+ * @param delay - 节流延迟时间(毫秒)
55
+ * @returns 文件监听器处理函数,用于后续清理
56
+ */
57
+ export function setupWatcher(router, server, handleFileChange, delay = 100) {
58
+ const pagesDirs = router.config.pages;
59
+ for (const dirConfig of pagesDirs) {
60
+ server.watcher.add(dirConfig.dir);
61
+ }
62
+ const handler = createBatchThrottledHandler(handleFileChange, delay);
63
+ server.watcher.on('all', handler);
64
+ return handler;
65
+ }
66
+ /**
67
+ * 递归收集模块的所有物理文件依赖者
68
+ *
69
+ * 从指定模块出发,沿着 importer 链向上递归遍历,
70
+ * 收集所有物理文件模块(排除虚拟模块和带查询参数的模块)。
71
+ * 用于 HMR 更新时找到所有需要接收更新通知的物理文件。
72
+ *
73
+ * 例如对于依赖链:`router/index.ts → auto-routes/index.ts → virtual:routes`
74
+ * 从 `virtual:routes` 出发,会收集到 `auto-routes/index.ts` 和 `router/index.ts`
75
+ *
76
+ * @param mod - 起始模块节点
77
+ * @param [visited] - 已访问模块集合,防止循环引用
78
+ * @returns 物理文件模块节点数组
79
+ */
80
+ export function collectPhysicalImporters(mod, visited = new Set()) {
81
+ const result = [];
82
+ for (const importer of mod.importers) {
83
+ if (visited.has(importer))
84
+ continue;
85
+ visited.add(importer);
86
+ const isPhysical = !importer.url.startsWith('\0') && !importer.url.includes('?');
87
+ if (isPhysical) {
88
+ result.push(importer);
89
+ }
90
+ // 继续向上递归查找(无论当前是否为物理文件,都继续遍历)
91
+ result.push(...collectPhysicalImporters(importer, visited));
92
+ }
93
+ return result;
94
+ }
95
+ /**
96
+ * 触发虚拟路由模块的 HMR 更新
97
+ *
98
+ * 使虚拟模块缓存失效,并递归收集所有物理文件依赖者,
99
+ * 向它们发送 HMR 更新信号,使客户端能接收到路由变化。
100
+ *
101
+ * 处理流程:
102
+ * 1. 获取虚拟模块在依赖图中的节点
103
+ * 2. 使虚拟模块缓存失效
104
+ * 3. 递归收集所有物理文件依赖者(支持 barrel 文件 re-export 场景)
105
+ * 4. 向每个物理文件依赖者发送 js-update 信号
106
+ *
107
+ * @param server - Vite 开发服务器实例
108
+ * @param virtualId - 虚拟模块的原始 ID(如 `virtual:vitarx-router:routes`)
109
+ * @returns 是否成功发送了 HMR 更新
110
+ */
111
+ export function invalidateVirtualModule(server, virtualId) {
112
+ // 1. 获取虚拟模块在依赖图中的节点
113
+ const virtualMod = server.moduleGraph.getModuleById(`\0${virtualId}`);
114
+ if (!virtualMod || virtualMod.importers.size === 0)
115
+ return false;
116
+ // 2. 使虚拟模块缓存失效
117
+ server.moduleGraph.invalidateModule(virtualMod);
118
+ // 3. 递归收集虚拟模块的所有物理文件依赖者
119
+ const physicalImporters = collectPhysicalImporters(virtualMod);
120
+ if (physicalImporters.length === 0)
121
+ return false;
122
+ // 4. 向每个物理文件依赖者发送 HMR 更新信号
123
+ // acceptedPath 必须与 Vite 客户端内部的模块 ID 转换规则一致:
124
+ // 客户端 import.meta.hot.accept('virtual:xxx', cb) 注册时,
125
+ // Vite 会将 'virtual:xxx' 转换为 '/@id/__x00__virtual:xxx' 作为 accept 的 key,
126
+ // 所以 HMR update 中的 acceptedPath 也必须使用这个转换后的路径。
127
+ // 注意:/@id/ 和 __x00__ 是 Vite 内部约定,非公开 API,升级时需验证。
128
+ const acceptedPath = `/@id/__x00__${virtualId}`;
129
+ const timestamp = Date.now();
130
+ const updates = physicalImporters.map(importer => ({
131
+ type: 'js-update',
132
+ path: importer.url,
133
+ acceptedPath,
134
+ timestamp
135
+ }));
136
+ server.ws.send({ type: 'update', updates });
137
+ return true;
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitarx-router",
3
- "version": "4.0.0-beta.27",
3
+ "version": "4.0.0-beta.28",
4
4
  "description": "Official routing solution for Vitarx framework with declarative routing, navigation guards, dynamic routes, file-based routing with HMR, and full TypeScript support.",
5
5
  "author": "ZhuChonglin <8210856@qq.com>",
6
6
  "license": "MIT",
@@ -1,15 +0,0 @@
1
- import type { ViteDevServer } from 'vite';
2
- import { FileRouter, type FileWatcherEvent } from '../file-router/index.js';
3
- /**
4
- * 设置文件监听器
5
- *
6
- * 为开发服务器配置文件监听,监听页面目录下的文件变化。
7
- * 当文件发生变化时,自动更新路由配置并触发 HMR。
8
- *
9
- * @param router - 文件路由管理器实例
10
- * @param server - Vite 开发服务器实例
11
- * @param handleFileChange - 文件变化处理函数
12
- * @param delay - 节流延迟时间(毫秒)
13
- * @returns 文件监听器处理函数,用于后续清理
14
- */
15
- export declare function setupWatcher(router: FileRouter, server: ViteDevServer, handleFileChange: (event: FileWatcherEvent, file: string) => void, delay?: number): (event: FileWatcherEvent, file: string) => void;
@@ -1,65 +0,0 @@
1
- /**
2
- * 创建批量节流处理函数
3
- *
4
- * 在指定时间窗口内收集所有变化事件,然后批量处理。
5
- * 同一文件的多次变更会被合并,只保留最后一次事件。
6
- * 这可以避免在短时间内多次触发路由重新生成,同时确保
7
- * 所有文件变化都被正确处理,不会丢失任何更新。
8
- *
9
- * @param callback - 需要节流的回调函数
10
- * @param delay - 节流延迟时间(毫秒)
11
- * @returns 节流后的处理函数
12
- *
13
- * @example
14
- * ```typescript
15
- * const batchHandler = createBatchThrottledHandler(
16
- * (event, file) => console.log(event, file),
17
- * 100
18
- * )
19
- *
20
- * // 快速调用多次,同文件变更会被合并
21
- * batchHandler('change', 'a.ts')
22
- * batchHandler('change', 'b.ts')
23
- * batchHandler('unlink', 'a.ts') // 覆盖之前的 a.ts change
24
- * // 100ms 后,只处理 a.ts unlink 和 b.ts change
25
- * ```
26
- */
27
- function createBatchThrottledHandler(callback, delay) {
28
- let pending = false;
29
- const pendingChanges = new Map();
30
- return (event, file) => {
31
- pendingChanges.set(file, event);
32
- if (pending)
33
- return;
34
- pending = true;
35
- setTimeout(() => {
36
- pending = false;
37
- const changes = new Map(pendingChanges);
38
- pendingChanges.clear();
39
- for (const [f, e] of changes) {
40
- callback(e, f);
41
- }
42
- }, delay);
43
- };
44
- }
45
- /**
46
- * 设置文件监听器
47
- *
48
- * 为开发服务器配置文件监听,监听页面目录下的文件变化。
49
- * 当文件发生变化时,自动更新路由配置并触发 HMR。
50
- *
51
- * @param router - 文件路由管理器实例
52
- * @param server - Vite 开发服务器实例
53
- * @param handleFileChange - 文件变化处理函数
54
- * @param delay - 节流延迟时间(毫秒)
55
- * @returns 文件监听器处理函数,用于后续清理
56
- */
57
- export function setupWatcher(router, server, handleFileChange, delay = 100) {
58
- const pagesDirs = router.config.pages;
59
- for (const dirConfig of pagesDirs) {
60
- server.watcher.add(dirConfig.dir);
61
- }
62
- const handler = createBatchThrottledHandler(handleFileChange, delay);
63
- server.watcher.on('all', handler);
64
- return handler;
65
- }