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.
- package/README.md +42 -17
- package/dist/{plugin-vite/auto-routes → auto-routes}/handleHotUpdate.d.ts +1 -1
- package/dist/components/RouterView.js +5 -4
- package/dist/core/common/constant.d.ts +5 -6
- package/dist/core/common/constant.js +5 -6
- package/dist/core/common/utils.js +2 -1
- package/dist/core/router/checkOptions.d.ts +11 -0
- package/dist/core/router/checkOptions.js +119 -0
- package/dist/core/router/manager.js +27 -23
- package/dist/core/router/router.d.ts +154 -1
- package/dist/core/router/router.js +303 -230
- package/dist/core/router/web.d.ts +13 -0
- package/dist/core/router/web.js +35 -4
- package/dist/core/shared/link.d.ts +7 -0
- package/dist/core/shared/link.js +11 -8
- package/dist/core/shared/route.js +1 -2
- package/dist/core/shared/router.d.ts +3 -3
- package/dist/core/shared/router.js +7 -4
- package/dist/core/types/options.d.ts +2 -0
- package/dist/file-router/config/index.d.ts +2 -1
- package/dist/file-router/config/index.js +2 -1
- package/dist/file-router/config/resolve.d.ts +43 -0
- package/dist/file-router/config/resolve.js +69 -0
- package/dist/file-router/{utils/validateOptions.d.ts → config/validate.d.ts} +11 -10
- package/dist/file-router/config/validate.js +280 -0
- package/dist/file-router/constants.d.ts +12 -2
- package/dist/file-router/constants.js +13 -3
- package/dist/file-router/generator/generateRoutes.d.ts +44 -13
- package/dist/file-router/generator/generateRoutes.js +159 -80
- package/dist/file-router/generator/generateTypes.d.ts +3 -29
- package/dist/file-router/generator/generateTypes.js +36 -41
- package/dist/file-router/global.d.ts +1 -1
- package/dist/file-router/index.d.ts +224 -90
- package/dist/file-router/index.js +571 -135
- package/dist/file-router/macros/astValueExtractor.d.ts +1 -1
- package/dist/file-router/macros/astValueExtractor.js +27 -7
- package/dist/file-router/macros/definePage.d.ts +20 -3
- package/dist/file-router/macros/definePage.js +120 -40
- package/dist/file-router/parser/exportChecker.d.ts +4 -23
- package/dist/file-router/parser/exportChecker.js +38 -79
- package/dist/file-router/parser/filterUtils.d.ts +25 -0
- package/dist/file-router/parser/filterUtils.js +43 -0
- package/dist/file-router/parser/index.d.ts +2 -1
- package/dist/file-router/parser/index.js +2 -1
- package/dist/file-router/parser/parsePage.d.ts +56 -9
- package/dist/file-router/parser/parsePage.js +194 -172
- package/dist/file-router/parser/routePath.d.ts +22 -0
- package/dist/file-router/parser/routePath.js +74 -0
- package/dist/file-router/types/hooks.d.ts +52 -0
- package/dist/file-router/types/index.d.ts +3 -0
- package/dist/file-router/types/index.js +1 -0
- package/dist/file-router/types/options.d.ts +279 -0
- package/dist/file-router/types/options.js +1 -0
- package/dist/file-router/types/route.d.ts +114 -0
- package/dist/file-router/types/route.js +1 -0
- package/dist/file-router/utils/fileReader.d.ts +11 -0
- package/dist/file-router/utils/fileReader.js +22 -0
- package/dist/file-router/utils/findRoute.d.ts +8 -0
- package/dist/file-router/utils/findRoute.js +22 -0
- package/dist/file-router/utils/index.d.ts +4 -2
- package/dist/file-router/utils/index.js +4 -2
- package/dist/file-router/utils/logger.d.ts +6 -6
- package/dist/file-router/utils/logger.js +44 -4
- package/dist/file-router/utils/pathStrategy.d.ts +28 -0
- package/dist/file-router/utils/{namingStrategy.js → pathStrategy.js} +18 -28
- package/dist/file-router/utils/pathUtils.d.ts +31 -0
- package/dist/file-router/utils/pathUtils.js +53 -1
- package/dist/plugin-vite/constant.d.ts +9 -0
- package/dist/plugin-vite/constant.js +9 -0
- package/dist/plugin-vite/index.d.ts +4 -24
- package/dist/plugin-vite/index.js +4 -94
- package/dist/plugin-vite/plugin.d.ts +86 -0
- package/dist/plugin-vite/plugin.js +181 -0
- package/dist/plugin-vite/watcher.d.ts +15 -0
- package/dist/plugin-vite/watcher.js +65 -0
- package/package.json +9 -7
- package/dist/file-router/config/configUtils.d.ts +0 -54
- package/dist/file-router/config/configUtils.js +0 -88
- package/dist/file-router/scanner/filterUtils.d.ts +0 -35
- package/dist/file-router/scanner/filterUtils.js +0 -188
- package/dist/file-router/scanner/index.d.ts +0 -8
- package/dist/file-router/scanner/index.js +0 -8
- package/dist/file-router/scanner/routeTreeBuilder.d.ts +0 -21
- package/dist/file-router/scanner/routeTreeBuilder.js +0 -312
- package/dist/file-router/scanner/scanPages.d.ts +0 -48
- package/dist/file-router/scanner/scanPages.js +0 -174
- package/dist/file-router/types.d.ts +0 -344
- package/dist/file-router/utils/namingStrategy.d.ts +0 -57
- package/dist/file-router/utils/validateOptions.js +0 -233
- /package/dist/{plugin-vite/auto-routes → auto-routes}/handleHotUpdate.js +0 -0
- /package/dist/{plugin-vite/auto-routes → auto-routes}/index.d.ts +0 -0
- /package/dist/{plugin-vite/auto-routes → auto-routes}/index.js +0 -0
- /package/dist/file-router/{types.js → types/hooks.js} +0 -0
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 命名策略工具模块
|
|
3
|
+
*
|
|
4
|
+
* 提供路由名称和路径的命名转换功能,支持三种策略:
|
|
5
|
+
* - kebab: 将驼峰命名转换为 kebab-case(默认)
|
|
6
|
+
* - lowercase: 简单转换为小写
|
|
7
|
+
* - none: 保持原始命名
|
|
8
|
+
*
|
|
9
|
+
* 注意:只处理路径段名称,不处理动态参数变量名。
|
|
10
|
+
*/
|
|
1
11
|
/**
|
|
2
12
|
* 将驼峰命名转换为 kebab-case
|
|
3
13
|
*
|
|
@@ -32,22 +42,22 @@ function toKebabCase(str) {
|
|
|
32
42
|
* applyNamingStrategy('MainHome', 'none') // => 'MainHome'
|
|
33
43
|
* ```
|
|
34
44
|
*/
|
|
35
|
-
|
|
45
|
+
function applyStrategy(str, strategy) {
|
|
36
46
|
switch (strategy) {
|
|
37
47
|
case 'kebab':
|
|
38
48
|
return toKebabCase(str);
|
|
39
49
|
case 'lowercase':
|
|
40
50
|
return str.toLowerCase();
|
|
41
|
-
case '
|
|
51
|
+
case 'raw':
|
|
42
52
|
default:
|
|
43
53
|
return str;
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
/**
|
|
47
57
|
* 动态参数正则表达式
|
|
48
|
-
* 匹配
|
|
58
|
+
* 匹配 [paramName] 或 [paramName?] 格式
|
|
49
59
|
*/
|
|
50
|
-
const DYNAMIC_PARAM_REGEX = /\
|
|
60
|
+
const DYNAMIC_PARAM_REGEX = /\[([^]]+)]/g;
|
|
51
61
|
/**
|
|
52
62
|
* 应用命名策略转换路由路径
|
|
53
63
|
*
|
|
@@ -60,12 +70,12 @@ const DYNAMIC_PARAM_REGEX = /\{([^}]+)}/g;
|
|
|
60
70
|
* @example
|
|
61
71
|
* ```typescript
|
|
62
72
|
* applyNamingStrategyToPath('/MainHome', 'kebab') // => '/main-home'
|
|
63
|
-
* applyNamingStrategyToPath('/User/
|
|
73
|
+
* applyNamingStrategyToPath('/User/[userName]', 'kebab') // => '/user/[userName]'
|
|
64
74
|
* applyNamingStrategyToPath('/API/UserProfile', 'kebab') // => '/api/user-profile'
|
|
65
75
|
* ```
|
|
66
76
|
*/
|
|
67
|
-
export function
|
|
68
|
-
if (strategy === '
|
|
77
|
+
export function applyPathStrategy(path, strategy) {
|
|
78
|
+
if (strategy === 'raw') {
|
|
69
79
|
return path;
|
|
70
80
|
}
|
|
71
81
|
const segments = path.split('/');
|
|
@@ -80,27 +90,7 @@ export function applyNamingStrategyToPath(path, strategy) {
|
|
|
80
90
|
DYNAMIC_PARAM_REGEX.lastIndex = 0;
|
|
81
91
|
continue;
|
|
82
92
|
}
|
|
83
|
-
result.push(
|
|
93
|
+
result.push(applyStrategy(segment, strategy));
|
|
84
94
|
}
|
|
85
95
|
return result.join('/');
|
|
86
96
|
}
|
|
87
|
-
/**
|
|
88
|
-
* 应用命名策略转换路由名称
|
|
89
|
-
*
|
|
90
|
-
* @param name - 路由名称
|
|
91
|
-
* @param strategy - 命名策略
|
|
92
|
-
* @returns 转换后的名称
|
|
93
|
-
*
|
|
94
|
-
* @example
|
|
95
|
-
* ```typescript
|
|
96
|
-
* applyNamingStrategyToName('MainHome', 'kebab') // => 'main-home'
|
|
97
|
-
* applyNamingStrategyToName('user-MainHome', 'kebab') // => 'user-main-home'
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
|
-
export function applyNamingStrategyToName(name, strategy) {
|
|
101
|
-
if (strategy === 'none') {
|
|
102
|
-
return name;
|
|
103
|
-
}
|
|
104
|
-
const parts = name.split('-');
|
|
105
|
-
return parts.map(part => applyNamingStrategy(part, strategy)).join('-');
|
|
106
|
-
}
|
|
@@ -20,3 +20,34 @@
|
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
export declare function normalizePathSeparator(path: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* 归一化path
|
|
25
|
+
*
|
|
26
|
+
* 去除所有空格、替换重复的斜杠、去除尾部的斜杠
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* normalizePath('/ foo') // '/foo'
|
|
30
|
+
* normalizePath('/foo/') // '/foo'
|
|
31
|
+
* normalizePath('/foo/bar') // '/foo/bar'
|
|
32
|
+
* normalizePath('foo/') // '/foo'
|
|
33
|
+
*
|
|
34
|
+
* @param {string} path - 路径字符串
|
|
35
|
+
* @return {string} - 格式化后的路径字符串
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeRoutePath(path: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* 解析文件名
|
|
40
|
+
*
|
|
41
|
+
* 提取文件名中的路由信息和动态参数。
|
|
42
|
+
*
|
|
43
|
+
* @param path - path
|
|
44
|
+
* @returns 解析结果
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolvePathVariable(path: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* 检查路径是否包含动态参数
|
|
49
|
+
*
|
|
50
|
+
* @param path - 路由路径
|
|
51
|
+
* @returns {boolean} 是否包含动态参数
|
|
52
|
+
*/
|
|
53
|
+
export declare function hasDynamicPath(path: string): boolean;
|
|
@@ -20,5 +20,57 @@
|
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
export function normalizePathSeparator(path) {
|
|
23
|
-
return path.replace(/\\/g, '/');
|
|
23
|
+
return path.trim().replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 归一化path
|
|
27
|
+
*
|
|
28
|
+
* 去除所有空格、替换重复的斜杠、去除尾部的斜杠
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* normalizePath('/ foo') // '/foo'
|
|
32
|
+
* normalizePath('/foo/') // '/foo'
|
|
33
|
+
* normalizePath('/foo/bar') // '/foo/bar'
|
|
34
|
+
* normalizePath('foo/') // '/foo'
|
|
35
|
+
*
|
|
36
|
+
* @param {string} path - 路径字符串
|
|
37
|
+
* @return {string} - 格式化后的路径字符串
|
|
38
|
+
*/
|
|
39
|
+
export function normalizeRoutePath(path) {
|
|
40
|
+
// 去除所有空格 处理重复//
|
|
41
|
+
const formated = `/${path.trim()}`.replace(/\s+/g, '').replace(/\/+/g, '/');
|
|
42
|
+
if (formated === '/')
|
|
43
|
+
return formated;
|
|
44
|
+
return formated.replace(/\/$/, '');
|
|
45
|
+
}
|
|
46
|
+
/** 动态参数匹配正则,如 [id]、[slug]、[param?] */
|
|
47
|
+
const DYNAMIC_PARAM_REGEX = /\{([^?}]+)(\?)?}|\[([^\]]+)]/g;
|
|
48
|
+
/**
|
|
49
|
+
* 解析文件名
|
|
50
|
+
*
|
|
51
|
+
* 提取文件名中的路由信息和动态参数。
|
|
52
|
+
*
|
|
53
|
+
* @param path - path
|
|
54
|
+
* @returns 解析结果
|
|
55
|
+
*/
|
|
56
|
+
export function resolvePathVariable(path) {
|
|
57
|
+
if (!path.includes('['))
|
|
58
|
+
return path;
|
|
59
|
+
// 使用 replace 遍历所有匹配项,避免只有第一个参数被替换的问题
|
|
60
|
+
return path.replace(DYNAMIC_PARAM_REGEX, (_match, braceName, isOptional, bracketName) => {
|
|
61
|
+
// 获取参数名:如果有 braceName 就用 braceName,否则用 bracketName
|
|
62
|
+
const paramName = braceName || bracketName;
|
|
63
|
+
// 处理可选标识:如果有 isOptional(即问号) 就加上 '?'
|
|
64
|
+
const optionalMark = isOptional ? '?' : '';
|
|
65
|
+
return `{${paramName}${optionalMark}}`;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 检查路径是否包含动态参数
|
|
70
|
+
*
|
|
71
|
+
* @param path - 路由路径
|
|
72
|
+
* @returns {boolean} 是否包含动态参数
|
|
73
|
+
*/
|
|
74
|
+
export function hasDynamicPath(path) {
|
|
75
|
+
return path.includes('{');
|
|
24
76
|
}
|
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
/**
|
|
6
|
-
* Vite 插件配置选项
|
|
7
|
-
*
|
|
8
|
-
* 扩展自 FileRouterOptions,添加 Vite 特有的配置项。
|
|
9
|
-
*/
|
|
10
|
-
export interface RouterPluginOptions extends FileRouterOptions {
|
|
11
|
-
/**
|
|
12
|
-
* 类型声明文件路径,设为 false 可禁用生成
|
|
13
|
-
*
|
|
14
|
-
* @default 'typed-router.d.ts'
|
|
15
|
-
*/
|
|
16
|
-
dts?: string | false;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* 创建 Vitarx Router Vite 插件
|
|
20
|
-
*
|
|
21
|
-
* @param options - 插件配置选项
|
|
22
|
-
* @returns Vite 插件实例
|
|
23
|
-
*/
|
|
24
|
-
export default function VitarxRouter(options?: RouterPluginOptions): Plugin;
|
|
1
|
+
import vitarxRouter from './plugin.js';
|
|
2
|
+
export * from './constant.js';
|
|
3
|
+
export * from './watcher.js';
|
|
4
|
+
export default vitarxRouter;
|
|
@@ -1,94 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
* 提供基于文件系统的自动路由生成能力,支持热更新和 TypeScript 类型生成。
|
|
6
|
-
*
|
|
7
|
-
* ## 安装使用
|
|
8
|
-
* ```typescript
|
|
9
|
-
* // vite.config.ts
|
|
10
|
-
* import VitarxRouter from 'vitarx-router/vite'
|
|
11
|
-
*
|
|
12
|
-
* export default defineConfig({
|
|
13
|
-
* plugins: [
|
|
14
|
-
* VitarxRouter({
|
|
15
|
-
* pagesDir: 'src/pages',
|
|
16
|
-
* extensions: ['.tsx', '.ts'],
|
|
17
|
-
* dts: 'typed-router.d.ts'
|
|
18
|
-
* })
|
|
19
|
-
* ]
|
|
20
|
-
* })
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* ## 虚拟模块
|
|
24
|
-
* - `virtual:vitarx-router:routes` - 自动生成的路由配置
|
|
25
|
-
*
|
|
26
|
-
* @module vite
|
|
27
|
-
*/
|
|
28
|
-
import chalk from 'chalk';
|
|
29
|
-
import { FileRouter, info } from '../file-router/index.js';
|
|
30
|
-
/** 默认的类型声明文件路径 */
|
|
31
|
-
export const DEFAULT_DTS_FILE = 'typed-router.d.ts';
|
|
32
|
-
/** 虚拟模块 ID:路由配置 */
|
|
33
|
-
const VIRTUAL_ROUTES_ID = 'virtual:vitarx-router:routes';
|
|
34
|
-
/** 解析后的路由模块 ID(Vite 内部使用) */
|
|
35
|
-
const RESOLVED_ROUTES_ID = '\0' + VIRTUAL_ROUTES_ID;
|
|
36
|
-
/**
|
|
37
|
-
* 创建 Vitarx Router Vite 插件
|
|
38
|
-
*
|
|
39
|
-
* @param options - 插件配置选项
|
|
40
|
-
* @returns Vite 插件实例
|
|
41
|
-
*/
|
|
42
|
-
export default function VitarxRouter(options = {}) {
|
|
43
|
-
const dts = options.dts ?? DEFAULT_DTS_FILE;
|
|
44
|
-
const router = new FileRouter(options);
|
|
45
|
-
return {
|
|
46
|
-
name: 'vite-plugin-vitarx-router',
|
|
47
|
-
enforce: 'pre',
|
|
48
|
-
configResolved() {
|
|
49
|
-
router.scan();
|
|
50
|
-
if (dts) {
|
|
51
|
-
const result = router.writeDts(dts);
|
|
52
|
-
info(`✨ generate type definitions:\n${chalk.yellow(result.path)}`);
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
resolveId(id) {
|
|
56
|
-
if (id === VIRTUAL_ROUTES_ID) {
|
|
57
|
-
return RESOLVED_ROUTES_ID;
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
},
|
|
61
|
-
async load(id) {
|
|
62
|
-
if (id === RESOLVED_ROUTES_ID) {
|
|
63
|
-
const { code } = await router.generateRoutes();
|
|
64
|
-
return code;
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
},
|
|
68
|
-
transform(code, id) {
|
|
69
|
-
return router.removeDefinePage(code, id);
|
|
70
|
-
},
|
|
71
|
-
configureServer(server) {
|
|
72
|
-
const absolutePagesDirs = router.config.pages;
|
|
73
|
-
for (const dirConfig of absolutePagesDirs) {
|
|
74
|
-
server.watcher.add(dirConfig.dir);
|
|
75
|
-
}
|
|
76
|
-
const handlePageFileChange = (file, server) => {
|
|
77
|
-
if (router.isPageFile(file)) {
|
|
78
|
-
router.invalidate();
|
|
79
|
-
router.scan();
|
|
80
|
-
if (dts) {
|
|
81
|
-
router.writeDts(dts);
|
|
82
|
-
}
|
|
83
|
-
const mod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_ID);
|
|
84
|
-
if (mod) {
|
|
85
|
-
server.moduleGraph.invalidateModule(mod);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
server.watcher.on('add', file => handlePageFileChange(file, server));
|
|
90
|
-
server.watcher.on('unlink', file => handlePageFileChange(file, server));
|
|
91
|
-
server.watcher.on('change', file => handlePageFileChange(file, server));
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
}
|
|
1
|
+
import vitarxRouter from './plugin.js';
|
|
2
|
+
export * from './constant.js';
|
|
3
|
+
export * from './watcher.js';
|
|
4
|
+
export default vitarxRouter;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Vite 插件入口模块
|
|
3
|
+
*
|
|
4
|
+
* 这是 vitarx-router 文件路由功能的 Vite 插件主入口。
|
|
5
|
+
* 提供基于文件系统的自动路由生成能力,支持热更新和 TypeScript 类型生成。
|
|
6
|
+
*
|
|
7
|
+
* ## 核心功能
|
|
8
|
+
* - **虚拟模块**: 通过 `virtual:vitarx-router:routes` 提供自动生成的路由配置
|
|
9
|
+
* - **热更新**: 监听文件变化,自动更新路由配置
|
|
10
|
+
* - **代码转换**: 移除页面文件中的 `definePage` 宏调用
|
|
11
|
+
* - **类型生成**: 支持生成 TypeScript 类型定义文件
|
|
12
|
+
*
|
|
13
|
+
* ## 安装使用
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // vite.config.ts
|
|
16
|
+
* import VitarxRouter from 'vitarx-router/vite'
|
|
17
|
+
*
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* plugins: [
|
|
20
|
+
* VitarxRouter({
|
|
21
|
+
* pagesDir: 'src/pages',
|
|
22
|
+
* extensions: ['.tsx', '.ts'],
|
|
23
|
+
* dts: 'typed-router.d.ts'
|
|
24
|
+
* })
|
|
25
|
+
* ]
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ## 虚拟模块
|
|
30
|
+
* - `virtual:vitarx-router:routes` - 自动生成的路由配置
|
|
31
|
+
*
|
|
32
|
+
* ## 插件生命周期
|
|
33
|
+
* 1. `config` - 识别是否为预览模式
|
|
34
|
+
* 2. `configResolved` - 创建 FileRouter 实例
|
|
35
|
+
* 3. `resolveId` - 解析虚拟模块 ID
|
|
36
|
+
* 4. `load` - 加载虚拟模块内容
|
|
37
|
+
* 5. `transform` - 转换页面文件代码
|
|
38
|
+
* 6. `configureServer` - 配置开发服务器文件监听
|
|
39
|
+
* 7. `closeBundle` - 清理资源
|
|
40
|
+
*
|
|
41
|
+
* @module vite
|
|
42
|
+
* @see {@link https://router.vitarx.cn 官方文档}
|
|
43
|
+
*/
|
|
44
|
+
import type { Plugin } from 'vite';
|
|
45
|
+
import { type FileRouterOptions } from '../file-router/index.js';
|
|
46
|
+
/**
|
|
47
|
+
* Vite 插件配置选项
|
|
48
|
+
*
|
|
49
|
+
* 继承自 FileRouterOptions,未添加额外的 Vite 特有配置项
|
|
50
|
+
*
|
|
51
|
+
* @see {@link FileRouterOptions} 文件路由配置选项
|
|
52
|
+
*/
|
|
53
|
+
export interface RouterPluginOptions extends FileRouterOptions {
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 创建 Vitarx Router Vite 插件
|
|
57
|
+
*
|
|
58
|
+
* 这是插件的主入口函数,返回一个标准的 Vite 插件对象。
|
|
59
|
+
* 插件通过虚拟模块提供自动生成的路由配置,支持热更新和代码转换。
|
|
60
|
+
*
|
|
61
|
+
* ## 功能特性
|
|
62
|
+
* - 自动扫描页面目录生成路由配置
|
|
63
|
+
* - 支持热模块替换(HMR)
|
|
64
|
+
* - 自动移除 `definePage` 宏调用
|
|
65
|
+
* - 支持 TypeScript 类型定义生成
|
|
66
|
+
*
|
|
67
|
+
* @param options - 插件配置选项
|
|
68
|
+
* @returns Vite 插件实例
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* // vite.config.ts
|
|
73
|
+
* import VitarxRouter from 'vitarx-router/vite'
|
|
74
|
+
*
|
|
75
|
+
* export default defineConfig({
|
|
76
|
+
* plugins: [
|
|
77
|
+
* VitarxRouter({
|
|
78
|
+
* pages: 'src/pages',
|
|
79
|
+
* pathStrategy: 'kebab',
|
|
80
|
+
* dts: 'typed-router.d.ts'
|
|
81
|
+
* })
|
|
82
|
+
* ]
|
|
83
|
+
* })
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export default function vitarxRouter(options?: RouterPluginOptions): Plugin;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { FileRouter, info, warn } from '../file-router/index.js';
|
|
2
|
+
import { RESOLVED_ROUTES_ID, VIRTUAL_ROUTES_ID } from './constant.js';
|
|
3
|
+
import { setupWatcher } from './watcher.js';
|
|
4
|
+
/** 插件名称,用于日志输出和调试 */
|
|
5
|
+
const PLUGIN_NAME = 'vite-plugin-vitarx-router';
|
|
6
|
+
/**
|
|
7
|
+
* 创建插件初始状态
|
|
8
|
+
*
|
|
9
|
+
* 工厂函数,用于创建干净的插件状态对象
|
|
10
|
+
*
|
|
11
|
+
* @returns 初始化的插件状态对象
|
|
12
|
+
*/
|
|
13
|
+
function createInitialState() {
|
|
14
|
+
return {
|
|
15
|
+
router: null,
|
|
16
|
+
isPreview: false,
|
|
17
|
+
watcherHandler: null
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 创建 Vitarx Router Vite 插件
|
|
22
|
+
*
|
|
23
|
+
* 这是插件的主入口函数,返回一个标准的 Vite 插件对象。
|
|
24
|
+
* 插件通过虚拟模块提供自动生成的路由配置,支持热更新和代码转换。
|
|
25
|
+
*
|
|
26
|
+
* ## 功能特性
|
|
27
|
+
* - 自动扫描页面目录生成路由配置
|
|
28
|
+
* - 支持热模块替换(HMR)
|
|
29
|
+
* - 自动移除 `definePage` 宏调用
|
|
30
|
+
* - 支持 TypeScript 类型定义生成
|
|
31
|
+
*
|
|
32
|
+
* @param options - 插件配置选项
|
|
33
|
+
* @returns Vite 插件实例
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // vite.config.ts
|
|
38
|
+
* import VitarxRouter from 'vitarx-router/vite'
|
|
39
|
+
*
|
|
40
|
+
* export default defineConfig({
|
|
41
|
+
* plugins: [
|
|
42
|
+
* VitarxRouter({
|
|
43
|
+
* pages: 'src/pages',
|
|
44
|
+
* pathStrategy: 'kebab',
|
|
45
|
+
* dts: 'typed-router.d.ts'
|
|
46
|
+
* })
|
|
47
|
+
* ]
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export default function vitarxRouter(options = {}) {
|
|
52
|
+
const state = createInitialState();
|
|
53
|
+
return {
|
|
54
|
+
name: PLUGIN_NAME,
|
|
55
|
+
/**
|
|
56
|
+
* 配置钩子
|
|
57
|
+
*
|
|
58
|
+
* 在 Vite 解析配置前调用,用于识别是否为预览模式。
|
|
59
|
+
* 预览模式下插件不执行任何操作,因为路由已在构建时生成。
|
|
60
|
+
*
|
|
61
|
+
* @param _config - Vite 配置对象(未使用)
|
|
62
|
+
* @param env - 环境配置,包含命令模式和模式名称
|
|
63
|
+
*/
|
|
64
|
+
config(_config, env) {
|
|
65
|
+
state.isPreview = !!env.isPreview;
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* 配置解析完成钩子
|
|
69
|
+
*
|
|
70
|
+
* 在 Vite 配置解析完成后调用,此时可以访问完整的配置。
|
|
71
|
+
* 用于创建 FileRouter 实例,初始化路由扫描。
|
|
72
|
+
*
|
|
73
|
+
* @throws 当 FileRouter 创建失败时抛出错误
|
|
74
|
+
*/
|
|
75
|
+
async configResolved() {
|
|
76
|
+
if (state.isPreview)
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
state.router = new FileRouter(options);
|
|
80
|
+
info(`✨ File Router Manager created`);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
84
|
+
warn(`Failed to create FileRouter: ${errorMessage}`);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* 模块 ID 解析钩子
|
|
90
|
+
*
|
|
91
|
+
* 用于解析虚拟模块 ID,将 `virtual:vitarx-router:routes`
|
|
92
|
+
* 转换为内部使用的 `\0virtual:vitarx-router:routes`。
|
|
93
|
+
*
|
|
94
|
+
* @param id - 模块 ID
|
|
95
|
+
* @returns 解析后的模块 ID,不匹配则返回 null
|
|
96
|
+
*/
|
|
97
|
+
resolveId(id) {
|
|
98
|
+
if (id === VIRTUAL_ROUTES_ID) {
|
|
99
|
+
return RESOLVED_ROUTES_ID;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* 模块加载钩子
|
|
105
|
+
*
|
|
106
|
+
* 加载虚拟模块内容,生成路由配置代码。
|
|
107
|
+
* 返回的代码会被 Vite 作为模块内容使用。
|
|
108
|
+
*
|
|
109
|
+
* @param id - 模块 ID
|
|
110
|
+
* @returns 模块内容,不匹配则返回 null
|
|
111
|
+
* @throws 当路由生成失败时抛出错误
|
|
112
|
+
*/
|
|
113
|
+
load(id) {
|
|
114
|
+
if (!state.router)
|
|
115
|
+
return null;
|
|
116
|
+
if (id !== RESOLVED_ROUTES_ID)
|
|
117
|
+
return null;
|
|
118
|
+
return state.router.generate().code;
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* 代码转换钩子
|
|
122
|
+
*
|
|
123
|
+
* 转换页面文件代码,移除 `definePage` 宏调用。
|
|
124
|
+
* `definePage` 作为全局宏使用,无需导入,构建时需要移除。
|
|
125
|
+
*
|
|
126
|
+
* @param code - 源代码
|
|
127
|
+
* @param id - 文件路径
|
|
128
|
+
* @returns 转换后的代码,无需转换则返回 null
|
|
129
|
+
*/
|
|
130
|
+
transform(code, id) {
|
|
131
|
+
if (!state.router)
|
|
132
|
+
return null;
|
|
133
|
+
try {
|
|
134
|
+
return state.router.removeDefinePage(code, id);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
warn(`Failed to transform ${id}:`, error);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* 配置开发服务器钩子
|
|
143
|
+
*
|
|
144
|
+
* 配置开发服务器的文件监听,实现热更新功能。
|
|
145
|
+
* 当页面文件发生变化时,自动更新路由配置并触发 HMR。
|
|
146
|
+
*
|
|
147
|
+
* @param server - Vite 开发服务器实例
|
|
148
|
+
*/
|
|
149
|
+
configureServer(server) {
|
|
150
|
+
if (!state.router)
|
|
151
|
+
return;
|
|
152
|
+
const router = state.router;
|
|
153
|
+
state.watcherHandler = setupWatcher(state.router, server, (event, file) => {
|
|
154
|
+
try {
|
|
155
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ROUTES_ID);
|
|
156
|
+
if (!mod)
|
|
157
|
+
return;
|
|
158
|
+
const isRouteAffected = router.handleChange(event, file);
|
|
159
|
+
if (isRouteAffected) {
|
|
160
|
+
server.moduleGraph.invalidateModule(mod);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
warn(`Failed to handle file change for ${file}:`, error);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* 构建结束钩子
|
|
170
|
+
*
|
|
171
|
+
* 在构建结束时清理资源,防止内存泄漏。
|
|
172
|
+
* 清理文件监听器引用和路由管理器实例。
|
|
173
|
+
*/
|
|
174
|
+
closeBundle() {
|
|
175
|
+
if (state.watcherHandler) {
|
|
176
|
+
state.watcherHandler = null;
|
|
177
|
+
}
|
|
178
|
+
state.router = null;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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;
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
}
|