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.
- package/dist/auto-routes/handleHotUpdate.d.ts +1 -0
- package/dist/auto-routes/handleHotUpdate.js +14 -3
- package/dist/auto-routes/index.d.ts +1 -2
- package/dist/auto-routes/index.js +1 -2
- package/dist/file-router/manager/index.js +1 -1
- package/dist/file-router/manager/route-updater.d.ts +1 -1
- package/dist/file-router/manager/route-updater.js +3 -3
- package/dist/plugin-vite/index.d.ts +1 -1
- package/dist/plugin-vite/index.js +1 -1
- package/dist/plugin-vite/plugin.js +2 -9
- package/dist/plugin-vite/utils.d.ts +47 -0
- package/dist/plugin-vite/utils.js +138 -0
- package/package.json +1 -1
- package/dist/plugin-vite/watcher.d.ts +0 -15
- package/dist/plugin-vite/watcher.js +0 -65
|
@@ -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.
|
|
36
|
-
|
|
37
|
-
|
|
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
|
}
|
|
@@ -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
|
*
|
|
@@ -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,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 './
|
|
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
|
-
|
|
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.
|
|
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
|
-
}
|