@vitarx/plugin-vite 0.0.1-beta.7 → 0.0.1-beta.8
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/context.d.ts +8 -2
- package/dist/context.js +3 -0
- package/dist/hmr-client/index.d.ts +2 -1
- package/dist/hmr-client/index.js +3 -2
- package/dist/index.js +3 -1
- package/dist/passes/directives/processDirectives.js +1 -1
- package/dist/passes/hmr/inject-imports.d.ts +19 -0
- package/dist/passes/hmr/inject-imports.js +26 -0
- package/dist/passes/hmr/inject-state.d.ts +29 -0
- package/dist/passes/hmr/inject-state.js +101 -0
- package/dist/passes/hmr/inject.d.ts +1 -1
- package/dist/passes/hmr/inject.js +13 -123
- package/dist/passes/jsx/processChildren.js +1 -1
- package/dist/passes/jsx/processJSXElement.js +1 -1
- package/dist/passes/jsx/processJSXFragment.js +4 -4
- package/dist/passes/props/index.js +1 -8
- package/dist/transform.d.ts +5 -8
- package/dist/transform.js +17 -18
- package/dist/types.d.ts +11 -0
- package/dist/utils/ast-builders.d.ts +2 -1
- package/dist/utils/ast-builders.js +4 -5
- package/dist/utils/branch-factory.js +1 -1
- package/dist/utils/collect-names.d.ts +37 -0
- package/dist/utils/collect-names.js +129 -0
- package/dist/utils/component-collect.d.ts +9 -4
- package/dist/utils/component-collect.js +64 -74
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/jsx-helpers.d.ts +3 -2
- package/dist/utils/jsx-helpers.js +3 -2
- package/package.json +10 -3
package/dist/context.d.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* 定义编译转换过程中的上下文数据结构
|
|
4
4
|
* @module context
|
|
5
5
|
*/
|
|
6
|
-
import type { File } from '@babel/types';
|
|
6
|
+
import type { CallExpression, File } from '@babel/types';
|
|
7
7
|
import type { CompilerWarning } from './error.js';
|
|
8
|
-
import type { CompileOptions } from './
|
|
8
|
+
import type { CompileOptions } from './types.js';
|
|
9
9
|
/**
|
|
10
10
|
* 导入信息
|
|
11
11
|
* 追踪需要注入的运行时 API
|
|
@@ -80,6 +80,12 @@ export interface TransformContext {
|
|
|
80
80
|
refVariables: Set<string>;
|
|
81
81
|
/** UI API 别名集合(用于 HMR 代码分离识别) */
|
|
82
82
|
uiApiAliases: Set<string>;
|
|
83
|
+
/** builder 函数的本地别名(用于识别纯构建组件) */
|
|
84
|
+
builderAlias: string | null;
|
|
85
|
+
/** 已处理的 AST 节点追踪集合 */
|
|
86
|
+
processedNodes: WeakSet<any>;
|
|
87
|
+
/** 已添加 PURE 注释的节点追踪集合 */
|
|
88
|
+
pureCommentedNodes: WeakSet<CallExpression>;
|
|
83
89
|
/** 编译警告列表 */
|
|
84
90
|
warnings: CompilerWarning[];
|
|
85
91
|
}
|
package/dist/context.js
CHANGED
package/dist/hmr-client/index.js
CHANGED
|
@@ -80,11 +80,12 @@ class HMRManager {
|
|
|
80
80
|
* 注册节点
|
|
81
81
|
*
|
|
82
82
|
* @param view - 组件视图节点
|
|
83
|
+
* @param component - 组件构造函数
|
|
83
84
|
*/
|
|
84
|
-
register(view) {
|
|
85
|
+
register(view, component) {
|
|
85
86
|
if (!view)
|
|
86
87
|
return;
|
|
87
|
-
|
|
88
|
+
component ?? (component = view.component);
|
|
88
89
|
const id = this.getId(component);
|
|
89
90
|
if (__classPrivateFieldGet(this, _HMRManager_idMapToView, "f").has(id)) {
|
|
90
91
|
__classPrivateFieldGet(this, _HMRManager_idMapToView, "f").get(id).add(view);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { version } from 'vite';
|
|
4
|
-
import { transform } from './transform.js';
|
|
4
|
+
import { shouldTransform, transform } from './transform.js';
|
|
5
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const IS_V8 = version.startsWith('8');
|
|
7
7
|
let viteTransform;
|
|
@@ -63,6 +63,8 @@ export default function vitarx(options) {
|
|
|
63
63
|
};
|
|
64
64
|
},
|
|
65
65
|
async transform(code, id) {
|
|
66
|
+
if (!shouldTransform(id))
|
|
67
|
+
return null;
|
|
66
68
|
const result = await transform(code, id, compileOptions);
|
|
67
69
|
if (!result)
|
|
68
70
|
return null;
|
|
@@ -27,7 +27,7 @@ export function processDirectives(viewCall, directives, ctx) {
|
|
|
27
27
|
}
|
|
28
28
|
markImport(ctx, 'withDirectives');
|
|
29
29
|
const withDirectivesAlias = getAlias(ctx.vitarxAliases, 'withDirectives');
|
|
30
|
-
return addPureComment(createWithDirectivesCall(viewCall, directiveArray, withDirectivesAlias));
|
|
30
|
+
return addPureComment(createWithDirectivesCall(viewCall, directiveArray, withDirectivesAlias), ctx);
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* 构建指令值对象
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMR 导入注入模块
|
|
3
|
+
* 负责注入 HMR 所需的导入语句
|
|
4
|
+
* @module passes/hmr/inject-imports
|
|
5
|
+
*/
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
/** getComponentView 的内部别名,避免与用户代码冲突 */
|
|
8
|
+
export declare const GET_COMPONENT_VIEW_ALIAS = "__$VITARX_GET_COMPONENT_VIEW$__";
|
|
9
|
+
/**
|
|
10
|
+
* 注入 HMR 客户端导入
|
|
11
|
+
* @param program - AST Program 节点
|
|
12
|
+
*/
|
|
13
|
+
export declare function injectHMRImport(program: t.Program): void;
|
|
14
|
+
/**
|
|
15
|
+
* 注入 getComponentView 导入
|
|
16
|
+
* 使用唯一的别名避免与用户代码冲突
|
|
17
|
+
* @param program - AST Program 节点
|
|
18
|
+
*/
|
|
19
|
+
export declare function injectGetComponentViewImport(program: t.Program): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMR 导入注入模块
|
|
3
|
+
* 负责注入 HMR 所需的导入语句
|
|
4
|
+
* @module passes/hmr/inject-imports
|
|
5
|
+
*/
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
import { HMR } from '../../constants/index.js';
|
|
8
|
+
/** getComponentView 的内部别名,避免与用户代码冲突 */
|
|
9
|
+
export const GET_COMPONENT_VIEW_ALIAS = '__$VITARX_GET_COMPONENT_VIEW$__';
|
|
10
|
+
/**
|
|
11
|
+
* 注入 HMR 客户端导入
|
|
12
|
+
* @param program - AST Program 节点
|
|
13
|
+
*/
|
|
14
|
+
export function injectHMRImport(program) {
|
|
15
|
+
const importDecl = t.importDeclaration([t.importDefaultSpecifier(t.identifier(HMR.manager))], t.stringLiteral('@vitarx/vite-plugin/hmr-client'));
|
|
16
|
+
program.body.unshift(importDecl);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 注入 getComponentView 导入
|
|
20
|
+
* 使用唯一的别名避免与用户代码冲突
|
|
21
|
+
* @param program - AST Program 节点
|
|
22
|
+
*/
|
|
23
|
+
export function injectGetComponentViewImport(program) {
|
|
24
|
+
const importDecl = t.importDeclaration([t.importSpecifier(t.identifier(GET_COMPONENT_VIEW_ALIAS), t.identifier('getComponentView'))], t.stringLiteral('vitarx'));
|
|
25
|
+
program.body.unshift(importDecl);
|
|
26
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMR 状态恢复模块
|
|
3
|
+
* 负责为组件函数注入状态保存和恢复代码
|
|
4
|
+
* @module passes/hmr/inject-state
|
|
5
|
+
*/
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
/**
|
|
8
|
+
* 创建组件函数体内的 HMR 注册代码
|
|
9
|
+
* @param variableNames - 需要追踪的变量名列表
|
|
10
|
+
* @returns HMR 注册语句数组
|
|
11
|
+
*/
|
|
12
|
+
export declare function createHMRRegistrationStatements(variableNames: string[]): t.Statement[];
|
|
13
|
+
/**
|
|
14
|
+
* 创建包装组件的 HMR 注册代码
|
|
15
|
+
* 只注册节点,不保存状态,传入组件函数自身作为第二个参数
|
|
16
|
+
* @param componentName - 组件函数名称
|
|
17
|
+
* @returns HMR 注册语句数组
|
|
18
|
+
*/
|
|
19
|
+
export declare function createWrappedComponentHMRStatements(componentName: string): t.Statement[];
|
|
20
|
+
/**
|
|
21
|
+
* 从函数体中收集局部变量名
|
|
22
|
+
* @param functionBody - 函数体语句块
|
|
23
|
+
* @returns 变量名数组
|
|
24
|
+
*/
|
|
25
|
+
export declare function collectLocalVariableNames(functionBody: t.BlockStatement): string[];
|
|
26
|
+
/**
|
|
27
|
+
* 为函数体内的变量声明注入状态恢复代码
|
|
28
|
+
*/
|
|
29
|
+
export declare function injectStatePreservation(functionBody: t.BlockStatement): void;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMR 状态恢复模块
|
|
3
|
+
* 负责为组件函数注入状态保存和恢复代码
|
|
4
|
+
* @module passes/hmr/inject-state
|
|
5
|
+
*/
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
import { HMR } from '../../constants/index.js';
|
|
8
|
+
import { collectPatternBindings } from '../../utils/index.js';
|
|
9
|
+
import { GET_COMPONENT_VIEW_ALIAS } from './inject-imports.js';
|
|
10
|
+
/**
|
|
11
|
+
* 创建组件函数体内的 HMR 注册代码
|
|
12
|
+
* @param variableNames - 需要追踪的变量名列表
|
|
13
|
+
* @returns HMR 注册语句数组
|
|
14
|
+
*/
|
|
15
|
+
export function createHMRRegistrationStatements(variableNames) {
|
|
16
|
+
const statements = [];
|
|
17
|
+
statements.push(t.variableDeclaration('const', [
|
|
18
|
+
t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), []))
|
|
19
|
+
]));
|
|
20
|
+
statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('register')), [t.identifier(HMR.view)])));
|
|
21
|
+
const stateProperties = variableNames.map(name => t.objectMethod('get', t.identifier(name), [], t.blockStatement([t.returnStatement(t.identifier(name))])));
|
|
22
|
+
statements.push(t.expressionStatement(t.logicalExpression('&&', t.identifier(HMR.view), t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.identifier('Promise'), t.identifier('resolve')), []), t.identifier('then')), [
|
|
23
|
+
t.arrowFunctionExpression([], t.blockStatement([
|
|
24
|
+
t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier(HMR.view), t.identifier(HMR.state)), t.objectExpression(stateProperties)))
|
|
25
|
+
]))
|
|
26
|
+
]))));
|
|
27
|
+
return statements;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 创建包装组件的 HMR 注册代码
|
|
31
|
+
* 只注册节点,不保存状态,传入组件函数自身作为第二个参数
|
|
32
|
+
* @param componentName - 组件函数名称
|
|
33
|
+
* @returns HMR 注册语句数组
|
|
34
|
+
*/
|
|
35
|
+
export function createWrappedComponentHMRStatements(componentName) {
|
|
36
|
+
const statements = [];
|
|
37
|
+
statements.push(t.variableDeclaration('const', [
|
|
38
|
+
t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), []))
|
|
39
|
+
]));
|
|
40
|
+
statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('register')), [t.identifier(HMR.view), t.identifier(componentName)])));
|
|
41
|
+
return statements;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 从函数体中收集局部变量名
|
|
45
|
+
* @param functionBody - 函数体语句块
|
|
46
|
+
* @returns 变量名数组
|
|
47
|
+
*/
|
|
48
|
+
export function collectLocalVariableNames(functionBody) {
|
|
49
|
+
const variableNames = new Set();
|
|
50
|
+
for (const stmt of functionBody.body) {
|
|
51
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
52
|
+
for (const decl of stmt.declarations) {
|
|
53
|
+
if (decl.id.type !== 'VoidPattern') {
|
|
54
|
+
collectPatternBindings(decl.id, variableNames);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return Array.from(variableNames);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 判断表达式是否为函数类型
|
|
63
|
+
*/
|
|
64
|
+
function isFunctionExpression(expr) {
|
|
65
|
+
if (!expr)
|
|
66
|
+
return false;
|
|
67
|
+
return (expr.type === 'ArrowFunctionExpression' ||
|
|
68
|
+
expr.type === 'FunctionExpression' ||
|
|
69
|
+
expr.type === 'ClassExpression');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 创建状态恢复表达式
|
|
73
|
+
* 格式:__$VITARX_HMR$__.instance.memo(__$VITARX_HMR_VIEW_NODE$__, '变量名') ?? 原始初始值
|
|
74
|
+
*/
|
|
75
|
+
function createMemoExpression(variableName, originalInit) {
|
|
76
|
+
return t.logicalExpression('??', t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('memo')), [t.identifier(HMR.view), t.stringLiteral(variableName)]), originalInit);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 为单个变量声明注入状态恢复代码
|
|
80
|
+
*/
|
|
81
|
+
function injectStatePreservationForDeclaration(decl) {
|
|
82
|
+
if (decl.id.type !== 'Identifier')
|
|
83
|
+
return;
|
|
84
|
+
if (!decl.init)
|
|
85
|
+
return;
|
|
86
|
+
if (isFunctionExpression(decl.init))
|
|
87
|
+
return;
|
|
88
|
+
decl.init = createMemoExpression(decl.id.name, decl.init);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 为函数体内的变量声明注入状态恢复代码
|
|
92
|
+
*/
|
|
93
|
+
export function injectStatePreservation(functionBody) {
|
|
94
|
+
for (const stmt of functionBody.body) {
|
|
95
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
96
|
+
for (const decl of stmt.declarations) {
|
|
97
|
+
injectStatePreservationForDeclaration(decl);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -5,133 +5,28 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
7
|
import { HMR } from '../../constants/index.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
const GET_COMPONENT_VIEW_ALIAS = '__$VITARX_GET_COMPONENT_VIEW$__';
|
|
11
|
-
/**
|
|
12
|
-
* 注入 HMR 客户端导入
|
|
13
|
-
* @param program - AST Program 节点
|
|
14
|
-
*/
|
|
15
|
-
function injectHMRImport(program) {
|
|
16
|
-
const importDecl = t.importDeclaration([t.importDefaultSpecifier(t.identifier(HMR.manager))], t.stringLiteral('@vitarx/vite-plugin/hmr-client'));
|
|
17
|
-
program.body.unshift(importDecl);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* 注入 getComponentView 导入
|
|
21
|
-
* 使用唯一的别名避免与用户代码冲突
|
|
22
|
-
* @param program - AST Program 节点
|
|
23
|
-
*/
|
|
24
|
-
function injectGetComponentViewImport(program) {
|
|
25
|
-
// 创建独立的 import 语句,使用不会冲突的别名
|
|
26
|
-
const importDecl = t.importDeclaration([t.importSpecifier(t.identifier(GET_COMPONENT_VIEW_ALIAS), t.identifier('getComponentView'))], t.stringLiteral('vitarx'));
|
|
27
|
-
program.body.unshift(importDecl);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 创建组件函数体内的 HMR 注册代码
|
|
31
|
-
* @param variableNames - 需要追踪的变量名列表
|
|
32
|
-
* @returns HMR 注册语句数组
|
|
33
|
-
*/
|
|
34
|
-
function createHMRRegistrationStatements(variableNames) {
|
|
35
|
-
const statements = [];
|
|
36
|
-
// 声明并获取当前视图实例
|
|
37
|
-
statements.push(t.variableDeclaration('const', [
|
|
38
|
-
t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), []))
|
|
39
|
-
]));
|
|
40
|
-
// 注册到 HMR 管理器
|
|
41
|
-
statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('register')), [t.identifier(HMR.view)])));
|
|
42
|
-
// 创建状态追踪对象
|
|
43
|
-
const stateProperties = variableNames.map(name => t.objectMethod('get', t.identifier(name), [], t.blockStatement([t.returnStatement(t.identifier(name))])));
|
|
44
|
-
// 异步设置状态
|
|
45
|
-
statements.push(t.expressionStatement(t.logicalExpression('&&', t.identifier(HMR.view), t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.identifier('Promise'), t.identifier('resolve')), []), t.identifier('then')), [
|
|
46
|
-
t.arrowFunctionExpression([], t.blockStatement([
|
|
47
|
-
t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier(HMR.view), t.identifier(HMR.state)), t.objectExpression(stateProperties)))
|
|
48
|
-
]))
|
|
49
|
-
]))));
|
|
50
|
-
return statements;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* 从函数体中收集局部变量名
|
|
54
|
-
* @param functionBody - 函数体语句块
|
|
55
|
-
* @returns 变量名数组
|
|
56
|
-
*/
|
|
57
|
-
function collectLocalVariableNames(functionBody) {
|
|
58
|
-
const variableNames = new Set();
|
|
59
|
-
for (const stmt of functionBody.body) {
|
|
60
|
-
if (stmt.type === 'VariableDeclaration') {
|
|
61
|
-
for (const decl of stmt.declarations) {
|
|
62
|
-
if (decl.id.type !== 'VoidPattern') {
|
|
63
|
-
collectPatternBindings(decl.id, variableNames);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return Array.from(variableNames);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* 判断表达式是否为函数类型
|
|
72
|
-
* 包括:箭头函数、函数表达式、类表达式
|
|
73
|
-
*/
|
|
74
|
-
function isFunctionExpression(expr) {
|
|
75
|
-
if (!expr)
|
|
76
|
-
return false;
|
|
77
|
-
return (expr.type === 'ArrowFunctionExpression' ||
|
|
78
|
-
expr.type === 'FunctionExpression' ||
|
|
79
|
-
expr.type === 'ClassExpression');
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* 创建状态恢复表达式
|
|
83
|
-
* 格式:__$VITARX_HMR$__.instance.memo(__$VITARX_HMR_VIEW_NODE$__, '变量名') ?? 原始初始值
|
|
84
|
-
*/
|
|
85
|
-
function createMemoExpression(variableName, originalInit) {
|
|
86
|
-
return t.logicalExpression('??', t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('memo')), [t.identifier(HMR.view), t.stringLiteral(variableName)]), originalInit);
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* 为单个变量声明注入状态恢复代码
|
|
90
|
-
* 仅处理标识符类型的变量声明,且初始值不是函数
|
|
91
|
-
*/
|
|
92
|
-
function injectStatePreservationForDeclaration(decl) {
|
|
93
|
-
// 只处理标识符类型的变量声明
|
|
94
|
-
if (decl.id.type !== 'Identifier')
|
|
95
|
-
return;
|
|
96
|
-
// 没有初始值,不需要处理
|
|
97
|
-
if (!decl.init)
|
|
98
|
-
return;
|
|
99
|
-
// 初始值是函数,不需要状态恢复
|
|
100
|
-
if (isFunctionExpression(decl.init))
|
|
101
|
-
return;
|
|
102
|
-
// 注入状态恢复代码
|
|
103
|
-
decl.init = createMemoExpression(decl.id.name, decl.init);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* 为函数体内的变量声明注入状态恢复代码
|
|
107
|
-
*/
|
|
108
|
-
function injectStatePreservation(functionBody) {
|
|
109
|
-
for (const stmt of functionBody.body) {
|
|
110
|
-
if (stmt.type === 'VariableDeclaration') {
|
|
111
|
-
for (const decl of stmt.declarations) {
|
|
112
|
-
injectStatePreservationForDeclaration(decl);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
8
|
+
import { injectHMRImport, injectGetComponentViewImport } from './inject-imports.js';
|
|
9
|
+
import { createHMRRegistrationStatements, createWrappedComponentHMRStatements, collectLocalVariableNames, injectStatePreservation } from './inject-state.js';
|
|
117
10
|
/**
|
|
118
11
|
* 为组件函数注入 HMR 注册代码
|
|
119
12
|
* @param func - 函数声明/表达式/箭头函数
|
|
120
13
|
* @param variableNames - 需要追踪的变量名列表
|
|
14
|
+
* @param isWrapped - 是否为包装组件
|
|
15
|
+
* @param componentName - 组件名称(包装组件需要)
|
|
121
16
|
*/
|
|
122
|
-
function injectHMRIntoFunction(func, variableNames) {
|
|
123
|
-
// 处理箭头函数没有函数体的情况(表达式体)
|
|
17
|
+
function injectHMRIntoFunction(func, variableNames, isWrapped = false, componentName) {
|
|
124
18
|
if (func.type === 'ArrowFunctionExpression' && func.body.type !== 'BlockStatement') {
|
|
125
|
-
// 将表达式体转换为块语句
|
|
126
19
|
const returnStmt = t.returnStatement(func.body);
|
|
127
20
|
func.body = t.blockStatement([returnStmt]);
|
|
128
21
|
}
|
|
129
22
|
if (!func.body || func.body.type !== 'BlockStatement')
|
|
130
23
|
return;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const statements =
|
|
24
|
+
if (!isWrapped) {
|
|
25
|
+
injectStatePreservation(func.body);
|
|
26
|
+
}
|
|
27
|
+
const statements = isWrapped
|
|
28
|
+
? createWrappedComponentHMRStatements(componentName)
|
|
29
|
+
: createHMRRegistrationStatements(variableNames);
|
|
135
30
|
func.body.body.unshift(...statements);
|
|
136
31
|
}
|
|
137
32
|
/**
|
|
@@ -171,19 +66,14 @@ function generateComponentId(filename, componentName) {
|
|
|
171
66
|
export function injectHMRSupport(program, components, filename) {
|
|
172
67
|
if (components.length === 0)
|
|
173
68
|
return;
|
|
174
|
-
// 注入必要的导入
|
|
175
69
|
injectHMRImport(program);
|
|
176
70
|
injectGetComponentViewImport(program);
|
|
177
|
-
|
|
178
|
-
for (const { node } of components) {
|
|
179
|
-
// 收集局部变量名(如果有函数体)
|
|
71
|
+
for (const { node, isWrapped, name } of components) {
|
|
180
72
|
const variableNames = node.body?.type === 'BlockStatement' ? collectLocalVariableNames(node.body) : [];
|
|
181
|
-
injectHMRIntoFunction(node, variableNames);
|
|
73
|
+
injectHMRIntoFunction(node, variableNames, isWrapped, name);
|
|
182
74
|
}
|
|
183
|
-
// 为每个组件绑定 ID
|
|
184
75
|
for (const { name } of components) {
|
|
185
76
|
program.body.push(createBindIdStatement(name, generateComponentId(filename, name)));
|
|
186
77
|
}
|
|
187
|
-
// 注入 import.meta.hot.accept
|
|
188
78
|
program.body.push(createHotAcceptStatement());
|
|
189
79
|
}
|
|
@@ -22,7 +22,7 @@ function handleLogicalExpression(expr, ctx, loc) {
|
|
|
22
22
|
markImport(ctx, 'dynamic');
|
|
23
23
|
const dynamicAlias = getAlias(ctx.vitarxAliases, 'dynamic');
|
|
24
24
|
const locInfo = loc ? getDevLocInfo(ctx, { loc }) : null;
|
|
25
|
-
return addPureComment(createDynamicCall(expr, dynamicAlias, locInfo));
|
|
25
|
+
return addPureComment(createDynamicCall(expr, dynamicAlias, locInfo), ctx);
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* 处理子节点数组
|
|
@@ -87,7 +87,7 @@ export function transformJSXElement(node, ctx, handleVIf = false) {
|
|
|
87
87
|
viewCall = processDirectives(viewCall, directives, ctx);
|
|
88
88
|
}
|
|
89
89
|
else {
|
|
90
|
-
viewCall = addPureComment(viewCall);
|
|
90
|
+
viewCall = addPureComment(viewCall, ctx);
|
|
91
91
|
}
|
|
92
92
|
if (node.loc) {
|
|
93
93
|
viewCall.loc = node.loc;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
|
-
import { addPureComment, createCreateViewCall, filterWhitespaceChildren, getAlias, getDevLocInfo,
|
|
2
|
+
import { addPureComment, createCreateViewCall, filterWhitespaceChildren, getAlias, getDevLocInfo, validateNoDirectMatchChild } from '../../utils/index.js';
|
|
3
3
|
import { processChildren } from './processChildren.js';
|
|
4
4
|
/**
|
|
5
5
|
* 处理 JSX Fragment
|
|
@@ -11,7 +11,7 @@ export function processJSXFragment(path, ctx) {
|
|
|
11
11
|
// 过滤空白子节点
|
|
12
12
|
const children = filterWhitespaceChildren(node.children);
|
|
13
13
|
// 校验 Match 组件
|
|
14
|
-
|
|
14
|
+
validateNoDirectMatchChild(children);
|
|
15
15
|
// 标记需要的导入
|
|
16
16
|
ctx.imports.Fragment = true;
|
|
17
17
|
ctx.imports.createView = true;
|
|
@@ -20,7 +20,7 @@ export function processJSXFragment(path, ctx) {
|
|
|
20
20
|
const locInfo = getDevLocInfo(ctx, node);
|
|
21
21
|
// 无子元素
|
|
22
22
|
if (children.length === 0) {
|
|
23
|
-
const viewCall = addPureComment(createCreateViewCall(t.identifier(fragmentAlias), null, locInfo, createViewAlias));
|
|
23
|
+
const viewCall = addPureComment(createCreateViewCall(t.identifier(fragmentAlias), null, locInfo, createViewAlias), ctx);
|
|
24
24
|
if (node.loc)
|
|
25
25
|
viewCall.loc = node.loc;
|
|
26
26
|
path.replaceWith(viewCall);
|
|
@@ -30,7 +30,7 @@ export function processJSXFragment(path, ctx) {
|
|
|
30
30
|
const processedChildren = processChildren(children, ctx);
|
|
31
31
|
const childrenValue = processedChildren.length === 1 ? processedChildren[0] : t.arrayExpression(processedChildren);
|
|
32
32
|
const props = t.objectExpression([t.objectProperty(t.identifier('children'), childrenValue)]);
|
|
33
|
-
const viewCall = addPureComment(createCreateViewCall(t.identifier(fragmentAlias), props, locInfo, createViewAlias));
|
|
33
|
+
const viewCall = addPureComment(createCreateViewCall(t.identifier(fragmentAlias), props, locInfo, createViewAlias), ctx);
|
|
34
34
|
if (node.loc)
|
|
35
35
|
viewCall.loc = node.loc;
|
|
36
36
|
path.replaceWith(viewCall);
|
|
@@ -5,16 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
7
|
import { createError } from '../../error.js';
|
|
8
|
-
import { isNativeElement } from '../../utils/index.js';
|
|
8
|
+
import { getJSXElementName, isNativeElement } from '../../utils/index.js';
|
|
9
9
|
import { createProperty, getAttributeValue, processAttribute, processSpreadAttribute } from './attribute.js';
|
|
10
10
|
import { createVModelProps, extractVModelState } from './vmodel.js';
|
|
11
|
-
/**
|
|
12
|
-
* 获取 JSX 元素的名称
|
|
13
|
-
*/
|
|
14
|
-
function getJSXElementName(node) {
|
|
15
|
-
const nameNode = node.openingElement.name;
|
|
16
|
-
return nameNode.type === 'JSXIdentifier' ? nameNode.name : null;
|
|
17
|
-
}
|
|
18
11
|
/**
|
|
19
12
|
* 校验 class 和 className 不能同时存在
|
|
20
13
|
*/
|
package/dist/transform.d.ts
CHANGED
|
@@ -4,16 +4,13 @@
|
|
|
4
4
|
* @module transform
|
|
5
5
|
*/
|
|
6
6
|
import { type GeneratorResult } from '@babel/generator';
|
|
7
|
+
import type { CompileOptions } from './types.js';
|
|
7
8
|
export interface TransformResult extends GeneratorResult {
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
runtimeModule: string;
|
|
14
|
-
sourceMap: boolean | 'inline' | 'both';
|
|
15
|
-
transformClassNameToClass: boolean;
|
|
16
|
-
}
|
|
10
|
+
/**
|
|
11
|
+
* 检查文件是否需要转换
|
|
12
|
+
*/
|
|
13
|
+
export declare function shouldTransform(id: string): boolean;
|
|
17
14
|
/**
|
|
18
15
|
* 转换 JSX/TSX 代码
|
|
19
16
|
*/
|
package/dist/transform.js
CHANGED
|
@@ -9,15 +9,6 @@ import traverse from '@babel/traverse';
|
|
|
9
9
|
import { createContext } from './context.js';
|
|
10
10
|
import { collectExistingImports, collectLocalBindings, collectRefApiAliases, collectRefVariables, injectHMRSupport, injectImports, processJSXElement, processJSXFragment, processPureCompileComponent, processVIfChain, transformJSXElement } from './passes/index.js';
|
|
11
11
|
import { collectComponentFunctions, generateUniqueAlias, getJSXElementName, isPureCompileComponent } from './utils/index.js';
|
|
12
|
-
/** 用于追踪已处理的节点 */
|
|
13
|
-
const processedNodes = new WeakSet();
|
|
14
|
-
/**
|
|
15
|
-
* 检查文件是否需要转换
|
|
16
|
-
*/
|
|
17
|
-
function shouldTransform(id) {
|
|
18
|
-
const ext = id.split('?')[0].split('.').pop()?.toLowerCase();
|
|
19
|
-
return ext === 'jsx' || ext === 'tsx';
|
|
20
|
-
}
|
|
21
12
|
/**
|
|
22
13
|
* 创建解析器选项
|
|
23
14
|
*/
|
|
@@ -66,6 +57,9 @@ function setupAliases(ctx, program) {
|
|
|
66
57
|
if (ctx.options.hmr) {
|
|
67
58
|
ctx.vitarxAliases.createView = generateUniqueAlias('jsxDEV', allNames);
|
|
68
59
|
}
|
|
60
|
+
if (vitarxImports.has('builder')) {
|
|
61
|
+
ctx.builderAlias = vitarxImports.get('builder');
|
|
62
|
+
}
|
|
69
63
|
}
|
|
70
64
|
/**
|
|
71
65
|
* 收集 ref 相关信息
|
|
@@ -83,21 +77,21 @@ function transformAST(ast, ctx) {
|
|
|
83
77
|
babelTraverse(ast, {
|
|
84
78
|
JSXElement: {
|
|
85
79
|
enter(path) {
|
|
86
|
-
if (processedNodes.has(path.node))
|
|
80
|
+
if (ctx.processedNodes.has(path.node))
|
|
87
81
|
return;
|
|
88
82
|
const name = getJSXElementName(path.node);
|
|
89
83
|
if (name && isPureCompileComponent(name)) {
|
|
90
|
-
processedNodes.add(path.node);
|
|
84
|
+
ctx.processedNodes.add(path.node);
|
|
91
85
|
processPureCompileComponent(path, ctx);
|
|
92
86
|
}
|
|
93
87
|
},
|
|
94
88
|
exit(path) {
|
|
95
|
-
if (processedNodes.has(path.node))
|
|
89
|
+
if (ctx.processedNodes.has(path.node))
|
|
96
90
|
return;
|
|
97
91
|
const name = getJSXElementName(path.node);
|
|
98
92
|
if (name && isPureCompileComponent(name))
|
|
99
93
|
return;
|
|
100
|
-
processedNodes.add(path.node);
|
|
94
|
+
ctx.processedNodes.add(path.node);
|
|
101
95
|
processJSXElement(path, ctx);
|
|
102
96
|
}
|
|
103
97
|
},
|
|
@@ -106,9 +100,9 @@ function transformAST(ast, ctx) {
|
|
|
106
100
|
processVIfChain(path, ctx, transformJSXElement);
|
|
107
101
|
},
|
|
108
102
|
exit(path) {
|
|
109
|
-
if (processedNodes.has(path.node))
|
|
103
|
+
if (ctx.processedNodes.has(path.node))
|
|
110
104
|
return;
|
|
111
|
-
processedNodes.add(path.node);
|
|
105
|
+
ctx.processedNodes.add(path.node);
|
|
112
106
|
processJSXFragment(path, ctx);
|
|
113
107
|
}
|
|
114
108
|
}
|
|
@@ -122,17 +116,22 @@ function generateCode(ast, code, id, sourceMap) {
|
|
|
122
116
|
const output = babelGenerate(ast, { sourceMaps: !!sourceMap, filename: id, sourceFileName: id }, code);
|
|
123
117
|
return { code: output.code, map: output.map };
|
|
124
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* 检查文件是否需要转换
|
|
121
|
+
*/
|
|
122
|
+
export function shouldTransform(id) {
|
|
123
|
+
const ext = id.split('?')[0].split('.').pop()?.toLowerCase();
|
|
124
|
+
return ext === 'jsx' || ext === 'tsx';
|
|
125
|
+
}
|
|
125
126
|
/**
|
|
126
127
|
* 转换 JSX/TSX 代码
|
|
127
128
|
*/
|
|
128
129
|
export async function transform(code, id, options) {
|
|
129
|
-
if (!shouldTransform(id))
|
|
130
|
-
return null;
|
|
131
130
|
const ast = parse(code, createParserOptions());
|
|
132
131
|
const ctx = createContext(code, id, options, ast);
|
|
133
132
|
setupAliases(ctx, ast.program);
|
|
134
133
|
collectRefInfo(ctx, ast.program);
|
|
135
|
-
const components = collectComponentFunctions(ast.program);
|
|
134
|
+
const components = collectComponentFunctions(ast.program, ctx.builderAlias);
|
|
136
135
|
transformAST(ast, ctx);
|
|
137
136
|
injectImports(ast.program, ctx);
|
|
138
137
|
if (options.hmr && components.length > 0) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { RenderUnit, View } from 'vitarx';
|
|
2
|
+
/**
|
|
3
|
+
* 编译选项
|
|
4
|
+
*/
|
|
5
|
+
export interface CompileOptions {
|
|
6
|
+
hmr: boolean;
|
|
7
|
+
dev: boolean;
|
|
8
|
+
ssr: boolean;
|
|
9
|
+
runtimeModule: string;
|
|
10
|
+
sourceMap: boolean | 'inline' | 'both';
|
|
11
|
+
transformClassNameToClass: boolean;
|
|
12
|
+
}
|
|
2
13
|
export interface IfBlockProps {
|
|
3
14
|
/**
|
|
4
15
|
* 子元素列表
|
|
@@ -70,9 +70,10 @@ export declare function createLocationObject(filename: string, loc: SourceLocati
|
|
|
70
70
|
/**
|
|
71
71
|
* 为调用表达式添加 @__PURE__ 注释
|
|
72
72
|
* @param node - 调用表达式节点
|
|
73
|
+
* @param ctx - 转换上下文
|
|
73
74
|
* @returns 添加注释后的节点
|
|
74
75
|
*/
|
|
75
|
-
export declare function addPureComment<T extends CallExpression>(node: T): T;
|
|
76
|
+
export declare function addPureComment<T extends CallExpression>(node: T, ctx: TransformContext): T;
|
|
76
77
|
/**
|
|
77
78
|
* 获取 API 别名
|
|
78
79
|
* @param aliases - 别名映射
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
import * as t from '@babel/types';
|
|
6
6
|
import { isIdentifier, isStringLiteral } from '@babel/types';
|
|
7
7
|
import { PURE_COMMENT } from '../constants/index.js';
|
|
8
|
-
/** 用于追踪已添加 PURE 注释的节点 */
|
|
9
|
-
const pureCommentedNodes = new WeakSet();
|
|
10
8
|
/**
|
|
11
9
|
* 创建 unref 调用
|
|
12
10
|
* @param argument - 参数表达式
|
|
@@ -129,13 +127,14 @@ export function createLocationObject(filename, loc) {
|
|
|
129
127
|
/**
|
|
130
128
|
* 为调用表达式添加 @__PURE__ 注释
|
|
131
129
|
* @param node - 调用表达式节点
|
|
130
|
+
* @param ctx - 转换上下文
|
|
132
131
|
* @returns 添加注释后的节点
|
|
133
132
|
*/
|
|
134
|
-
export function addPureComment(node) {
|
|
135
|
-
if (pureCommentedNodes.has(node)) {
|
|
133
|
+
export function addPureComment(node, ctx) {
|
|
134
|
+
if (ctx.pureCommentedNodes.has(node)) {
|
|
136
135
|
return node;
|
|
137
136
|
}
|
|
138
|
-
pureCommentedNodes.add(node);
|
|
137
|
+
ctx.pureCommentedNodes.add(node);
|
|
139
138
|
t.addComment(node, 'leading', ` ${PURE_COMMENT} `, false);
|
|
140
139
|
return node;
|
|
141
140
|
}
|
|
@@ -22,7 +22,7 @@ export function createBranch(config, ctx) {
|
|
|
22
22
|
}
|
|
23
23
|
const conditionExpr = buildNestedCondition(conditions, ctx, useRef);
|
|
24
24
|
const branchAlias = getAlias(ctx.vitarxAliases, 'branch');
|
|
25
|
-
return addPureComment(createBranchCall(createArrowFunction(conditionExpr), branches, branchAlias, locInfo));
|
|
25
|
+
return addPureComment(createBranchCall(createArrowFunction(conditionExpr), branches, branchAlias, locInfo), ctx);
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* 判断表达式是否需要括号包裹
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 名称收集模块
|
|
3
|
+
* 负责从 AST 中收集导出名称、绑定名称和 builder 包装名称
|
|
4
|
+
* @module utils/collect-names
|
|
5
|
+
*/
|
|
6
|
+
import type * as t from '@babel/types';
|
|
7
|
+
/**
|
|
8
|
+
* 从命名导出中收集名称
|
|
9
|
+
*/
|
|
10
|
+
export declare function collectFromNamedExport(node: t.ExportNamedDeclaration, names: Set<string>): void;
|
|
11
|
+
/**
|
|
12
|
+
* 从默认导出中收集名称
|
|
13
|
+
*/
|
|
14
|
+
export declare function collectFromDefaultExport(node: t.ExportDefaultDeclaration, names: Set<string>): void;
|
|
15
|
+
/**
|
|
16
|
+
* 收集所有导出的标识符名称
|
|
17
|
+
*/
|
|
18
|
+
export declare function collectExportedNames(program: t.Program): Set<string>;
|
|
19
|
+
/**
|
|
20
|
+
* 收集模块中所有绑定的名称(用于避免命名冲突)
|
|
21
|
+
*/
|
|
22
|
+
export declare function collectAllBindingNames(program: t.Program): Set<string>;
|
|
23
|
+
/**
|
|
24
|
+
* 判断调用表达式是否是 builder 调用
|
|
25
|
+
* @param callExpr - 调用表达式
|
|
26
|
+
* @param builderAlias - builder 的本地别名
|
|
27
|
+
* @returns 是否是 builder 调用
|
|
28
|
+
*/
|
|
29
|
+
export declare function isBuilderCall(callExpr: t.CallExpression, builderAlias: string | null): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 收集被 builder 调用的组件名称
|
|
32
|
+
* 检测 builder(Comp) 或 builder(Comp, options) 形式的调用
|
|
33
|
+
* @param program - AST Program 节点
|
|
34
|
+
* @param builderAlias - builder 的本地别名
|
|
35
|
+
* @returns 被 builder 调用的组件名称集合
|
|
36
|
+
*/
|
|
37
|
+
export declare function collectBuilderWrappedNames(program: t.Program, builderAlias: string | null): Set<string>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从命名导出中收集名称
|
|
3
|
+
*/
|
|
4
|
+
export function collectFromNamedExport(node, names) {
|
|
5
|
+
if (node.declaration) {
|
|
6
|
+
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
7
|
+
names.add(node.declaration.id.name);
|
|
8
|
+
}
|
|
9
|
+
else if (node.declaration.type === 'VariableDeclaration') {
|
|
10
|
+
for (const decl of node.declaration.declarations) {
|
|
11
|
+
if (decl.id.type === 'Identifier') {
|
|
12
|
+
names.add(decl.id.name);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (node.specifiers) {
|
|
18
|
+
for (const spec of node.specifiers) {
|
|
19
|
+
if (spec.type === 'ExportSpecifier') {
|
|
20
|
+
names.add(spec.local.name);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 从默认导出中收集名称
|
|
27
|
+
*/
|
|
28
|
+
export function collectFromDefaultExport(node, names) {
|
|
29
|
+
const decl = node.declaration;
|
|
30
|
+
if (decl.type === 'FunctionDeclaration' && decl.id) {
|
|
31
|
+
names.add(decl.id.name);
|
|
32
|
+
}
|
|
33
|
+
else if (decl.type === 'Identifier') {
|
|
34
|
+
names.add(decl.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 从变量声明中收集变量名
|
|
39
|
+
*/
|
|
40
|
+
function collectFromVariableDeclaration(node, names) {
|
|
41
|
+
for (const decl of node.declarations) {
|
|
42
|
+
if (decl.id.type === 'Identifier') {
|
|
43
|
+
names.add(decl.id.name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 从导入声明中收集绑定的名称
|
|
49
|
+
*/
|
|
50
|
+
function collectFromImportDeclaration(node, names) {
|
|
51
|
+
for (const spec of node.specifiers) {
|
|
52
|
+
names.add(spec.local.name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 收集所有导出的标识符名称
|
|
57
|
+
*/
|
|
58
|
+
export function collectExportedNames(program) {
|
|
59
|
+
const names = new Set();
|
|
60
|
+
for (const node of program.body) {
|
|
61
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
62
|
+
collectFromNamedExport(node, names);
|
|
63
|
+
}
|
|
64
|
+
else if (node.type === 'ExportDefaultDeclaration') {
|
|
65
|
+
collectFromDefaultExport(node, names);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return names;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 收集模块中所有绑定的名称(用于避免命名冲突)
|
|
72
|
+
*/
|
|
73
|
+
export function collectAllBindingNames(program) {
|
|
74
|
+
const names = new Set();
|
|
75
|
+
for (const node of program.body) {
|
|
76
|
+
if (node.type === 'FunctionDeclaration' && node.id) {
|
|
77
|
+
names.add(node.id.name);
|
|
78
|
+
}
|
|
79
|
+
else if (node.type === 'VariableDeclaration') {
|
|
80
|
+
collectFromVariableDeclaration(node, names);
|
|
81
|
+
}
|
|
82
|
+
else if (node.type === 'ImportDeclaration') {
|
|
83
|
+
collectFromImportDeclaration(node, names);
|
|
84
|
+
}
|
|
85
|
+
else if (node.type === 'ExportNamedDeclaration') {
|
|
86
|
+
collectFromNamedExport(node, names);
|
|
87
|
+
}
|
|
88
|
+
else if (node.type === 'ExportDefaultDeclaration') {
|
|
89
|
+
collectFromDefaultExport(node, names);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return names;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 判断调用表达式是否是 builder 调用
|
|
96
|
+
* @param callExpr - 调用表达式
|
|
97
|
+
* @param builderAlias - builder 的本地别名
|
|
98
|
+
* @returns 是否是 builder 调用
|
|
99
|
+
*/
|
|
100
|
+
export function isBuilderCall(callExpr, builderAlias) {
|
|
101
|
+
if (!builderAlias)
|
|
102
|
+
return false;
|
|
103
|
+
const callee = callExpr.callee;
|
|
104
|
+
return callee.type === 'Identifier' && callee.name === builderAlias;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 收集被 builder 调用的组件名称
|
|
108
|
+
* 检测 builder(Comp) 或 builder(Comp, options) 形式的调用
|
|
109
|
+
* @param program - AST Program 节点
|
|
110
|
+
* @param builderAlias - builder 的本地别名
|
|
111
|
+
* @returns 被 builder 调用的组件名称集合
|
|
112
|
+
*/
|
|
113
|
+
export function collectBuilderWrappedNames(program, builderAlias) {
|
|
114
|
+
const wrappedNames = new Set();
|
|
115
|
+
if (!builderAlias)
|
|
116
|
+
return wrappedNames;
|
|
117
|
+
for (const node of program.body) {
|
|
118
|
+
if (node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression') {
|
|
119
|
+
const callExpr = node.expression;
|
|
120
|
+
if (isBuilderCall(callExpr, builderAlias)) {
|
|
121
|
+
const arg = callExpr.arguments[0];
|
|
122
|
+
if (arg?.type === 'Identifier') {
|
|
123
|
+
wrappedNames.add(arg.name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return wrappedNames;
|
|
129
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @module
|
|
2
|
+
* 组件收集模块
|
|
3
|
+
* 负责从 AST 中收集组件函数信息
|
|
4
|
+
* @module utils/component-collect
|
|
5
5
|
*/
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
7
|
/**
|
|
@@ -10,8 +10,13 @@ import * as t from '@babel/types';
|
|
|
10
10
|
export interface ComponentInfo {
|
|
11
11
|
name: string;
|
|
12
12
|
node: t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression;
|
|
13
|
+
/** 是否为包装组件(传递给 builder/defineComponent 等函数) */
|
|
14
|
+
isWrapped?: boolean;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* 收集模块中的组件函数
|
|
18
|
+
* @param program - AST Program 节点
|
|
19
|
+
* @param builderAlias - builder 函数的本地别名(用于识别纯构建组件)
|
|
20
|
+
* @returns 组件信息数组
|
|
16
21
|
*/
|
|
17
|
-
export declare function collectComponentFunctions(program: t.Program): ComponentInfo[];
|
|
22
|
+
export declare function collectComponentFunctions(program: t.Program, builderAlias?: string | null): ComponentInfo[];
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @module
|
|
2
|
+
* 组件收集模块
|
|
3
|
+
* 负责从 AST 中收集组件函数信息
|
|
4
|
+
* @module utils/component-collect
|
|
5
5
|
*/
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
7
|
import { DEFAULT_EXPORT_BASE_NAME } from '../constants/index.js';
|
|
8
8
|
import { generateUniqueAlias } from './generate.js';
|
|
9
|
+
import { collectExportedNames, collectAllBindingNames, collectBuilderWrappedNames, isBuilderCall } from './collect-names.js';
|
|
9
10
|
/**
|
|
10
11
|
* 生成唯一的默认导出组件名称
|
|
11
12
|
* 避免与已存在的名称冲突
|
|
@@ -23,58 +24,22 @@ function generateUniqueDefaultName(exportedNames) {
|
|
|
23
24
|
function isValidComponentName(name) {
|
|
24
25
|
return /^[A-Z]/.test(name);
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
* 从命名导出中收集名称
|
|
28
|
-
*/
|
|
29
|
-
function collectFromNamedExport(node, names) {
|
|
30
|
-
if (node.declaration) {
|
|
31
|
-
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
32
|
-
names.add(node.declaration.id.name);
|
|
33
|
-
}
|
|
34
|
-
else if (node.declaration.type === 'VariableDeclaration') {
|
|
35
|
-
for (const decl of node.declaration.declarations) {
|
|
36
|
-
if (decl.id.type === 'Identifier') {
|
|
37
|
-
names.add(decl.id.name);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (node.specifiers) {
|
|
43
|
-
for (const spec of node.specifiers) {
|
|
44
|
-
if (spec.type === 'ExportSpecifier') {
|
|
45
|
-
names.add(spec.local.name);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* 从默认导出中收集名称
|
|
52
|
-
*/
|
|
53
|
-
function collectFromDefaultExport(node, names) {
|
|
54
|
-
const decl = node.declaration;
|
|
55
|
-
if (decl.type === 'FunctionDeclaration' && decl.id) {
|
|
56
|
-
names.add(decl.id.name);
|
|
57
|
-
}
|
|
58
|
-
else if (decl.type === 'Identifier') {
|
|
59
|
-
names.add(decl.name);
|
|
60
|
-
}
|
|
61
|
-
// 匿名函数不在此处预留名称,而是在处理时动态生成唯一名称
|
|
62
|
-
}
|
|
63
|
-
function tryAddComponent(name, node, exportedNames, components) {
|
|
27
|
+
function tryAddComponent(name, node, exportedNames, components, builderWrappedNames = new Set()) {
|
|
64
28
|
if (isValidComponentName(name) && exportedNames.has(name)) {
|
|
65
|
-
|
|
29
|
+
const isWrapped = builderWrappedNames.has(name);
|
|
30
|
+
components.push({ name, node, isWrapped });
|
|
66
31
|
}
|
|
67
32
|
}
|
|
68
33
|
/**
|
|
69
34
|
* 处理变量声明中的组件
|
|
70
35
|
*/
|
|
71
|
-
function processVariableDeclaration(declaration, exportedNames, components) {
|
|
36
|
+
function processVariableDeclaration(declaration, exportedNames, components, builderWrappedNames = new Set()) {
|
|
72
37
|
for (const decl of declaration.declarations) {
|
|
73
38
|
if (decl.id.type !== 'Identifier')
|
|
74
39
|
continue;
|
|
75
40
|
const init = decl.init;
|
|
76
41
|
if (init?.type === 'ArrowFunctionExpression' || init?.type === 'FunctionExpression') {
|
|
77
|
-
tryAddComponent(decl.id.name, init, exportedNames, components);
|
|
42
|
+
tryAddComponent(decl.id.name, init, exportedNames, components, builderWrappedNames);
|
|
78
43
|
}
|
|
79
44
|
}
|
|
80
45
|
}
|
|
@@ -93,14 +58,43 @@ function convertToNamedFunctionDeclaration(func, name) {
|
|
|
93
58
|
funcDecl.loc = func.loc;
|
|
94
59
|
return funcDecl;
|
|
95
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* 从调用表达式中提取函数参数
|
|
63
|
+
* 将 export default builder(()=><div/>) 转换为:
|
|
64
|
+
* const _defaultExport = ()=><div/>
|
|
65
|
+
* export default builder(_defaultExport)
|
|
66
|
+
* @param callExpr - 调用表达式
|
|
67
|
+
* @param exportedNames - 已存在的名称集合
|
|
68
|
+
* @param builderAlias - builder 的本地别名
|
|
69
|
+
* @returns 提取结果,包含名称、函数声明节点和是否是 builder 包装
|
|
70
|
+
*/
|
|
71
|
+
function extractFunctionFromCallExpression(callExpr, exportedNames, builderAlias) {
|
|
72
|
+
const isBuilder = isBuilderCall(callExpr, builderAlias);
|
|
73
|
+
for (let i = 0; i < callExpr.arguments.length; i++) {
|
|
74
|
+
const arg = callExpr.arguments[i];
|
|
75
|
+
if (arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression') {
|
|
76
|
+
const name = generateUniqueDefaultName(exportedNames);
|
|
77
|
+
const funcNode = convertToNamedFunctionDeclaration(arg, name);
|
|
78
|
+
callExpr.arguments[i] = t.identifier(name);
|
|
79
|
+
return { name, funcNode, isBuilderWrapped: isBuilder };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
96
84
|
/**
|
|
97
85
|
* 处理默认导出的匿名函数
|
|
98
86
|
* 为匿名函数生成唯一名称并转换为命名函数声明
|
|
87
|
+
* @param node - 默认导出节点
|
|
88
|
+
* @param program - AST Program 节点,用于插入提取的函数声明
|
|
89
|
+
* @param exportedNames - 已存在的名称集合
|
|
90
|
+
* @param components - 组件信息数组
|
|
91
|
+
* @param builderAlias - builder 的本地别名
|
|
99
92
|
*/
|
|
100
|
-
function processAnonymousDefaultExport(node, exportedNames, components) {
|
|
93
|
+
function processAnonymousDefaultExport(node, program, exportedNames, components, builderAlias) {
|
|
101
94
|
const decl = node.declaration;
|
|
102
95
|
let funcNode = null;
|
|
103
96
|
let name;
|
|
97
|
+
let isWrapped = false;
|
|
104
98
|
if (decl.type === 'FunctionDeclaration' && !decl.id) {
|
|
105
99
|
name = generateUniqueDefaultName(exportedNames);
|
|
106
100
|
decl.id = t.identifier(name);
|
|
@@ -116,59 +110,55 @@ function processAnonymousDefaultExport(node, exportedNames, components) {
|
|
|
116
110
|
funcNode = convertToNamedFunctionDeclaration(decl, name);
|
|
117
111
|
node.declaration = funcNode;
|
|
118
112
|
}
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (node.type === 'ExportNamedDeclaration') {
|
|
130
|
-
collectFromNamedExport(node, names);
|
|
131
|
-
}
|
|
132
|
-
else if (node.type === 'ExportDefaultDeclaration') {
|
|
133
|
-
collectFromDefaultExport(node, names);
|
|
113
|
+
else if (decl.type === 'CallExpression') {
|
|
114
|
+
const result = extractFunctionFromCallExpression(decl, exportedNames, builderAlias);
|
|
115
|
+
if (result) {
|
|
116
|
+
name = result.name;
|
|
117
|
+
funcNode = result.funcNode;
|
|
118
|
+
isWrapped = result.isBuilderWrapped;
|
|
119
|
+
const exportIndex = program.body.indexOf(node);
|
|
120
|
+
if (exportIndex !== -1) {
|
|
121
|
+
program.body.splice(exportIndex, 0, funcNode);
|
|
122
|
+
}
|
|
134
123
|
}
|
|
135
124
|
}
|
|
136
|
-
|
|
125
|
+
if (funcNode) {
|
|
126
|
+
components.push({ name: name, node: funcNode, isWrapped });
|
|
127
|
+
}
|
|
137
128
|
}
|
|
138
129
|
/**
|
|
139
130
|
* 收集模块中的组件函数
|
|
131
|
+
* @param program - AST Program 节点
|
|
132
|
+
* @param builderAlias - builder 函数的本地别名(用于识别纯构建组件)
|
|
133
|
+
* @returns 组件信息数组
|
|
140
134
|
*/
|
|
141
|
-
export function collectComponentFunctions(program) {
|
|
135
|
+
export function collectComponentFunctions(program, builderAlias = null) {
|
|
142
136
|
const exportedNames = collectExportedNames(program);
|
|
137
|
+
const allBindingNames = collectAllBindingNames(program);
|
|
138
|
+
const builderWrappedNames = collectBuilderWrappedNames(program, builderAlias);
|
|
143
139
|
const components = [];
|
|
144
140
|
for (const node of program.body) {
|
|
145
|
-
// 普通函数声明
|
|
146
141
|
if (node.type === 'FunctionDeclaration' && node.id) {
|
|
147
|
-
tryAddComponent(node.id.name, node, exportedNames, components);
|
|
142
|
+
tryAddComponent(node.id.name, node, exportedNames, components, builderWrappedNames);
|
|
148
143
|
}
|
|
149
|
-
// 普通变量声明
|
|
150
144
|
else if (node.type === 'VariableDeclaration') {
|
|
151
|
-
processVariableDeclaration(node, exportedNames, components);
|
|
145
|
+
processVariableDeclaration(node, exportedNames, components, builderWrappedNames);
|
|
152
146
|
}
|
|
153
|
-
// 命名导出
|
|
154
147
|
else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
155
148
|
if (node.declaration.type === 'FunctionDeclaration' && node.declaration.id) {
|
|
156
|
-
tryAddComponent(node.declaration.id.name, node.declaration, exportedNames, components);
|
|
149
|
+
tryAddComponent(node.declaration.id.name, node.declaration, exportedNames, components, builderWrappedNames);
|
|
157
150
|
}
|
|
158
151
|
else if (node.declaration.type === 'VariableDeclaration') {
|
|
159
|
-
processVariableDeclaration(node.declaration, exportedNames, components);
|
|
152
|
+
processVariableDeclaration(node.declaration, exportedNames, components, builderWrappedNames);
|
|
160
153
|
}
|
|
161
154
|
}
|
|
162
|
-
// 默认导出
|
|
163
155
|
else if (node.type === 'ExportDefaultDeclaration') {
|
|
164
156
|
const decl = node.declaration;
|
|
165
|
-
// 命名函数声明
|
|
166
157
|
if (decl.type === 'FunctionDeclaration' && decl.id) {
|
|
167
|
-
tryAddComponent(decl.id.name, decl, exportedNames, components);
|
|
158
|
+
tryAddComponent(decl.id.name, decl, exportedNames, components, builderWrappedNames);
|
|
168
159
|
}
|
|
169
|
-
// 匿名函数(函数声明、函数表达式、箭头函数)
|
|
170
160
|
else {
|
|
171
|
-
processAnonymousDefaultExport(node,
|
|
161
|
+
processAnonymousDefaultExport(node, program, allBindingNames, components, builderAlias);
|
|
172
162
|
}
|
|
173
163
|
}
|
|
174
164
|
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { isWhitespaceJSXText } from './ast-guards.js';
|
|
2
|
-
export { getJSXElementName, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren,
|
|
2
|
+
export { getJSXElementName, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren, validateNoDirectMatchChild } from './jsx-helpers.js';
|
|
3
3
|
export { createUnrefCall, createAccessCall, createDynamicCall, createBranchCall, createCreateViewCall, createWithDirectivesCall, createArrowFunction, createLocationObject, addPureComment, getAlias, getDevLocInfo } from './ast-builders.js';
|
|
4
4
|
export { collectPatternBindings, collectObjectPatternBindings } from './pattern-helpers.js';
|
|
5
5
|
export { createBranch, createBinaryBranch, buildNestedCondition, type BranchConfig } from './branch-factory.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 类型守卫
|
|
2
2
|
export { isWhitespaceJSXText } from './ast-guards.js';
|
|
3
3
|
// JSX 辅助函数
|
|
4
|
-
export { getJSXElementName, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren,
|
|
4
|
+
export { getJSXElementName, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren, validateNoDirectMatchChild } from './jsx-helpers.js';
|
|
5
5
|
// AST 构建函数
|
|
6
6
|
export { createUnrefCall, createAccessCall, createDynamicCall, createBranchCall, createCreateViewCall, createWithDirectivesCall, createArrowFunction, createLocationObject, addPureComment, getAlias, getDevLocInfo } from './ast-builders.js';
|
|
7
7
|
// 模式处理辅助函数
|
|
@@ -77,7 +77,8 @@ export declare function hasEffectiveChildren(node: JSXElement): boolean;
|
|
|
77
77
|
*/
|
|
78
78
|
export declare function filterEffectiveChildren(node: JSXElement): t.Node[];
|
|
79
79
|
/**
|
|
80
|
-
*
|
|
80
|
+
* 校验子节点中不能直接包含 Match 组件
|
|
81
|
+
* Match 必须在 Switch 内使用,非 Switch 元素的子节点中不允许出现 Match
|
|
81
82
|
* @param children - 子节点数组
|
|
82
83
|
*/
|
|
83
|
-
export declare function
|
|
84
|
+
export declare function validateNoDirectMatchChild(children: t.Node[]): void;
|
|
@@ -205,10 +205,11 @@ export function filterEffectiveChildren(node) {
|
|
|
205
205
|
});
|
|
206
206
|
}
|
|
207
207
|
/**
|
|
208
|
-
*
|
|
208
|
+
* 校验子节点中不能直接包含 Match 组件
|
|
209
|
+
* Match 必须在 Switch 内使用,非 Switch 元素的子节点中不允许出现 Match
|
|
209
210
|
* @param children - 子节点数组
|
|
210
211
|
*/
|
|
211
|
-
export function
|
|
212
|
+
export function validateNoDirectMatchChild(children) {
|
|
212
213
|
for (const child of children) {
|
|
213
214
|
if (isJSXElement(child)) {
|
|
214
215
|
const childName = getJSXElementName(child);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitarx/plugin-vite",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.8",
|
|
4
4
|
"description": "The official plugin for Vitarx support in Vite.",
|
|
5
5
|
"author": "ZhuChongLin <8210856@qq.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,12 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"default": "./dist/index.js"
|
|
28
28
|
}
|
|
29
|
+
},
|
|
30
|
+
"./transform": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/transform.d.ts",
|
|
33
|
+
"default": "./dist/transform.js"
|
|
34
|
+
}
|
|
29
35
|
}
|
|
30
36
|
},
|
|
31
37
|
"peerDependencies": {
|
|
@@ -53,12 +59,13 @@
|
|
|
53
59
|
"prettier": "^3.8.1",
|
|
54
60
|
"rimraf": "^6.1.3",
|
|
55
61
|
"vitest": "^4.0.18",
|
|
56
|
-
"vite": "^8.0.
|
|
62
|
+
"vite": "^8.0.5",
|
|
57
63
|
"vitarx": "^4.0.0-beta.8"
|
|
58
64
|
},
|
|
59
65
|
"scripts": {
|
|
60
|
-
"build": "rimraf dist && tsc",
|
|
66
|
+
"build": "vitest run && rimraf dist && tsc -p tsconfig.build.json",
|
|
61
67
|
"test": "vitest run",
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
62
69
|
"release": "release-cli"
|
|
63
70
|
}
|
|
64
71
|
}
|