@dongmingzhixiu/vuexir-compiler 0.4.1 → 0.4.3
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/codegen/compose.js
CHANGED
|
@@ -439,6 +439,10 @@ function safeBind(name, ctx, loop) {
|
|
|
439
439
|
return '""';
|
|
440
440
|
}
|
|
441
441
|
function kotlinCondExpr(expr, ctx, loop) {
|
|
442
|
+
// R128:下沉的条件状态 → 判真
|
|
443
|
+
const sunk = expr.match(/^__vrnCond:(\w+)$/);
|
|
444
|
+
if (sunk)
|
|
445
|
+
return `vrnTruthy(${sunk[1]}.toString())`;
|
|
442
446
|
// R114:条件里引用了原生端不存在的标识符 → 回退为 true(保留内容)并告警,避免整页编译失败
|
|
443
447
|
{
|
|
444
448
|
// 先剥掉字符串字面量,避免把 "success" 之类当成标识符
|
package/dist/codegen/swiftui.js
CHANGED
|
@@ -511,6 +511,10 @@ function emitNode(node, ctx) {
|
|
|
511
511
|
}
|
|
512
512
|
/** JS 条件表达式 → Swift 兼容:===/!== 为 JS 严格比较,Swift 用 ==/!=;字符串字面量单引号 → 双引号 */
|
|
513
513
|
function swiftCondExpr(expr, known) {
|
|
514
|
+
// R128:下沉的条件状态 → 判真
|
|
515
|
+
const sunk = expr.match(/^__vrnCond:(\w+)$/);
|
|
516
|
+
if (sunk)
|
|
517
|
+
return `vrnTruthy(${sunk[1]})`;
|
|
514
518
|
// R114:条件引用原生端不存在的标识符 → true(保留内容),避免整页编译失败
|
|
515
519
|
const knownSet = known && known.size > 0 ? known : currentStateNames;
|
|
516
520
|
if (knownSet && knownSet.size > 0) {
|
|
@@ -810,7 +814,7 @@ function emitNodeInner(node, ctx) {
|
|
|
810
814
|
let line = isRef
|
|
811
815
|
? pad(`Text(${props.ref})`)
|
|
812
816
|
: props.bindExpr !== undefined
|
|
813
|
-
? pad(`Text(${props.bindExpr})`)
|
|
817
|
+
? pad(`Text(${String(props.bindExpr).includes('.') ? '""' : props.bindExpr})`)
|
|
814
818
|
: props.fieldOf !== undefined
|
|
815
819
|
? pad(`Text(vrnField(${props.fieldOf.obj}, "${props.fieldOf.field}"${props.fieldOf.fallback !== undefined ? `, "${swiftStringEscape(props.fieldOf.fallback)}"` : ''}))`)
|
|
816
820
|
: props.dynField !== undefined
|
package/dist/transform/vue2ir.js
CHANGED
|
@@ -41,6 +41,8 @@ function addJsExprState(expr) {
|
|
|
41
41
|
jsExprStates.push({ name, init: '', computed: js });
|
|
42
42
|
return name;
|
|
43
43
|
}
|
|
44
|
+
/** R125 当前生效的 v-for 作用域栈(绕开 loopScope 透传不一致的问题) */
|
|
45
|
+
let activeLoops = [];
|
|
44
46
|
/** R115 是否允许未注册组件降级(由 vueToIr 选项设置) */
|
|
45
47
|
let allowUnknownComponentsFlag = false;
|
|
46
48
|
/** R104 脚本里声明过的名字(const/let/function)——未登记为 state 时走运行时求值 */
|
|
@@ -979,6 +981,7 @@ export function vueToIr(source, opts = {}) {
|
|
|
979
981
|
jsExprSeq = 0;
|
|
980
982
|
jsExprStates = [];
|
|
981
983
|
allowUnknownComponentsFlag = opts.allowUnknownComponents === true;
|
|
984
|
+
activeLoops = [];
|
|
982
985
|
const warnings = [];
|
|
983
986
|
const { descriptor, errors } = parse(source, { filename: 'Screen.vue' });
|
|
984
987
|
if (errors.length > 0) {
|
|
@@ -1417,12 +1420,18 @@ function walk(node, classProps, comboRules, scopedRules, pseudoRules, warnings,
|
|
|
1417
1420
|
if (tri)
|
|
1418
1421
|
return [{ type: 'text', props: { bindExpr: tri } }];
|
|
1419
1422
|
// M2:v-for 循环变量(item/index)优先于状态表
|
|
1420
|
-
const loopVar = loopScope.find((l) => l.itemKey === name || l.indexKey === name)
|
|
1423
|
+
const loopVar = loopScope.find((l) => l.itemKey === name || l.indexKey === name) ??
|
|
1424
|
+
[...activeLoops].reverse().find((l) => l.itemKey === name.split('.')[0] || l.indexKey === name.split('.')[0]);
|
|
1421
1425
|
if (loopVar) {
|
|
1422
|
-
// M4b 动态列表项属性访问 {{ item.field }} → dynField
|
|
1426
|
+
// M4b 动态列表项属性访问 {{ item.field }} / item.a.b → dynField(点路径拍平键)
|
|
1423
1427
|
const dot = name.split('.');
|
|
1424
|
-
if (dot.length
|
|
1425
|
-
return [{ type: 'text', props: { dynField: { var: dot[0], field: dot
|
|
1428
|
+
if (dot.length >= 2 && dot[0] === loopVar.itemKey && dot.slice(1).every((seg) => /^\w+$/.test(seg))) {
|
|
1429
|
+
return [{ type: 'text', props: { dynField: { var: dot[0], field: dot.slice(1).join('.') } } }];
|
|
1430
|
+
}
|
|
1431
|
+
// R126:循环体内出现复杂表达式(三元等)→ 原生端无法静态求值,置空避免非法代码
|
|
1432
|
+
if (!/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(name)) {
|
|
1433
|
+
warnings.push(`v-for 项内复杂表达式「${name.slice(0, 30)}」暂不支持 → 已置空(R126)`);
|
|
1434
|
+
return [{ type: 'text', props: { text: ' ' } }];
|
|
1426
1435
|
}
|
|
1427
1436
|
return [{ type: 'text', bind: { key: name } }];
|
|
1428
1437
|
}
|
|
@@ -1707,10 +1716,12 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
1707
1716
|
...node,
|
|
1708
1717
|
props: node.props.filter((p) => !(p.type === NodeTypes.DIRECTIVE && p.name === 'for')),
|
|
1709
1718
|
};
|
|
1719
|
+
activeLoops.push({ itemKey: vfSunk.itemKey, indexKey: vfSunk.indexKey });
|
|
1710
1720
|
const itemSunk = walkElement(innerNodeSunk, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable, propRefs, [
|
|
1711
1721
|
...loopScope,
|
|
1712
1722
|
{ itemKey: vfSunk.itemKey, indexKey: vfSunk.indexKey },
|
|
1713
1723
|
], ancestorClasses);
|
|
1724
|
+
activeLoops.pop();
|
|
1714
1725
|
return { type: 'for', vfor: vfSunk, children: [itemSunk] };
|
|
1715
1726
|
}
|
|
1716
1727
|
}
|
|
@@ -1746,10 +1757,12 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
1746
1757
|
throw new VueCompileError(`v-for 动态数据源「${vf.listState}」未在 <script setup> 中声明为 ref 数组(M4b)`);
|
|
1747
1758
|
}
|
|
1748
1759
|
const innerNode = { ...node, props: node.props.filter((p) => !(p.type === NodeTypes.DIRECTIVE && p.name === 'for')) };
|
|
1760
|
+
activeLoops.push({ itemKey: vf.itemKey, indexKey: vf.indexKey });
|
|
1749
1761
|
const item = walkElement(innerNode, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable, propRefs, [
|
|
1750
1762
|
...loopScope,
|
|
1751
1763
|
{ itemKey: vf.itemKey, indexKey: vf.indexKey },
|
|
1752
1764
|
], ancestorClasses);
|
|
1765
|
+
activeLoops.pop();
|
|
1753
1766
|
return { type: 'for', vfor: vf, children: [item] };
|
|
1754
1767
|
}
|
|
1755
1768
|
// R76 Vue 内置容器组件(Transition/KeepAlive/Teleport/Suspense/Fragment)→ 透明展开:
|
|
@@ -1984,6 +1997,7 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
1984
1997
|
// 组件 View 内 {{ prop }}(propRefs)→ 单插值时可整体作为 text ref(Text(prop))
|
|
1985
1998
|
// - 其余(group/image):递归 walk
|
|
1986
1999
|
const children = [];
|
|
2000
|
+
let prevCondExpr;
|
|
1987
2001
|
if (mapping.type === 'text') {
|
|
1988
2002
|
let text = '';
|
|
1989
2003
|
let refOnly;
|
|
@@ -2000,7 +2014,8 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
2000
2014
|
// 动态列表项属性 {{ item.field }}:基名(dot[0])命中循环变量才成立
|
|
2001
2015
|
const dot = name.split('.');
|
|
2002
2016
|
const base = dot[0];
|
|
2003
|
-
const loopVar = loopScope.find((l) => l.itemKey === base || l.indexKey === base)
|
|
2017
|
+
const loopVar = loopScope.find((l) => l.itemKey === base || l.indexKey === base) ??
|
|
2018
|
+
[...activeLoops].reverse().find((l) => l.itemKey === base || l.indexKey === base);
|
|
2004
2019
|
const dyn = loopVar && dot.length >= 2 && dot[0] === loopVar.itemKey && dot.slice(1).every((seg) => /^\w+$/.test(seg));
|
|
2005
2020
|
if (!text && !parts.some((p) => 'bind' in p || 'dynField' in p || 'fieldOf' in p)) {
|
|
2006
2021
|
// M2 模板内三元(D1 小范围):cond ? 'a' : 'b' → Swift 三元字符串表达式(Text(expr))
|
|
@@ -2069,7 +2084,7 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
2069
2084
|
}
|
|
2070
2085
|
// 混合段:静态文本之后的插值
|
|
2071
2086
|
if (dyn)
|
|
2072
|
-
parts.push({ dynField: { var: dot[0], field: dot
|
|
2087
|
+
parts.push({ dynField: { var: dot[0], field: dot.slice(1).join('.') } });
|
|
2073
2088
|
else if (/^\w+$/.test(name) && (loopVar || (state && state.states.some((s) => s.name === name))))
|
|
2074
2089
|
parts.push({ bind: name });
|
|
2075
2090
|
else if (propRefs)
|
|
@@ -2163,7 +2178,30 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
2163
2178
|
}
|
|
2164
2179
|
continue;
|
|
2165
2180
|
}
|
|
2166
|
-
|
|
2181
|
+
// R128:v-else / v-else-if 与前一兄弟互补(此前无条件渲染,空状态块常显)
|
|
2182
|
+
const __kids = walk(child, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable, propRefs, loopScope, [
|
|
2183
|
+
...ancestorClasses,
|
|
2184
|
+
...elementClasses,
|
|
2185
|
+
]);
|
|
2186
|
+
if (child.type === NodeTypes.ELEMENT) {
|
|
2187
|
+
const dirs = child.props.filter((p2) => p2.type === NodeTypes.DIRECTIVE);
|
|
2188
|
+
const isElse = dirs.some((d) => d.name === 'else');
|
|
2189
|
+
const elseIf = dirs.find((d) => d.name === 'else-if');
|
|
2190
|
+
if (isElse || elseIf) {
|
|
2191
|
+
const prev = prevCondExpr;
|
|
2192
|
+
const own = elseIf?.exp?.content?.trim();
|
|
2193
|
+
if (prev) {
|
|
2194
|
+
const condExpr = own ? `(${prev}) && (${own})` : `!(${prev})`;
|
|
2195
|
+
const stName = addJsExprState(`String(${condExpr})`);
|
|
2196
|
+
for (const k of __kids)
|
|
2197
|
+
k.condition = `__vrnCond:${stName}`;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
const ifDir = dirs.find((d) => d.name === 'if');
|
|
2201
|
+
if (ifDir?.exp?.content)
|
|
2202
|
+
prevCondExpr = ifDir.exp.content.trim();
|
|
2203
|
+
}
|
|
2204
|
+
children.push(...__kids);
|
|
2167
2205
|
}
|
|
2168
2206
|
}
|
|
2169
2207
|
// R108:文本节点丢弃布局类属性(原生端 width/height/flexDirection 只对容器有意义)
|
|
@@ -2225,7 +2263,19 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
2225
2263
|
const mCond = nc.match(/^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/);
|
|
2226
2264
|
const ownerKnown = mCond !== null &&
|
|
2227
2265
|
((state?.states ?? []).some((st) => st.name === mCond[1]) || (propRefs?.has(mCond[1]) ?? false));
|
|
2228
|
-
|
|
2266
|
+
// R128:条件里的复杂表达式(含 || && 比较、对象字段组合)统一下沉给 JS 运行时求值,
|
|
2267
|
+
// 生成 `__vrnCond:<state>`,两端只判真——不再往条件翻译器里堆规则
|
|
2268
|
+
const simpleCond = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)?$/.test(nc) ||
|
|
2269
|
+
/^![A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)?$/.test(nc) ||
|
|
2270
|
+
/^[A-Za-z_$][\w$]*(\.length)?\s*(==|!=|>|<|>=|<=)\s*('.*'|".*"|-?\d+)$/.test(nc);
|
|
2271
|
+
const usesLoopVar = activeLoops.some((l) => new RegExp(`\\b${l.itemKey}\\b`).test(nc));
|
|
2272
|
+
if (!simpleCond && !usesLoopVar) {
|
|
2273
|
+
const stName = addJsExprState(`String(${nc})`);
|
|
2274
|
+
irNode.condition = `__vrnCond:${stName}`;
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
irNode.condition = ownerKnown && mCond ? `__vrnField__:${mCond[1]}:${mCond[2]}` : nc;
|
|
2278
|
+
}
|
|
2229
2279
|
}
|
|
2230
2280
|
if (activeWhen && activeProps) {
|
|
2231
2281
|
irNode.active = { when: activeWhen, props: activeProps };
|