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
|
@@ -47,7 +47,11 @@ function extractStringRecord(node) {
|
|
|
47
47
|
for (const prop of node.properties) {
|
|
48
48
|
if (prop.type !== 'ObjectProperty')
|
|
49
49
|
continue;
|
|
50
|
-
const key = prop.key.type === 'Identifier'
|
|
50
|
+
const key = prop.key.type === 'Identifier'
|
|
51
|
+
? prop.key.name
|
|
52
|
+
: prop.key.type === 'StringLiteral'
|
|
53
|
+
? prop.key.value
|
|
54
|
+
: null;
|
|
51
55
|
if (!key)
|
|
52
56
|
continue;
|
|
53
57
|
if (prop.value.type === 'StringLiteral') {
|
|
@@ -69,7 +73,11 @@ function extractMetaValue(node) {
|
|
|
69
73
|
for (const prop of node.properties) {
|
|
70
74
|
if (prop.type !== 'ObjectProperty')
|
|
71
75
|
continue;
|
|
72
|
-
const key = prop.key.type === 'Identifier'
|
|
76
|
+
const key = prop.key.type === 'Identifier'
|
|
77
|
+
? prop.key.name
|
|
78
|
+
: prop.key.type === 'StringLiteral'
|
|
79
|
+
? prop.key.value
|
|
80
|
+
: null;
|
|
73
81
|
if (!key)
|
|
74
82
|
continue;
|
|
75
83
|
meta[key] = extractAstLiteralValue(prop.value);
|
|
@@ -91,7 +99,11 @@ function extractPatternValue(node) {
|
|
|
91
99
|
for (const prop of node.properties) {
|
|
92
100
|
if (prop.type !== 'ObjectProperty')
|
|
93
101
|
continue;
|
|
94
|
-
const key = prop.key.type === 'Identifier'
|
|
102
|
+
const key = prop.key.type === 'Identifier'
|
|
103
|
+
? prop.key.name
|
|
104
|
+
: prop.key.type === 'StringLiteral'
|
|
105
|
+
? prop.key.value
|
|
106
|
+
: null;
|
|
95
107
|
if (!key)
|
|
96
108
|
continue;
|
|
97
109
|
const value = prop.value;
|
|
@@ -116,8 +128,8 @@ function extractRegExp(node) {
|
|
|
116
128
|
if (node.callee.type === 'Identifier' && node.callee.name === 'RegExp') {
|
|
117
129
|
const args = node.arguments;
|
|
118
130
|
if (args.length >= 1) {
|
|
119
|
-
const firstArg = args
|
|
120
|
-
const secondArg = args
|
|
131
|
+
const firstArg = args.at(0);
|
|
132
|
+
const secondArg = args.at(1);
|
|
121
133
|
let patternStr = null;
|
|
122
134
|
let flags = '';
|
|
123
135
|
if (firstArg.type === 'StringLiteral') {
|
|
@@ -158,7 +170,11 @@ function extractRedirectValue(node) {
|
|
|
158
170
|
for (const prop of node.properties) {
|
|
159
171
|
if (prop.type !== 'ObjectProperty')
|
|
160
172
|
continue;
|
|
161
|
-
const key = prop.key.type === 'Identifier'
|
|
173
|
+
const key = prop.key.type === 'Identifier'
|
|
174
|
+
? prop.key.name
|
|
175
|
+
: prop.key.type === 'StringLiteral'
|
|
176
|
+
? prop.key.value
|
|
177
|
+
: null;
|
|
162
178
|
if (!key)
|
|
163
179
|
continue;
|
|
164
180
|
if (key === 'index' && prop.value.type === 'StringLiteral') {
|
|
@@ -209,7 +225,11 @@ export function extractPageOptions(node) {
|
|
|
209
225
|
for (const prop of node.properties) {
|
|
210
226
|
if (prop.type !== 'ObjectProperty')
|
|
211
227
|
continue;
|
|
212
|
-
const key = prop.key.type === 'Identifier'
|
|
228
|
+
const key = prop.key.type === 'Identifier'
|
|
229
|
+
? prop.key.name
|
|
230
|
+
: prop.key.type === 'StringLiteral'
|
|
231
|
+
? prop.key.value
|
|
232
|
+
: null;
|
|
213
233
|
if (!key)
|
|
214
234
|
continue;
|
|
215
235
|
switch (key) {
|
|
@@ -6,17 +6,26 @@
|
|
|
6
6
|
* definePage 作为全局宏使用,无需导入,因此只需移除调用语句。
|
|
7
7
|
*/
|
|
8
8
|
import type { GeneratorResult } from '@babel/generator';
|
|
9
|
-
import type { PageOptions } from '../types.js';
|
|
9
|
+
import type { PageOptions } from '../types/index.js';
|
|
10
|
+
type Falsy = false | null | undefined | 0 | '';
|
|
11
|
+
/**
|
|
12
|
+
* 合并多个页面配置
|
|
13
|
+
*
|
|
14
|
+
* @param optionsList - 配置列表
|
|
15
|
+
* @returns 合并后的配置
|
|
16
|
+
*/
|
|
17
|
+
export declare function mergePageOptions(...optionsList: (PageOptions | Falsy)[]): PageOptions;
|
|
10
18
|
/**
|
|
11
19
|
* 解析 definePage 宏配置
|
|
12
20
|
*
|
|
13
21
|
* definePage 作为全局宏使用,无需导入。
|
|
14
22
|
* 直接查找代码中的 definePage 调用并提取配置。
|
|
15
23
|
*
|
|
16
|
-
* @param
|
|
24
|
+
* @param content - 源代码
|
|
25
|
+
* @param filePath - 文件路径
|
|
17
26
|
* @returns 解析出的页面配置,解析失败返回 null
|
|
18
27
|
*/
|
|
19
|
-
export declare function parseDefinePage(filePath: string): PageOptions | null;
|
|
28
|
+
export declare function parseDefinePage(content: string, filePath: string): PageOptions | null;
|
|
20
29
|
/**
|
|
21
30
|
* 移除 definePage 宏调用(仅在构建模式下)
|
|
22
31
|
*
|
|
@@ -28,3 +37,11 @@ export declare function parseDefinePage(filePath: string): PageOptions | null;
|
|
|
28
37
|
* @returns 转换后的代码,无需转换返回 null
|
|
29
38
|
*/
|
|
30
39
|
export declare function removeDefinePage(code: string, filename: string): GeneratorResult | null;
|
|
40
|
+
/**
|
|
41
|
+
* 判断两个页面配置是否相等
|
|
42
|
+
*
|
|
43
|
+
* @param a
|
|
44
|
+
* @param b
|
|
45
|
+
*/
|
|
46
|
+
export declare function isEqualPageOptions(a?: PageOptions | null, b?: PageOptions | null): boolean;
|
|
47
|
+
export {};
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* definePage 作为全局宏使用,无需导入,因此只需移除调用语句。
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
9
|
-
import { babelGenerate, babelTraverse, parseCode, warn } from '../utils/index.js';
|
|
8
|
+
import { babelGenerate, babelTraverse, error, parseCode, warn } from '../utils/index.js';
|
|
10
9
|
import { extractPageOptions } from './astValueExtractor.js';
|
|
11
10
|
/**
|
|
12
11
|
* 合并多个页面配置
|
|
@@ -14,23 +13,19 @@ import { extractPageOptions } from './astValueExtractor.js';
|
|
|
14
13
|
* @param optionsList - 配置列表
|
|
15
14
|
* @returns 合并后的配置
|
|
16
15
|
*/
|
|
17
|
-
function mergePageOptions(optionsList) {
|
|
16
|
+
export function mergePageOptions(...optionsList) {
|
|
18
17
|
const merged = {};
|
|
19
|
-
// 遍历所有配置,按优先级合并
|
|
20
18
|
for (const options of optionsList) {
|
|
21
|
-
|
|
19
|
+
if (!options)
|
|
20
|
+
continue;
|
|
22
21
|
if (options.name)
|
|
23
22
|
merged.name = options.name;
|
|
24
|
-
// meta 合并
|
|
25
23
|
if (options.meta)
|
|
26
24
|
merged.meta = { ...merged.meta, ...options.meta };
|
|
27
|
-
// pattern 合并
|
|
28
25
|
if (options.pattern)
|
|
29
26
|
merged.pattern = { ...merged.pattern, ...options.pattern };
|
|
30
|
-
// 重定向直接覆盖
|
|
31
27
|
if (options.redirect)
|
|
32
28
|
merged.redirect = options.redirect;
|
|
33
|
-
// 别名合并(支持字符串和数组形式)
|
|
34
29
|
if (options.alias) {
|
|
35
30
|
if (!merged.alias) {
|
|
36
31
|
merged.alias = options.alias;
|
|
@@ -50,55 +45,41 @@ function mergePageOptions(optionsList) {
|
|
|
50
45
|
* definePage 作为全局宏使用,无需导入。
|
|
51
46
|
* 直接查找代码中的 definePage 调用并提取配置。
|
|
52
47
|
*
|
|
53
|
-
* @param
|
|
48
|
+
* @param content - 源代码
|
|
49
|
+
* @param filePath - 文件路径
|
|
54
50
|
* @returns 解析出的页面配置,解析失败返回 null
|
|
55
51
|
*/
|
|
56
|
-
export function parseDefinePage(filePath) {
|
|
57
|
-
// 检查文件是否存在
|
|
58
|
-
if (!fs.existsSync(filePath)) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
62
|
-
// 快速检查是否包含 definePage 调用
|
|
52
|
+
export function parseDefinePage(content, filePath) {
|
|
63
53
|
if (!content.includes('definePage')) {
|
|
64
54
|
return null;
|
|
65
55
|
}
|
|
66
56
|
try {
|
|
67
|
-
// 解析代码为 AST
|
|
68
57
|
const ast = parseCode(content);
|
|
69
|
-
const
|
|
70
|
-
// 遍历 AST 查找 definePage 调用
|
|
58
|
+
const routeOptionsList = [];
|
|
71
59
|
babelTraverse(ast, {
|
|
72
60
|
CallExpression(nodePath) {
|
|
73
61
|
const { node } = nodePath;
|
|
74
|
-
// 检查是否是 definePage 调用
|
|
75
62
|
if (node.callee.type !== 'Identifier' || node.callee.name !== 'definePage') {
|
|
76
63
|
return;
|
|
77
64
|
}
|
|
78
|
-
// 检查参数是否为对象表达式
|
|
79
65
|
const arg = node.arguments[0];
|
|
80
66
|
if (!arg || arg.type !== 'ObjectExpression') {
|
|
81
67
|
return;
|
|
82
68
|
}
|
|
83
|
-
// 提取页面配置
|
|
84
69
|
const options = extractPageOptions(arg);
|
|
85
|
-
|
|
70
|
+
routeOptionsList.push(options);
|
|
86
71
|
}
|
|
87
72
|
});
|
|
88
|
-
|
|
89
|
-
if (pageOptionsList.length === 0) {
|
|
73
|
+
if (routeOptionsList.length === 0) {
|
|
90
74
|
return null;
|
|
91
75
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
warn('检测到多个 definePage 调用,将合并所有配置', '建议每个文件只调用一次 definePage');
|
|
76
|
+
if (routeOptionsList.length > 1) {
|
|
77
|
+
warn('检测到多个 definePage 调用,将合并所有配置,建议每个文件只调用一次 definePage', `in ${filePath}`);
|
|
95
78
|
}
|
|
96
|
-
|
|
97
|
-
return mergePageOptions(pageOptionsList);
|
|
79
|
+
return mergePageOptions(...routeOptionsList);
|
|
98
80
|
}
|
|
99
81
|
catch (e) {
|
|
100
|
-
|
|
101
|
-
warn('解析 definePage 失败', `${filePath}: ${e?.toString()}`);
|
|
82
|
+
error(`解析 definePage 失败 in ${e}`, e);
|
|
102
83
|
return null;
|
|
103
84
|
}
|
|
104
85
|
}
|
|
@@ -113,29 +94,22 @@ export function parseDefinePage(filePath) {
|
|
|
113
94
|
* @returns 转换后的代码,无需转换返回 null
|
|
114
95
|
*/
|
|
115
96
|
export function removeDefinePage(code, filename) {
|
|
116
|
-
// 快速检查是否包含 definePage 调用
|
|
117
97
|
if (!code.includes('definePage')) {
|
|
118
98
|
return null;
|
|
119
99
|
}
|
|
120
|
-
// 解析代码为 AST
|
|
121
100
|
const ast = parseCode(code);
|
|
122
101
|
let hasDefinePage = false;
|
|
123
|
-
// 遍历 AST 查找并移除 definePage 调用
|
|
124
102
|
babelTraverse(ast, {
|
|
125
103
|
CallExpression(nodePath) {
|
|
126
104
|
const { node } = nodePath;
|
|
127
|
-
// 检查是否是 definePage 调用
|
|
128
105
|
if (node.callee.type === 'Identifier' && node.callee.name === 'definePage') {
|
|
129
106
|
hasDefinePage = true;
|
|
130
|
-
// 移除节点
|
|
131
107
|
nodePath.remove();
|
|
132
108
|
}
|
|
133
109
|
}
|
|
134
110
|
});
|
|
135
|
-
// 没有找到 definePage 调用
|
|
136
111
|
if (!hasDefinePage)
|
|
137
112
|
return null;
|
|
138
|
-
// 生成转换后的代码
|
|
139
113
|
return babelGenerate(ast, {
|
|
140
114
|
retainLines: false,
|
|
141
115
|
compact: false,
|
|
@@ -144,3 +118,109 @@ export function removeDefinePage(code, filename) {
|
|
|
144
118
|
sourceFileName: filename
|
|
145
119
|
});
|
|
146
120
|
}
|
|
121
|
+
/** 普通 Record<string, string> 比较 */
|
|
122
|
+
function recordEqual(a, b) {
|
|
123
|
+
if (a === b)
|
|
124
|
+
return true;
|
|
125
|
+
if (!a || !b)
|
|
126
|
+
return false;
|
|
127
|
+
const keys = Object.keys(a);
|
|
128
|
+
if (keys.length !== Object.keys(b).length)
|
|
129
|
+
return false;
|
|
130
|
+
for (let i = 0; i < keys.length; i++) {
|
|
131
|
+
const k = keys[i];
|
|
132
|
+
if (a[k] !== b[k])
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
/** RegExp 结构比较 */
|
|
138
|
+
function patternEqual(a, b) {
|
|
139
|
+
if (a === b)
|
|
140
|
+
return true;
|
|
141
|
+
if (!a || !b)
|
|
142
|
+
return false;
|
|
143
|
+
const keys = Object.keys(a);
|
|
144
|
+
if (keys.length !== Object.keys(b).length)
|
|
145
|
+
return false;
|
|
146
|
+
for (let i = 0; i < keys.length; i++) {
|
|
147
|
+
const k = keys[i];
|
|
148
|
+
const ra = a[k];
|
|
149
|
+
const rb = b[k];
|
|
150
|
+
if (!rb)
|
|
151
|
+
return false;
|
|
152
|
+
// 关键:比较正则内容 + flags
|
|
153
|
+
if (ra.source !== rb.source || ra.flags !== rb.flags) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
/** redirect: string | Record */
|
|
160
|
+
function redirectEqual(a, b) {
|
|
161
|
+
if (a === b)
|
|
162
|
+
return true;
|
|
163
|
+
if (!a || !b)
|
|
164
|
+
return false;
|
|
165
|
+
const ta = typeof a;
|
|
166
|
+
const tb = typeof b;
|
|
167
|
+
// 类型不同,直接不等
|
|
168
|
+
if (ta !== tb)
|
|
169
|
+
return false;
|
|
170
|
+
// string
|
|
171
|
+
if (ta === 'string') {
|
|
172
|
+
return a === b;
|
|
173
|
+
}
|
|
174
|
+
// RedirectConfig
|
|
175
|
+
const ra = a;
|
|
176
|
+
const rb = b;
|
|
177
|
+
if (ra.index !== rb.index)
|
|
178
|
+
return false;
|
|
179
|
+
if (!recordEqual(ra.query, rb.query))
|
|
180
|
+
return false;
|
|
181
|
+
return recordEqual(ra.params, rb.params);
|
|
182
|
+
}
|
|
183
|
+
/** alias: string | string[] */
|
|
184
|
+
function aliasEqual(a, b) {
|
|
185
|
+
if (a === b)
|
|
186
|
+
return true;
|
|
187
|
+
if (!a || !b)
|
|
188
|
+
return false;
|
|
189
|
+
const arrA = Array.isArray(a) ? a : [a];
|
|
190
|
+
const arrB = Array.isArray(b) ? b : [b];
|
|
191
|
+
const len = arrA.length;
|
|
192
|
+
if (len !== arrB.length)
|
|
193
|
+
return false;
|
|
194
|
+
// ⚠️ 默认:顺序敏感(更安全,避免隐藏 bug)
|
|
195
|
+
for (let i = 0; i < len; i++) {
|
|
196
|
+
if (arrA[i] !== arrB[i])
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 判断两个页面配置是否相等
|
|
203
|
+
*
|
|
204
|
+
* @param a
|
|
205
|
+
* @param b
|
|
206
|
+
*/
|
|
207
|
+
export function isEqualPageOptions(a, b) {
|
|
208
|
+
if (a === b)
|
|
209
|
+
return true;
|
|
210
|
+
if (!a || !b)
|
|
211
|
+
return false;
|
|
212
|
+
// name
|
|
213
|
+
if (a.name !== b.name)
|
|
214
|
+
return false;
|
|
215
|
+
// meta
|
|
216
|
+
if (!recordEqual(a.meta, b.meta))
|
|
217
|
+
return false;
|
|
218
|
+
// pattern (RegExp)
|
|
219
|
+
if (!patternEqual(a.pattern, b.pattern))
|
|
220
|
+
return false;
|
|
221
|
+
// redirect
|
|
222
|
+
if (!redirectEqual(a.redirect, b.redirect))
|
|
223
|
+
return false;
|
|
224
|
+
// alias
|
|
225
|
+
return aliasEqual(a.alias, b.alias);
|
|
226
|
+
}
|
|
@@ -1,27 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 默认导出检测结果
|
|
3
|
-
*/
|
|
4
|
-
export interface DefaultExportCheckResult {
|
|
5
|
-
/** 是否有默认导出 */
|
|
6
|
-
hasDefaultExport: boolean;
|
|
7
|
-
/** 默认导出是否为函数或类 */
|
|
8
|
-
isFunctionOrClass: boolean;
|
|
9
|
-
/** 导出名称(如果有) */
|
|
10
|
-
exportName: string | null;
|
|
11
|
-
/** 警告信息 */
|
|
12
|
-
warning: string | null;
|
|
13
|
-
}
|
|
14
1
|
/**
|
|
15
2
|
* 检测文件是否具有有效的默认导出函数组件
|
|
16
3
|
*
|
|
17
|
-
* @param
|
|
18
|
-
* @
|
|
19
|
-
|
|
20
|
-
export declare function checkDefaultExport(filePath: string): DefaultExportCheckResult;
|
|
21
|
-
/**
|
|
22
|
-
* 检查文件扩展名是否需要导出检测
|
|
23
|
-
*
|
|
24
|
-
* @param ext - 文件扩展名
|
|
25
|
-
* @returns 是否需要检测
|
|
4
|
+
* @param content - 文件内容
|
|
5
|
+
* @param file - 文件路径
|
|
6
|
+
* @returns {boolean} 检测结果
|
|
26
7
|
*/
|
|
27
|
-
export declare function
|
|
8
|
+
export declare function checkDefaultExport(content: string, file: string): boolean;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { parseCode, babelTraverse } from '../utils/babelUtils.js';
|
|
3
|
-
/** 需要进行默认导出检测的文件扩展名 */
|
|
4
|
-
const CHECK_EXPORT_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
|
|
1
|
+
import { babelTraverse, debug, parseCode, warn } from '../utils/index.js';
|
|
5
2
|
/**
|
|
6
3
|
* 收集变量声明
|
|
7
4
|
*
|
|
@@ -23,9 +20,6 @@ function createVariableDeclarationVisitor(variableDeclarations) {
|
|
|
23
20
|
else if (node.init.type === 'FunctionExpression') {
|
|
24
21
|
variableDeclarations.set(name, 'function');
|
|
25
22
|
}
|
|
26
|
-
else if (node.init.type === 'ClassExpression') {
|
|
27
|
-
variableDeclarations.set(name, 'class');
|
|
28
|
-
}
|
|
29
23
|
else {
|
|
30
24
|
variableDeclarations.set(name, 'unknown');
|
|
31
25
|
}
|
|
@@ -37,12 +31,6 @@ function createVariableDeclarationVisitor(variableDeclarations) {
|
|
|
37
31
|
if (node.id) {
|
|
38
32
|
variableDeclarations.set(node.id.name, 'function');
|
|
39
33
|
}
|
|
40
|
-
},
|
|
41
|
-
ClassDeclaration(nodePath) {
|
|
42
|
-
const { node } = nodePath;
|
|
43
|
-
if (node.id) {
|
|
44
|
-
variableDeclarations.set(node.id.name, 'class');
|
|
45
|
-
}
|
|
46
34
|
}
|
|
47
35
|
};
|
|
48
36
|
}
|
|
@@ -62,16 +50,15 @@ function createExportDeclarationVisitor(result, variableDeclarations) {
|
|
|
62
50
|
switch (declaration.type) {
|
|
63
51
|
case 'FunctionDeclaration':
|
|
64
52
|
case 'FunctionExpression':
|
|
53
|
+
case 'ArrowFunctionExpression':
|
|
65
54
|
result.isFunction = true;
|
|
66
|
-
result.exportName = declaration.id?.name || null;
|
|
67
55
|
break;
|
|
68
|
-
case '
|
|
56
|
+
case 'CallExpression':
|
|
69
57
|
result.isFunction = true;
|
|
70
58
|
break;
|
|
71
59
|
case 'Identifier':
|
|
72
|
-
result.exportName = declaration.name;
|
|
73
60
|
const varType = variableDeclarations.get(declaration.name);
|
|
74
|
-
if (varType === 'function' || varType === 'arrow'
|
|
61
|
+
if (varType === 'function' || varType === 'arrow') {
|
|
75
62
|
result.isFunction = true;
|
|
76
63
|
}
|
|
77
64
|
break;
|
|
@@ -110,11 +97,6 @@ function processNamedExportDeclarationNode(declaration, variableDeclarations) {
|
|
|
110
97
|
variableDeclarations.set(declaration.id.name, 'function');
|
|
111
98
|
}
|
|
112
99
|
}
|
|
113
|
-
else if (declaration.type === 'ClassDeclaration') {
|
|
114
|
-
if (declaration.id) {
|
|
115
|
-
variableDeclarations.set(declaration.id.name, 'class');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
100
|
else if (declaration.type === 'VariableDeclaration') {
|
|
119
101
|
for (const decl of declaration.declarations) {
|
|
120
102
|
if (decl.id.type === 'Identifier') {
|
|
@@ -126,9 +108,6 @@ function processNamedExportDeclarationNode(declaration, variableDeclarations) {
|
|
|
126
108
|
else if (decl.init.type === 'FunctionExpression') {
|
|
127
109
|
variableDeclarations.set(name, 'function');
|
|
128
110
|
}
|
|
129
|
-
else if (decl.init.type === 'ClassExpression') {
|
|
130
|
-
variableDeclarations.set(name, 'class');
|
|
131
|
-
}
|
|
132
111
|
}
|
|
133
112
|
}
|
|
134
113
|
}
|
|
@@ -149,9 +128,8 @@ function processExportSpecifiers(specifiers, result, variableDeclarations) {
|
|
|
149
128
|
: specifier.exported.value;
|
|
150
129
|
if (exportedName === 'default') {
|
|
151
130
|
result.hasDefaultExport = true;
|
|
152
|
-
result.exportName = specifier.local.name;
|
|
153
131
|
const varType = variableDeclarations.get(specifier.local.name);
|
|
154
|
-
if (varType === 'function' || varType === 'arrow'
|
|
132
|
+
if (varType === 'function' || varType === 'arrow') {
|
|
155
133
|
result.isFunction = true;
|
|
156
134
|
}
|
|
157
135
|
}
|
|
@@ -161,68 +139,49 @@ function processExportSpecifiers(specifiers, result, variableDeclarations) {
|
|
|
161
139
|
/**
|
|
162
140
|
* 检测文件是否具有有效的默认导出函数组件
|
|
163
141
|
*
|
|
164
|
-
* @param
|
|
165
|
-
* @
|
|
142
|
+
* @param content - 文件内容
|
|
143
|
+
* @param file - 文件路径
|
|
144
|
+
* @returns {boolean} 检测结果
|
|
166
145
|
*/
|
|
167
|
-
export function checkDefaultExport(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
exportName: null,
|
|
173
|
-
warning: `文件不存在: ${filePath}`
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
177
|
-
if (!content.includes('export')) {
|
|
178
|
-
return {
|
|
179
|
-
hasDefaultExport: false,
|
|
180
|
-
isFunctionOrClass: false,
|
|
181
|
-
exportName: null,
|
|
182
|
-
warning: `未检测到默认导出 (default export),该文件将被跳过。请确保导出一个函数组件。`
|
|
183
|
-
};
|
|
146
|
+
export function checkDefaultExport(content, file) {
|
|
147
|
+
// 快速检测,避免对文件进行复杂的解析
|
|
148
|
+
if (!content.includes('export default')) {
|
|
149
|
+
debug(`⚠️ 未检测到默认导出 (default export),该文件可能被跳过。请确保导出一个函数组件。`, `in ${file}`);
|
|
150
|
+
return false;
|
|
184
151
|
}
|
|
152
|
+
// 解析文件内容为 AST 进行精确检查
|
|
153
|
+
const ast = parseCode(content);
|
|
154
|
+
const variableDeclarations = new Map();
|
|
155
|
+
const result = {
|
|
156
|
+
hasDefaultExport: false,
|
|
157
|
+
isFunction: false
|
|
158
|
+
};
|
|
185
159
|
try {
|
|
186
|
-
const ast = parseCode(content);
|
|
187
|
-
const variableDeclarations = new Map();
|
|
188
|
-
const result = {
|
|
189
|
-
hasDefaultExport: false,
|
|
190
|
-
isFunction: false,
|
|
191
|
-
exportName: null
|
|
192
|
-
};
|
|
193
160
|
babelTraverse(ast, {
|
|
194
161
|
...createVariableDeclarationVisitor(variableDeclarations),
|
|
195
|
-
...createExportDeclarationVisitor(result, variableDeclarations)
|
|
162
|
+
...createExportDeclarationVisitor(result, variableDeclarations),
|
|
163
|
+
CallExpression(nodePath) {
|
|
164
|
+
const { node } = nodePath;
|
|
165
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'console') {
|
|
166
|
+
nodePath.remove();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
196
169
|
});
|
|
197
|
-
let
|
|
170
|
+
let debugInfo = null;
|
|
198
171
|
if (!result.hasDefaultExport) {
|
|
199
|
-
|
|
172
|
+
debugInfo = `⚠️ 未检测到默认导出 (default export),该文件可能被跳过。请确保导出一个函数组件。`;
|
|
200
173
|
}
|
|
201
174
|
else if (!result.isFunction) {
|
|
202
|
-
|
|
175
|
+
debugInfo = `⚠️ 默认导出的不是函数,该文件可能被跳过。请确保导出一个有效的函数组件。`;
|
|
203
176
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
};
|
|
177
|
+
if (debugInfo) {
|
|
178
|
+
debug(debugInfo, `in ${file}`);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
210
182
|
}
|
|
211
|
-
catch (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
isFunctionOrClass: false,
|
|
215
|
-
exportName: null,
|
|
216
|
-
warning: `解析文件失败: ${error instanceof Error ? error.message : String(error)}`
|
|
217
|
-
};
|
|
183
|
+
catch (e) {
|
|
184
|
+
warn(`⚠️ 无法解析文件 ${file}`, `${e instanceof Error ? e.message : String(e)}`);
|
|
185
|
+
return false;
|
|
218
186
|
}
|
|
219
187
|
}
|
|
220
|
-
/**
|
|
221
|
-
* 检查文件扩展名是否需要导出检测
|
|
222
|
-
*
|
|
223
|
-
* @param ext - 文件扩展名
|
|
224
|
-
* @returns 是否需要检测
|
|
225
|
-
*/
|
|
226
|
-
export function shouldCheckExport(ext) {
|
|
227
|
-
return CHECK_EXPORT_EXTENSIONS.includes(ext);
|
|
228
|
-
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface FilterOptions {
|
|
2
|
+
dir: string;
|
|
3
|
+
include: readonly string[];
|
|
4
|
+
exclude: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 检查文件是否为页面文件(单目录版本)
|
|
8
|
+
*
|
|
9
|
+
* 支持 glob 模式匹配包含和排除规则。
|
|
10
|
+
*
|
|
11
|
+
* @param file - 文件路径
|
|
12
|
+
* @param options - 页面目录配置
|
|
13
|
+
* @returns 如果是有效的页面文件返回 true
|
|
14
|
+
*/
|
|
15
|
+
export declare function isPageFile(file: string, options: FilterOptions): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 检查文件是否为页面文件(多目录版本)
|
|
18
|
+
*
|
|
19
|
+
* 遍历所有页面目录配置,检查文件是否属于任一目录。
|
|
20
|
+
*
|
|
21
|
+
* @param file - 文件路径
|
|
22
|
+
* @param pages - 页面目录配置列表
|
|
23
|
+
* @returns 如果是有效的页面文件返回 true
|
|
24
|
+
*/
|
|
25
|
+
export declare function isPageFileInDirs(file: string, pages: readonly FilterOptions[]): FilterOptions | false;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 文件过滤工具模块
|
|
3
|
+
*
|
|
4
|
+
* 提供基于 glob 模式的文件包含/排除过滤功能。
|
|
5
|
+
* 从 scanPages.ts 中提取,消除重复代码。
|
|
6
|
+
*/
|
|
7
|
+
import micromatch from 'micromatch';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { normalizePathSeparator } from '../utils/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* 检查文件是否为页面文件(单目录版本)
|
|
12
|
+
*
|
|
13
|
+
* 支持 glob 模式匹配包含和排除规则。
|
|
14
|
+
*
|
|
15
|
+
* @param file - 文件路径
|
|
16
|
+
* @param options - 页面目录配置
|
|
17
|
+
* @returns 如果是有效的页面文件返回 true
|
|
18
|
+
*/
|
|
19
|
+
export function isPageFile(file, options) {
|
|
20
|
+
const { dir, include, exclude } = options;
|
|
21
|
+
if (!file.startsWith(dir))
|
|
22
|
+
return false;
|
|
23
|
+
const normalizedPath = normalizePathSeparator(path.relative(dir, file));
|
|
24
|
+
if (include.length === 0)
|
|
25
|
+
return true;
|
|
26
|
+
return micromatch.isMatch(normalizedPath, include, { dot: true, noext: true, ignore: exclude });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 检查文件是否为页面文件(多目录版本)
|
|
30
|
+
*
|
|
31
|
+
* 遍历所有页面目录配置,检查文件是否属于任一目录。
|
|
32
|
+
*
|
|
33
|
+
* @param file - 文件路径
|
|
34
|
+
* @param pages - 页面目录配置列表
|
|
35
|
+
* @returns 如果是有效的页面文件返回 true
|
|
36
|
+
*/
|
|
37
|
+
export function isPageFileInDirs(file, pages) {
|
|
38
|
+
for (const page of pages) {
|
|
39
|
+
if (isPageFile(file, page))
|
|
40
|
+
return page;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|