diff --git a/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/js/handlers/optionParserHandler.js b/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/js/handlers/optionParserHandler.js
index 76221ff92b93a13631f9336523db48b2446aac90..07c64f385c6f05d13776b10969457ee36330e17e 100644
--- a/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/js/handlers/optionParserHandler.js
+++ b/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/js/handlers/optionParserHandler.js
@@ -1,11 +1,12 @@
-import t from '@babel/types';
-import LOG from '../../../logHelper.js';
-import { globalLibPaths } from '../../defaultConfig.js';
-import { addInstance } from '../../jsx/handlers/instanceHandler.js';
-import { JSErrors } from '../../../errors.js';
-import { DATA_REACTIVE, INSTANCE } from '../../jsx/consts.js';
-import { watchParser } from './watchHandler.js';
-import { propsParser } from './propsHandler.js';
+import t from '@babel/types'
+import LOG from '../../../logHelper.js'
+import { globalLibPaths } from '../../defaultConfig.js'
+import { addInstance } from '../../jsx/handlers/instanceHandler.js'
+import { JSErrors } from '../../../errors.js'
+import { DATA_REACTIVE, INSTANCE } from '../../jsx/consts.js'
+import { watchParser } from './watchHandler.js'
+import { propsParser } from './propsHandler.js'
+import { getIsPinia } from '../jsUtils.js';
/**
* 找到Object的key和value
@@ -116,6 +117,7 @@ function computedParser(ast, reactCovert) {
let computedBody = null;
if (prop.type === 'SpreadElement') {
/*
+ vuex中需要讲mapState转成useMapState, pinia不需要
computed: {
...mapState('counter', ['count']),
}
@@ -124,7 +126,8 @@ function computedParser(ast, reactCovert) {
*/
if (prop.argument.type === 'CallExpression') {
const callName = prop.argument.callee.name;
- const ags0 = prop.argument.arguments[0];
+ const isPinia = getIsPinia(reactCovert);
+ const ags0 = prop.argument.arguments[isPinia ? 1 : 0];
let outKeys = [];
let outputsNode = null;
// 存在vuex的模块定义
@@ -143,10 +146,10 @@ function computedParser(ast, reactCovert) {
});
}
}
-
+ const newCallName = isPinia ? callName : globalLibPaths.vuex.imports[callName];
// 创建函数调用表达式 xx('')
const functionCallExpression = t.callExpression(
- t.identifier(globalLibPaths.vuex.imports[callName] || callName),
+ t.identifier(newCallName),
prop.argument.arguments
);
@@ -221,24 +224,26 @@ function computedParser(ast, reactCovert) {
}
function methodsParser(ast, reactCovert) {
+ // vuex中需要讲mapState转成useMapState, pinia不需要
const methodNames = [];
ast?.properties.forEach(prop => {
let computedBody = null;
if (prop.type === 'SpreadElement') {
if (prop.argument.type === 'CallExpression') {
+ const isPinia = getIsPinia(reactCovert);
const callName = prop.argument.callee.name;
- const ags0 = prop.argument.arguments[0];
+ const mapAttrParam = prop.argument.arguments[isPinia ? 1 : 0];
let outKeys = [];
let outputsNode = null;
// 存在vuex的模块定义
- if (ags0.type === 'ObjectExpression') {
- const { keys } = findObjectExpressionKeyAndValue(ags0);
+ if (mapAttrParam.type === 'ObjectExpression') {
+ const { keys } = findObjectExpressionKeyAndValue(mapAttrParam);
outKeys = keys;
} else {
- if (ags0.type === 'StringLiteral') {
+ if (mapAttrParam.type === 'StringLiteral') {
outputsNode = prop.argument.arguments?.[1];
- } else if (ags0.type === 'ArrayExpression') {
- outputsNode = ags0;
+ } else if (mapAttrParam.type === 'ArrayExpression') {
+ outputsNode = mapAttrParam;
}
if (outputsNode && outputsNode.type === 'ArrayExpression') {
outputsNode.elements.forEach(v => {
@@ -247,10 +252,10 @@ function methodsParser(ast, reactCovert) {
});
}
}
-
+ const newCallName = isPinia ? callName : globalLibPaths.vuex.imports[callName]
// 创建函数调用表达式 xx('')
const functionCallExpression = t.callExpression(
- t.identifier(globalLibPaths.vuex.imports[callName] || callName),
+ t.identifier(newCallName),
prop.argument.arguments
);
@@ -298,11 +303,16 @@ function methodsParser(ast, reactCovert) {
// 创建 methods 对象
const methodsObject = t.objectExpression(
- methodNames.map(name => t.objectProperty(t.identifier(name), t.identifier(name), false, true))
+ methodNames.map(name =>
+ t.objectProperty(t.identifier(name), t.identifier(name), false, true)
+ )
);
// 创建 setToInstance 函数调用
- const setToInstanceCall = t.callExpression(t.identifier('setToInstance'), [t.identifier(INSTANCE), methodsObject]);
+ const setToInstanceCall = t.callExpression(t.identifier('setToInstance'), [
+ t.identifier(INSTANCE),
+ methodsObject
+ ]);
// 创建表达式语句
const expressionStatement = t.expressionStatement(setToInstanceCall);
@@ -382,8 +392,10 @@ function componentsParser(ast, reactCovert) {
t.identifier('components'),
t.objectExpression(
ast.properties.map(node => {
- const name = reactCovert.sourceCodeContext.optionTypeRegistComponentMap.get(node.key.name);
- return t.objectProperty(t.identifier(node.key.name), t.identifier(name));
+ const keyName = node.key.name || node.key.value;
+ const mapped = reactCovert.sourceCodeContext.optionTypeRegistComponentMap.get(keyName);
+ const targetName = mapped || keyName;
+ return t.objectProperty(t.identifier(keyName), t.identifier(targetName));
})
)
),
diff --git a/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/handlers/expressionHandler.js b/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/handlers/expressionHandler.js
index c9c462ca79a9a8e0380c18c4c03789e7867e999a..ec35f13fc0e3e889affe9587b95fd31a75141540 100644
--- a/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/handlers/expressionHandler.js
+++ b/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/handlers/expressionHandler.js
@@ -1,13 +1,13 @@
import t from '@babel/types';
import { globalLibPaths } from '../../defaultConfig.js';
-import { addInstance, addUsedGlobalProperties } from './instanceHandler.js';
+import { addInstance, addUsedGlobalProperties } from './instanceHandler.js'
import LOG from '../../../logHelper.js';
import parser from '@babel/parser';
import { traverse } from '@babel/core';
-import { isFunctionParameter, processIdentifier, processIdentifierPath } from './identifierHandler.js';
-import { JSWarnings } from '../../../errors.js';
-import { INSTANCE } from '../consts.js';
+import { isFunctionParameter, processIdentifier, processIdentifierPath } from './identifierHandler.js'
+import { JSWarnings } from '../../../errors.js'
+import { INSTANCE } from '../consts.js'
/**
* 处理this 表达式
@@ -22,7 +22,7 @@ import { INSTANCE } from '../consts.js';
* @param {*} path
*/
export function handlerThisExpression(path, reactCovert) {
- if (isThisNode(path.node.object)) {
+ if (isThisNode(path.node.object, path)) {
// 将 this.xxx 替换为一个不含 this 的标识符 xxx
let name = getThisProperty(path.node);
if (name === '') {
@@ -30,7 +30,7 @@ export function handlerThisExpression(path, reactCovert) {
return;
}
- let replaceNode = createNodeByVueVariable(name, reactCovert);
+ let replaceNode = createNodeByVueVariable(name, reactCovert, path);
if (replaceNode) {
path.replaceWith(replaceNode);
} else {
@@ -64,36 +64,103 @@ export function getThisProperty(node) {
return name;
}
-export const THIS_ALIASES = [
- '_this',
- 'this_',
- '$this',
- 'this$',
- 'self',
- '_self',
- 'self_',
- 'that',
- '_that',
- 'that',
- '_serf',
-];
+export const THIS_ALIASES = ['_this', 'this_', '$this', 'this$', 'self', '_self', 'self_', 'that', '_that', 'that', '_serf', 'seft'];
/**
* 检查给定的节点是否表示 this 或其别名(如 self, _that)
* @param {Object} node - Babel 的 AST 节点
+ * @param path
* @returns {boolean} - 如果节点表示 this 或其别名则返回 true,否则返回 false
*/
-export function isThisNode(node) {
+export function isThisNode(node, path) {
// 检查 this
if (t.isThisExpression(node)) {
return true;
}
// 检查 this 的别名(如 self, _that)
- if (t.isIdentifier(node)) {
- return THIS_ALIASES.includes(node.name);
+ if (!t.isIdentifier(node) || !THIS_ALIASES.includes(node.name)) {
+ return false;
}
+ // 查找该变量的声明节点(定义处)
+ const binding = path.scope.getBinding(node.name);
+ if (!binding) {
+ return false;
+ }
+
+ // 检查该绑定的值是否来自 `this`
+ const declaration = binding.path.node;
+ if (!t.isVariableDeclarator(declaration)) {
+ return false;
+ }
+
+ const init = declaration.init;
+ if (!init) {
+ return false;
+ }
+
+ // 判断初始化是否綁定的 `this` 表达式
+ if (t.isThisExpression(init) || init.name === 'instance') {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * 查找当前node是否在reactive里面使用的
+ *
+ * 用于处理reactive里面调用reactive的参数的场景
+ * 示例转换:
+ * const dataReactive = reactive({
+ * subId: [],
+ * obj: {
+ * taskId: this.subId
+ * }
+ * });
+ * 转换后:
+ * const dataReactive = reactive({
+ * subId: [],
+ * obj: {
+ * taskId: undefined
+ * }
+ * });
+ * @returns {boolean} - 如果节点在reactive里面被调用,返回true,否则返回false
+ */
+function isThisInReactiveScope(path) {
+ if (!path) {
+ return false;
+ }
+
+ let currentPath = path.parentPath;
+ let depth = 0;
+ const MAX_DEPTH = 10; // 最多向上遍历 10 层
+
+ while (currentPath && depth < MAX_DEPTH) {
+ const node = currentPath.node;
+
+ // 1. 遇到函数体节点,立即停止遍历
+ if (
+ node.type === 'ArrowFunctionExpression' ||
+ node.type === 'FunctionDeclaration' ||
+ node.type === 'FunctionExpression' ||
+ node.type === 'ClassMethod'
+ ) {
+ return false;
+ }
+
+ // 遇到 CallExpression 且其 callee 是 'reactive',返回true
+ if (node.type === 'CallExpression' && node.callee?.name === 'reactive') {
+ return true;
+ }
+
+ // 移动到下一个祖先节点
+ currentPath = currentPath.parentPath;
+ depth++;
+ }
+
+ // 遍历了指定层数或者到达了根节点都没有找到
return false;
}
@@ -109,7 +176,7 @@ export function isThisNode(node) {
* 5. store: this.$store -> useStore()
* 6. instance: this.$parent -> instance.$parent
*/
-export function createNodeByVueVariable(name, reactCovert) {
+export function createNodeByVueVariable(name, reactCovert, path) {
// 处理 props
if (reactCovert.sourceCodeContext.findKeyInProps(name)) {
return t.memberExpression(t.identifier(reactCovert.sourceCodeContext.propsName), t.identifier(name));
@@ -124,7 +191,8 @@ export function createNodeByVueVariable(name, reactCovert) {
}
// 处理 reactive
else if (reactCovert.sourceCodeContext.findKeyInReactive(name)) {
- return t.memberExpression(t.identifier(reactCovert.sourceCodeContext.reactive[name]), t.identifier(name));
+ const isInReactive = isThisInReactiveScope(path);
+ return isInReactive ? t.identifier('undefined') : t.memberExpression(t.identifier(reactCovert.sourceCodeContext.reactive[name]), t.identifier(name));
}
// 处理全局属性
else if (reactCovert.sourceCodeContext.findKeyInGlobalProperties(name)) {
@@ -133,14 +201,29 @@ export function createNodeByVueVariable(name, reactCovert) {
}
// 处理自定义的 this 定义
else if (reactCovert.sourceCodeContext.findKeyInSelfThisDefines(name)) {
- return t.memberExpression(
- t.identifier(reactCovert.sourceCodeContext.selfThisDefines.get(name)),
- t.identifier('value')
- );
+ return t.memberExpression(t.identifier(reactCovert.sourceCodeContext.selfThisDefines.get(name)), t.identifier('value'));
}
// 处理特殊的 $ 开头的属性
else if (name.startsWith('$')) {
switch (name) {
+ case '$event': {
+ // 将模板中的 $event 识别为当前事件参数 e
+ return t.identifier('e');
+ }
+ case '$emit': {
+ // 将模板中的 $emit(...) 统一映射为 emit(...)
+ // 若不存在 emit,则自动注入: const emit = defineEmits(props);
+ reactCovert.sourceCodeContext.addExtrasImport('defineEmits', globalLibPaths.vue);
+ reactCovert.addCodeAstToHorizonForOnce('emit', () => {
+ return t.variableDeclaration('const', [
+ t.variableDeclarator(
+ t.identifier('emit'),
+ t.callExpression(t.identifier('defineEmits'), [t.identifier(reactCovert.sourceCodeContext.propsName)])
+ ),
+ ]);
+ });
+ return t.identifier('emit');
+ }
case '$store':
// 处理 Vuex store
reactCovert.sourceCodeContext.addExtrasImport('useStore', globalLibPaths.vuex.path);
@@ -161,10 +244,6 @@ export function createNodeByVueVariable(name, reactCovert) {
addInstance(reactCovert);
return t.memberExpression(t.identifier(INSTANCE), t.identifier(name));
- // 处理 Vue 3 的 $attrs
- case '$attrs':
- return t.identifier('attrs');
-
// 处理国际化相关方法
case '$t':
case '$l':
@@ -185,9 +264,9 @@ export function createNodeByVueVariable(name, reactCovert) {
* @returns
*/
export function memberExpressionValueReplaceHandler(expression, reactCovert, path) {
- const processNode = node => {
+ const processNode = (node) => {
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
- if (isThisNode(node.object)) {
+ if (isThisNode(node.object, path)) {
const name = getThisProperty(node);
if (name === '') {
return node.property;
@@ -196,7 +275,7 @@ export function memberExpressionValueReplaceHandler(expression, reactCovert, pat
// {{this.xxx}} ==> {{dataReactive.xxx}}
return createNodeByVueVariable(name, reactCovert);
}
- if (node.computed && (t.isIdentifier(node.property) || t.isBinaryExpression(node.property))) {
+ if (node.computed && (t.isIdentifier(node.property) || t.isBinaryExpression(node.property)) || t.isMemberExpression(node.property)) {
node.property = processNode(node.property);
}
// 递归处理对象和属性
@@ -229,20 +308,15 @@ export function memberExpressionValueReplaceHandler(expression, reactCovert, pat
if (nodeAst && nodeAst.type !== 'Identifier') {
// 如果解析成功且结果不是简单的标识符,进行进一步处理
- traverse(
- nodeAst,
- {
- Identifier(path) {
- processIdentifierPath(path, reactCovert);
- },
- MemberExpression(path) {
- path.replaceWith(processNode(path.node));
- path.skip();
- },
+ traverse(nodeAst, {
+ Identifier(path) {
+ processIdentifierPath(path, reactCovert);
},
- path.scope,
- path
- );
+ MemberExpression(path) {
+ path.replaceWith(processNode(path.node));
+ path.skip();
+ }
+ }, path.scope, path);
return nodeAst;
} else {
if (!isFunctionParameter(path)) {
@@ -255,3 +329,5 @@ export function memberExpressionValueReplaceHandler(expression, reactCovert, pat
return processNode(expression);
}
+
+
diff --git a/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/stringRegexHandler.js b/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/stringRegexHandler.js
index 22b5aa57ea5a29ecb935bd8c9994744d59b260f0..02c515bff3d3591c03c103b7c6c0962b32cece08 100644
--- a/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/stringRegexHandler.js
+++ b/packages/vue-to-horizon/libs/horizon-vue/src/next/convert/jsx/stringRegexHandler.js
@@ -196,6 +196,20 @@ const convertJsxNoCloseTag = input => {
return input.replaceAll(/
/g, '
');
};
+// 支持动态事件/指令参数归一化
+const normalizeDynamicDirectiveArgs = input => {
+ // @[] 动态事件到 v-on:
+ let result = input.replace(/@\[(.*?)\]((\.[a-z0-9]+)*)(="[^"]*")?/gi, (m, name, modifiers, _, handler = '') => {
+ const modStr = (modifiers || '').replace(/\./g, '__');
+ return `v-on:${name}${modStr}${handler || ''}`;
+ });
+ // v-on:[] -> v-on:
+ result = result.replace(/v-on:\[([^\]]+)\]/gi, (m, name) => `v-on:${name}`);
+ // 通用 v-xxx:[arg] -> v-xxx:arg
+ result = result.replace(/v-([a-zA-Z-]+):\[([^\]]+)\]/g, (m, dir, arg) => `v-${dir}:${arg}`);
+ return result;
+};
+
// 正则表达式匹配 v-model.xxx 语法
const regex = /v-model\.(\w+)/g;
const removeModelModifiers = input => {
@@ -246,6 +260,7 @@ const DefaultHardCodeHandler = {
JSXNotes: convertJSXNotes,
DynamicBinding: convertVueBindingToReact,
ShortBinding: convertVBindShorthand,
+ DynamicArgs: normalizeDynamicDirectiveArgs,
ShortOn: convertVueShortEventToReact,
ShortSlot: convertVueShortSlotToReact,
vShow: transformVShowSyntax,