@vitarx/plugin-vite 0.0.1-alpha.0
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/LICENSE +27 -0
- package/README.md +298 -0
- package/dist/components.js +1 -0
- package/dist/constants/index.js +59 -0
- package/dist/context.js +78 -0
- package/dist/error.js +156 -0
- package/dist/hmr-client/index.js +143 -0
- package/dist/hmr-client/update.js +53 -0
- package/dist/hmr-client/utils.js +125 -0
- package/dist/index.js +65 -0
- package/dist/passes/components/ifBlock.js +42 -0
- package/dist/passes/components/index.js +24 -0
- package/dist/passes/components/switch.js +102 -0
- package/dist/passes/directives/index.js +6 -0
- package/dist/passes/directives/processDirectives.js +46 -0
- package/dist/passes/directives/vIf.js +64 -0
- package/dist/passes/hmr/index.js +5 -0
- package/dist/passes/hmr/inject.js +189 -0
- package/dist/passes/imports/collectImports.js +56 -0
- package/dist/passes/imports/collectRefVariables.js +95 -0
- package/dist/passes/imports/index.js +7 -0
- package/dist/passes/imports/injectImports.js +96 -0
- package/dist/passes/index.js +16 -0
- package/dist/passes/jsx/index.js +7 -0
- package/dist/passes/jsx/processChildren.js +114 -0
- package/dist/passes/jsx/processJSXElement.js +173 -0
- package/dist/passes/jsx/processJSXFragment.js +37 -0
- package/dist/passes/props/attribute.js +165 -0
- package/dist/passes/props/index.js +94 -0
- package/dist/passes/props/types.js +1 -0
- package/dist/passes/props/vmodel.js +115 -0
- package/dist/transform.js +144 -0
- package/dist/utils/ast-builders.js +142 -0
- package/dist/utils/ast-guards.js +16 -0
- package/dist/utils/branch-factory.js +107 -0
- package/dist/utils/component-collect.js +233 -0
- package/dist/utils/generate.js +11 -0
- package/dist/utils/index.js +16 -0
- package/dist/utils/jsx-helpers.js +168 -0
- package/dist/utils/pattern-helpers.js +47 -0
- package/dist/utils/vif-helpers.js +127 -0
- package/package.json +63 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _HMRManager_idMapToView, _HMRManager_idMapToComponent;
|
|
7
|
+
import { isReactive, isRef } from 'vitarx';
|
|
8
|
+
import { HMR } from '../constants/index.js';
|
|
9
|
+
import { processUpdate } from './update.js';
|
|
10
|
+
class HMRManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
/**
|
|
13
|
+
* id模块映射到组件虚拟节点集合
|
|
14
|
+
*
|
|
15
|
+
* 模块id -> 组件虚拟节点
|
|
16
|
+
*/
|
|
17
|
+
_HMRManager_idMapToView.set(this, new Map()
|
|
18
|
+
/**
|
|
19
|
+
* id映射到组件构造函数
|
|
20
|
+
*/
|
|
21
|
+
);
|
|
22
|
+
/**
|
|
23
|
+
* id映射到组件构造函数
|
|
24
|
+
*/
|
|
25
|
+
_HMRManager_idMapToComponent.set(this, new Map()
|
|
26
|
+
/**
|
|
27
|
+
* 获取单实例
|
|
28
|
+
*/
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 获取单实例
|
|
33
|
+
*/
|
|
34
|
+
static get instance() {
|
|
35
|
+
if (!window[HMR.manager]) {
|
|
36
|
+
window[HMR.manager] = new HMRManager();
|
|
37
|
+
}
|
|
38
|
+
return window[HMR.manager];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 给组件绑定唯一id
|
|
42
|
+
*
|
|
43
|
+
* @param component
|
|
44
|
+
* @param id
|
|
45
|
+
*/
|
|
46
|
+
bindId(component, id) {
|
|
47
|
+
if (typeof component === 'function') {
|
|
48
|
+
Object.defineProperty(component, HMR.id, { value: id });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 置换新模块
|
|
53
|
+
*
|
|
54
|
+
* 此方法提供给`jsxDev`函数调用,保持每次创建组件实例都是最新的模块!
|
|
55
|
+
* @param component
|
|
56
|
+
*/
|
|
57
|
+
resolveComponent(component) {
|
|
58
|
+
const id = this.getId(component);
|
|
59
|
+
return id ? (__classPrivateFieldGet(this, _HMRManager_idMapToComponent, "f").get(id) ?? component) : component;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 恢复状态
|
|
63
|
+
*
|
|
64
|
+
* @param {ComponentView} view - 组件视图
|
|
65
|
+
* @param {string} name - 变量名称
|
|
66
|
+
* @example
|
|
67
|
+
* const state = __$VITARX_HMR$__.instance.memo(__$VITARX_HMR_VIEW_NODE$__, 'state') ?? ref(0)
|
|
68
|
+
*/
|
|
69
|
+
memo(view, name) {
|
|
70
|
+
if (!view)
|
|
71
|
+
return undefined;
|
|
72
|
+
const state = view[HMR.state]?.[name];
|
|
73
|
+
// 如果是副作用,则丢弃。
|
|
74
|
+
if (!isRef(state) && !isReactive(state))
|
|
75
|
+
return undefined;
|
|
76
|
+
return state;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 注册节点
|
|
80
|
+
*
|
|
81
|
+
* @param view - 组件视图节点
|
|
82
|
+
*/
|
|
83
|
+
register(view) {
|
|
84
|
+
if (!view)
|
|
85
|
+
return;
|
|
86
|
+
const component = view.component;
|
|
87
|
+
const id = this.getId(component);
|
|
88
|
+
if (__classPrivateFieldGet(this, _HMRManager_idMapToView, "f").has(id)) {
|
|
89
|
+
__classPrivateFieldGet(this, _HMRManager_idMapToView, "f").get(id).add(view);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
__classPrivateFieldGet(this, _HMRManager_idMapToView, "f").set(id, new Set([view]));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 获取模块id
|
|
97
|
+
*
|
|
98
|
+
* @param component
|
|
99
|
+
*/
|
|
100
|
+
getId(component) {
|
|
101
|
+
return Reflect.get(component, HMR.id);
|
|
102
|
+
}
|
|
103
|
+
update(mod) {
|
|
104
|
+
if (!mod)
|
|
105
|
+
return;
|
|
106
|
+
try {
|
|
107
|
+
for (const modKey in mod) {
|
|
108
|
+
// 新模块
|
|
109
|
+
const newComponent = mod[modKey];
|
|
110
|
+
// 模块id
|
|
111
|
+
const moduleId = this.getId(newComponent);
|
|
112
|
+
// 模块ID不存在,跳过
|
|
113
|
+
if (!moduleId)
|
|
114
|
+
continue;
|
|
115
|
+
// 模块活跃的虚拟节点集合
|
|
116
|
+
const nodes = __classPrivateFieldGet(this, _HMRManager_idMapToView, "f").get(moduleId);
|
|
117
|
+
// 模块不存在,跳过
|
|
118
|
+
if (!nodes)
|
|
119
|
+
continue;
|
|
120
|
+
for (const node of nodes) {
|
|
121
|
+
// 更新模块
|
|
122
|
+
const id = this.getId(newComponent);
|
|
123
|
+
if (id)
|
|
124
|
+
__classPrivateFieldGet(this, _HMRManager_idMapToComponent, "f").set(id, newComponent);
|
|
125
|
+
// 更新节点视图
|
|
126
|
+
if (node.active && node.isMounted) {
|
|
127
|
+
processUpdate(node, newComponent);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
if (import.meta.hot) {
|
|
134
|
+
import.meta.hot.invalidate(`[VitarxHMR]: ${e}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
_HMRManager_idMapToView = new WeakMap(), _HMRManager_idMapToComponent = new WeakMap();
|
|
143
|
+
export default HMRManager;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createCommentView, EffectScope, getRenderer, runComponent } from 'vitarx';
|
|
2
|
+
import { diffComponentChange } from './utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* 处理组件更新函数
|
|
5
|
+
* @param view - 组件视图对象,包含当前组件的实例和节点信息
|
|
6
|
+
* @param newComponent - 新的组件定义,用于替换现有组件
|
|
7
|
+
*/
|
|
8
|
+
export function processUpdate(view, newComponent) {
|
|
9
|
+
// 获取渲染器实例
|
|
10
|
+
const renderer = getRenderer();
|
|
11
|
+
// 创建一个占位符注释节点
|
|
12
|
+
const placeholder = renderer.createComment('');
|
|
13
|
+
// 获取旧组件的DOM元素
|
|
14
|
+
const oldElement = view.node;
|
|
15
|
+
// 将占位符插入到旧元素的位置
|
|
16
|
+
renderer.insert(placeholder, oldElement);
|
|
17
|
+
// 获取组件实例
|
|
18
|
+
const instance = view.instance;
|
|
19
|
+
// 比较新旧组件的差异,检查逻辑是否有变化
|
|
20
|
+
const { logic } = diffComponentChange(view.component.toString(), newComponent.toString());
|
|
21
|
+
view.component = newComponent;
|
|
22
|
+
// 如果逻辑有变化,则完全重新挂载组件
|
|
23
|
+
if (logic) {
|
|
24
|
+
view.dispose();
|
|
25
|
+
view.mount(placeholder, 'replace');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// 销毁旧作用域
|
|
29
|
+
instance.scope.dispose();
|
|
30
|
+
instance.scope = new EffectScope({
|
|
31
|
+
name: view.name,
|
|
32
|
+
errorHandler: (error, source) => {
|
|
33
|
+
instance.reportError(error, `effect:${source}`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// 销毁旧的子树
|
|
37
|
+
instance.subView.dispose();
|
|
38
|
+
// 创建新的子树
|
|
39
|
+
let subView;
|
|
40
|
+
try {
|
|
41
|
+
subView = runComponent(instance, () => newComponent(view.props));
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
instance.reportError(e, 'component:run');
|
|
45
|
+
subView = createCommentView(`Component<${view.name}>:failed`);
|
|
46
|
+
}
|
|
47
|
+
;
|
|
48
|
+
instance.subView = instance['normalizeView'](subView);
|
|
49
|
+
// 初始化新的子树
|
|
50
|
+
instance.subView.init(instance.subViewContext);
|
|
51
|
+
// 挂载新的子树
|
|
52
|
+
instance.subView.mount(placeholder, 'replace');
|
|
53
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMR 代码变更检测工具
|
|
3
|
+
* 用于判断组件代码中 UI 描述部分和非 UI 部分的变更
|
|
4
|
+
* @module hmr-client/utils
|
|
5
|
+
*/
|
|
6
|
+
import { parse } from 'acorn';
|
|
7
|
+
import { simple as walkSimple } from 'acorn-walk';
|
|
8
|
+
/**
|
|
9
|
+
* UI 相关的运行时 API 名称
|
|
10
|
+
* 这些 API 调用代表 UI 描述代码
|
|
11
|
+
*/
|
|
12
|
+
const UI_APIS = new Set(['createView', 'branch', 'dynamic', 'access', 'withDirectives', 'jsxDEV']);
|
|
13
|
+
/**
|
|
14
|
+
* 判断标识符名称是否为 UI API
|
|
15
|
+
* 支持原始名称和带后缀的名称(如 jsxDEV$1)
|
|
16
|
+
*/
|
|
17
|
+
function isUIApi(name) {
|
|
18
|
+
if (UI_APIS.has(name))
|
|
19
|
+
return true;
|
|
20
|
+
// 支持带数字后缀的 createView
|
|
21
|
+
return /^jsxDEV\$\d+$/.test(name) || /^createView\$\d+$/.test(name);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 从函数代码中分离 UI 代码和非 UI 代码
|
|
25
|
+
* @param functionCode 完整函数代码
|
|
26
|
+
* @returns 包含逻辑代码和渲染代码的对象
|
|
27
|
+
*/
|
|
28
|
+
function separateLogicAndRender(functionCode) {
|
|
29
|
+
const uiNodes = [];
|
|
30
|
+
// 包装代码以便解析
|
|
31
|
+
const wrappedCode = `(${functionCode})`;
|
|
32
|
+
try {
|
|
33
|
+
const ast = parse(wrappedCode, {
|
|
34
|
+
ecmaVersion: 'latest',
|
|
35
|
+
sourceType: 'module',
|
|
36
|
+
allowReturnOutsideFunction: true,
|
|
37
|
+
allowAwaitOutsideFunction: true,
|
|
38
|
+
allowImportExportEverywhere: true,
|
|
39
|
+
allowHashBang: true,
|
|
40
|
+
allowReserved: true
|
|
41
|
+
});
|
|
42
|
+
// 遍历 AST 收集所有 UI API 调用节点
|
|
43
|
+
walkSimple(ast, {
|
|
44
|
+
CallExpression(node) {
|
|
45
|
+
const callee = node.callee;
|
|
46
|
+
// 直接调用:createView(...)
|
|
47
|
+
if (callee.type === 'Identifier' && isUIApi(callee.name)) {
|
|
48
|
+
uiNodes.push({ start: node.start, end: node.end });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// 成员调用:exports.createView(...)
|
|
52
|
+
if (callee.type === 'MemberExpression' &&
|
|
53
|
+
callee.property.type === 'Identifier' &&
|
|
54
|
+
isUIApi(callee.property.name)) {
|
|
55
|
+
uiNodes.push({ start: node.start, end: node.end });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// 解析失败时,返回原始代码作为逻辑代码
|
|
62
|
+
return {
|
|
63
|
+
logicCode: functionCode.trim(),
|
|
64
|
+
renderCode: ''
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// 按位置排序,从后向前替换以保持索引正确
|
|
68
|
+
uiNodes.sort((a, b) => b.start - a.start);
|
|
69
|
+
// 提取 UI 代码
|
|
70
|
+
const renderCode = uiNodes
|
|
71
|
+
.map(n => wrappedCode.slice(n.start, n.end))
|
|
72
|
+
.reverse()
|
|
73
|
+
.join('\n');
|
|
74
|
+
// 移除 UI 代码后的逻辑代码
|
|
75
|
+
let logicCode = wrappedCode;
|
|
76
|
+
for (const node of uiNodes) {
|
|
77
|
+
logicCode = logicCode.slice(0, node.start) + logicCode.slice(node.end);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
logicCode: logicCode.trim(),
|
|
81
|
+
renderCode
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 规范化代码字符串
|
|
86
|
+
* 移除多余空白、注释等,用于比较
|
|
87
|
+
*/
|
|
88
|
+
function normalizeCode(code) {
|
|
89
|
+
// 移除多行注释
|
|
90
|
+
let result = code.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
91
|
+
// 移除单行注释
|
|
92
|
+
result = result.replace(/\/\/.*$/gm, '');
|
|
93
|
+
// 移除多余空白
|
|
94
|
+
result = result.replace(/\s+/g, ' ');
|
|
95
|
+
// 移除首尾空白
|
|
96
|
+
return result.trim();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 判断两个函数组件的代码差异
|
|
100
|
+
*
|
|
101
|
+
* 分析策略:
|
|
102
|
+
* 1. 解析函数代码,提取所有 createView/branch/dynamic 等 UI API 调用
|
|
103
|
+
* 2. 将这些调用识别为 UI 描述代码
|
|
104
|
+
* 3. 其余代码识别为非 UI 代码(逻辑代码)
|
|
105
|
+
* 4. 分别比较 UI 代码和非 UI 代码是否变化
|
|
106
|
+
*
|
|
107
|
+
* @param newCode 新函数代码
|
|
108
|
+
* @param oldCode 旧函数代码
|
|
109
|
+
* @returns {ChangeCode} 变更检测结果
|
|
110
|
+
*/
|
|
111
|
+
export function diffComponentChange(newCode, oldCode) {
|
|
112
|
+
const { renderCode: newRenderCode, logicCode: newLogicCode } = separateLogicAndRender(newCode);
|
|
113
|
+
const { renderCode: oldRenderCode, logicCode: oldLogicCode } = separateLogicAndRender(oldCode);
|
|
114
|
+
// 规范化后比较,避免格式差异导致误判
|
|
115
|
+
const normalizedNewLogic = normalizeCode(newLogicCode);
|
|
116
|
+
const normalizedOldLogic = normalizeCode(oldLogicCode);
|
|
117
|
+
const normalizedNewRender = normalizeCode(newRenderCode);
|
|
118
|
+
const normalizedOldRender = normalizeCode(oldRenderCode);
|
|
119
|
+
return {
|
|
120
|
+
// UI 代码变化:需要重新构建视图
|
|
121
|
+
build: normalizedNewRender !== normalizedOldRender,
|
|
122
|
+
// 非 UI 代码变化:需要完全重新挂载组件
|
|
123
|
+
logic: normalizedNewLogic !== normalizedOldLogic
|
|
124
|
+
};
|
|
125
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { transformWithOxc } from 'vite';
|
|
3
|
+
import { transform } from './transform.js';
|
|
4
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
5
|
+
/**
|
|
6
|
+
* vite-plugin-vitarx
|
|
7
|
+
*
|
|
8
|
+
* 功能:
|
|
9
|
+
* - jsx -> createView 编译转换
|
|
10
|
+
* - 支持 v-if、v-else-if、v-else 、v-model 等编译宏指令
|
|
11
|
+
* - 支持 Switch , IfBlock 等编译宏组件
|
|
12
|
+
* - 开发时 HMR 热更新相关代码注入与功能支持
|
|
13
|
+
*
|
|
14
|
+
* @param _options - 暂无可选配置。
|
|
15
|
+
* @returns - vite插件对象。
|
|
16
|
+
*/
|
|
17
|
+
export default function vitarx(_options) {
|
|
18
|
+
let compileOptions;
|
|
19
|
+
let isDEV = false;
|
|
20
|
+
let isSSR = false;
|
|
21
|
+
let viteConfig;
|
|
22
|
+
return {
|
|
23
|
+
name: 'vite-plugin-vitarx',
|
|
24
|
+
config(config, env) {
|
|
25
|
+
isDEV = env.command === 'serve' && !env.isPreview;
|
|
26
|
+
const configSSR = !!config.build?.ssr;
|
|
27
|
+
isSSR = env.isSsrBuild === true || configSSR;
|
|
28
|
+
return {
|
|
29
|
+
oxc: {
|
|
30
|
+
jsx: 'preserve',
|
|
31
|
+
exclude: /\.[jt]sx$/,
|
|
32
|
+
},
|
|
33
|
+
define: {
|
|
34
|
+
__VITARX_DEV__: JSON.stringify(isDEV),
|
|
35
|
+
__VITARX_SSR__: JSON.stringify(isSSR)
|
|
36
|
+
},
|
|
37
|
+
resolve: {
|
|
38
|
+
alias: {
|
|
39
|
+
'@vitarx/vite-plugin/hmr-client': path.join(__dirname, 'hmr-client/index.js')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
configResolved(config) {
|
|
45
|
+
viteConfig = config;
|
|
46
|
+
const sourcemap = config.build.sourcemap;
|
|
47
|
+
compileOptions = {
|
|
48
|
+
dev: isDEV,
|
|
49
|
+
ssr: isSSR,
|
|
50
|
+
hmr: isDEV && !isSSR,
|
|
51
|
+
runtimeModule: 'vitarx',
|
|
52
|
+
sourceMap: sourcemap === 'inline' ? 'inline' : sourcemap === true ? 'both' : false
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
async transform(code, id) {
|
|
56
|
+
const result = await transform(code, id, compileOptions);
|
|
57
|
+
if (result) {
|
|
58
|
+
return await transformWithOxc(result.code, id, {
|
|
59
|
+
jsx: 'preserve'
|
|
60
|
+
}, result.map, viteConfig);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isJSXElement } from '@babel/types';
|
|
2
|
+
import { createError } from '../../error.js';
|
|
3
|
+
import { collectVIfChainInfo, createArrowFunction, createBranch, filterWhitespaceChildren, hasDirective, removeVDirectives, validateVIfChain } from '../../utils/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* 处理 IfBlock 组件
|
|
6
|
+
*/
|
|
7
|
+
export function processIfBlock(path, ctx) {
|
|
8
|
+
const children = filterWhitespaceChildren(path.node.children);
|
|
9
|
+
if (children.length === 0) {
|
|
10
|
+
throw createError('E014', path.node);
|
|
11
|
+
}
|
|
12
|
+
validateChildrenType(children);
|
|
13
|
+
const jsxChildren = children;
|
|
14
|
+
validateVIfChain(jsxChildren);
|
|
15
|
+
const { nodes, conditions } = collectVIfChainInfo(jsxChildren);
|
|
16
|
+
nodes.forEach(node => removeVDirectives(node));
|
|
17
|
+
const branches = nodes.map(node => createArrowFunction(node));
|
|
18
|
+
const branchCall = createBranch({ conditions, branches }, ctx);
|
|
19
|
+
if (path.node.loc) {
|
|
20
|
+
branchCall.loc = path.node.loc;
|
|
21
|
+
}
|
|
22
|
+
path.replaceWith(branchCall);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 验证子元素类型
|
|
26
|
+
*/
|
|
27
|
+
function validateChildrenType(children) {
|
|
28
|
+
for (const child of children) {
|
|
29
|
+
if (!isJSXElement(child)) {
|
|
30
|
+
throw createError('E008', child, 'IfBlock children must be JSX elements with v-if directives');
|
|
31
|
+
}
|
|
32
|
+
if (!hasVIfChainDirective(child)) {
|
|
33
|
+
throw createError('E008', child, 'IfBlock children must have v-if/v-else-if/v-else directives');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 检查元素是否有 v-if 链指令
|
|
39
|
+
*/
|
|
40
|
+
function hasVIfChainDirective(node) {
|
|
41
|
+
return (hasDirective(node, 'v-if') || hasDirective(node, 'v-else-if') || hasDirective(node, 'v-else'));
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getJSXElementName, isPureCompileComponent } from '../../utils/index.js';
|
|
2
|
+
import { processIfBlock } from './ifBlock.js';
|
|
3
|
+
import { processSwitch } from './switch.js';
|
|
4
|
+
/**
|
|
5
|
+
* 处理纯编译组件
|
|
6
|
+
* 根据组件名称分发到对应的处理器
|
|
7
|
+
* @param path - JSX 元素路径
|
|
8
|
+
* @param ctx - 转换上下文
|
|
9
|
+
*/
|
|
10
|
+
export function processPureCompileComponent(path, ctx) {
|
|
11
|
+
const name = getJSXElementName(path.node);
|
|
12
|
+
if (!name || !isPureCompileComponent(name))
|
|
13
|
+
return;
|
|
14
|
+
switch (name) {
|
|
15
|
+
case 'Switch':
|
|
16
|
+
processSwitch(path, ctx);
|
|
17
|
+
break;
|
|
18
|
+
case 'IfBlock':
|
|
19
|
+
processIfBlock(path, ctx);
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export { processSwitch } from './switch.js';
|
|
24
|
+
export { processIfBlock } from './ifBlock.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import { isJSXElement, isJSXText } from '@babel/types';
|
|
3
|
+
import { createError } from '../../error.js';
|
|
4
|
+
import { createArrowFunction, createBranch, getJSXAttributeByName, getJSXElementName } from '../../utils/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* 处理 Switch 组件
|
|
7
|
+
* @param path - JSX 元素路径
|
|
8
|
+
* @param ctx - 转换上下文
|
|
9
|
+
*/
|
|
10
|
+
export function processSwitch(path, ctx) {
|
|
11
|
+
const children = path.node.children;
|
|
12
|
+
// 获取 fallback 属性
|
|
13
|
+
const fallbackValue = extractFallbackValue(path.node);
|
|
14
|
+
// 收集 Match 子节点
|
|
15
|
+
const matchNodes = collectMatchNodes(children);
|
|
16
|
+
// 构建条件和分支
|
|
17
|
+
const { conditions, branches } = buildConditionsAndBranches(matchNodes);
|
|
18
|
+
// 添加 fallback 分支
|
|
19
|
+
if (fallbackValue) {
|
|
20
|
+
conditions.push(t.booleanLiteral(true));
|
|
21
|
+
branches.push(createArrowFunction(fallbackValue));
|
|
22
|
+
}
|
|
23
|
+
// 生成 branch 调用
|
|
24
|
+
// Switch 的 when 条件如果是标识符,需要 unref
|
|
25
|
+
const branchCall = createBranch({ conditions, branches, useRef: true }, ctx);
|
|
26
|
+
if (path.node.loc) {
|
|
27
|
+
branchCall.loc = path.node.loc;
|
|
28
|
+
}
|
|
29
|
+
path.replaceWith(branchCall);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 提取 fallback 属性值
|
|
33
|
+
*/
|
|
34
|
+
function extractFallbackValue(node) {
|
|
35
|
+
const fallbackAttr = getJSXAttributeByName(node, 'fallback');
|
|
36
|
+
if (!fallbackAttr?.value)
|
|
37
|
+
return null;
|
|
38
|
+
if (fallbackAttr.value.type === 'JSXExpressionContainer') {
|
|
39
|
+
return fallbackAttr.value.expression;
|
|
40
|
+
}
|
|
41
|
+
return fallbackAttr.value;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 收集 Match 子节点
|
|
45
|
+
*/
|
|
46
|
+
function collectMatchNodes(children) {
|
|
47
|
+
const matchNodes = [];
|
|
48
|
+
for (const child of children) {
|
|
49
|
+
if (isJSXText(child))
|
|
50
|
+
continue;
|
|
51
|
+
if (!isJSXElement(child)) {
|
|
52
|
+
throw createError('E006', child, 'Switch children must be Match components');
|
|
53
|
+
}
|
|
54
|
+
const childName = getJSXElementName(child);
|
|
55
|
+
if (childName === 'Match') {
|
|
56
|
+
matchNodes.push(child);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
throw createError('E006', child, `Invalid child "${childName}" in Switch`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (matchNodes.length === 0) {
|
|
63
|
+
throw createError('E015', undefined);
|
|
64
|
+
}
|
|
65
|
+
return matchNodes;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 构建条件数组和分支数组
|
|
69
|
+
*/
|
|
70
|
+
function buildConditionsAndBranches(matchNodes) {
|
|
71
|
+
const conditions = [];
|
|
72
|
+
const branches = [];
|
|
73
|
+
for (const matchNode of matchNodes) {
|
|
74
|
+
const whenAttr = getJSXAttributeByName(matchNode, 'when');
|
|
75
|
+
if (!whenAttr?.value) {
|
|
76
|
+
throw createError('E007', matchNode);
|
|
77
|
+
}
|
|
78
|
+
// 提取 when 条件
|
|
79
|
+
const whenExpr = whenAttr.value.type === 'JSXExpressionContainer'
|
|
80
|
+
? whenAttr.value.expression
|
|
81
|
+
: whenAttr.value;
|
|
82
|
+
conditions.push(whenExpr);
|
|
83
|
+
// 提取分支内容
|
|
84
|
+
const matchChildren = matchNode.children.filter(c => !isJSXText(c) || c.value.trim());
|
|
85
|
+
if (matchChildren.length === 0) {
|
|
86
|
+
throw createError('E013', matchNode);
|
|
87
|
+
}
|
|
88
|
+
if (matchChildren.length === 1) {
|
|
89
|
+
const child = matchChildren[0];
|
|
90
|
+
if (isJSXText(child)) {
|
|
91
|
+
branches.push(createArrowFunction(t.stringLiteral(child.value.trim())));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
branches.push(createArrowFunction(child));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
branches.push(createArrowFunction(t.arrayExpression(matchChildren)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { conditions, branches };
|
|
102
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 指令处理模块
|
|
3
|
+
* 处理 v-show 等指令
|
|
4
|
+
* @module passes/directives/processDirectives
|
|
5
|
+
*/
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
import { isIdentifier } from '@babel/types';
|
|
8
|
+
import { markImport } from '../../context.js';
|
|
9
|
+
import { addPureComment, createWithDirectivesCall, getAlias } from '../../utils/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* 处理指令
|
|
12
|
+
* 将指令转换为 withDirectives 调用
|
|
13
|
+
* @param viewCall - 视图调用表达式
|
|
14
|
+
* @param directives - 指令映射
|
|
15
|
+
* @param ctx - 转换上下文
|
|
16
|
+
* @returns 处理后的调用表达式
|
|
17
|
+
*/
|
|
18
|
+
export function processDirectives(viewCall, directives, ctx) {
|
|
19
|
+
if (directives.size === 0) {
|
|
20
|
+
return viewCall;
|
|
21
|
+
}
|
|
22
|
+
const directiveArray = [];
|
|
23
|
+
for (const [name, value] of directives) {
|
|
24
|
+
const directiveName = name.slice(2); // 移除 'v-' 前缀
|
|
25
|
+
const directiveValue = buildDirectiveValue(value, ctx);
|
|
26
|
+
directiveArray.push([directiveName, directiveValue]);
|
|
27
|
+
}
|
|
28
|
+
markImport(ctx, 'withDirectives');
|
|
29
|
+
const withDirectivesAlias = getAlias(ctx.vitarxAliases, 'withDirectives');
|
|
30
|
+
return addPureComment(createWithDirectivesCall(viewCall, directiveArray, withDirectivesAlias));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 构建指令值对象
|
|
34
|
+
*/
|
|
35
|
+
function buildDirectiveValue(value, ctx) {
|
|
36
|
+
if (isIdentifier(value)) {
|
|
37
|
+
markImport(ctx, 'unref');
|
|
38
|
+
const unrefAlias = getAlias(ctx.vitarxAliases, 'unref');
|
|
39
|
+
return t.objectExpression([
|
|
40
|
+
t.objectMethod('get', t.identifier('value'), [], t.blockStatement([t.returnStatement(t.callExpression(t.identifier(unrefAlias), [value]))]))
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
return t.objectExpression([
|
|
44
|
+
t.objectMethod('get', t.identifier('value'), [], t.blockStatement([t.returnStatement(value)]))
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import { isJSXElement, isJSXText } from '@babel/types';
|
|
3
|
+
import { collectFragmentVIfChains, createArrowFunction, createBranch, isWhitespaceJSXText, removeVDirectives } from '../../utils/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* 处理 Fragment 中的 v-if 链
|
|
6
|
+
* @param path - JSX Fragment 路径
|
|
7
|
+
* @param ctx - 转换上下文
|
|
8
|
+
* @param transformJSXElement - JSX 元素转换函数
|
|
9
|
+
*/
|
|
10
|
+
export function processVIfChain(path, ctx, transformJSXElement) {
|
|
11
|
+
const children = path.node.children;
|
|
12
|
+
const chains = collectFragmentVIfChains(children);
|
|
13
|
+
if (chains.length === 0)
|
|
14
|
+
return;
|
|
15
|
+
// 从后向前处理链,避免索引偏移问题
|
|
16
|
+
for (let c = chains.length - 1; c >= 0; c--) {
|
|
17
|
+
processVIfChainItem(chains[c], children, ctx, transformJSXElement);
|
|
18
|
+
}
|
|
19
|
+
// 清理已处理的节点
|
|
20
|
+
cleanupProcessedChildren(path, children);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 处理单个 v-if 链
|
|
24
|
+
*/
|
|
25
|
+
function processVIfChainItem(chain, children, ctx, transformJSXElement) {
|
|
26
|
+
const { nodes, conditions, endIndex } = chain;
|
|
27
|
+
// 计算链在 children 中的起始位置
|
|
28
|
+
let start = -1;
|
|
29
|
+
for (let i = 0; i < children.length; i++) {
|
|
30
|
+
const child = children[i];
|
|
31
|
+
if (isJSXElement(child) && child === nodes[0]) {
|
|
32
|
+
start = i;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 移除指令并转换节点
|
|
37
|
+
const branches = [];
|
|
38
|
+
for (const node of nodes) {
|
|
39
|
+
removeVDirectives(node);
|
|
40
|
+
const transformedNode = transformJSXElement(node, ctx, false);
|
|
41
|
+
if (transformedNode) {
|
|
42
|
+
branches.push(createArrowFunction(transformedNode));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 生成 branch 调用
|
|
46
|
+
const branchCall = createBranch({ conditions, branches }, ctx);
|
|
47
|
+
// 替换链的第一个节点,其他节点标记为 null
|
|
48
|
+
if (start >= 0) {
|
|
49
|
+
children[start] = branchCall;
|
|
50
|
+
for (let k = start + 1; k <= endIndex; k++) {
|
|
51
|
+
children[k] = t.nullLiteral();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 清理已处理的子节点
|
|
57
|
+
*/
|
|
58
|
+
function cleanupProcessedChildren(path, children) {
|
|
59
|
+
path.node.children = children.filter(child => {
|
|
60
|
+
if (t.isNullLiteral(child))
|
|
61
|
+
return false;
|
|
62
|
+
return !(isJSXText(child) && isWhitespaceJSXText(child));
|
|
63
|
+
});
|
|
64
|
+
}
|