@dongmingzhixiu/vuexir-compiler 0.3.9 → 0.3.11
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/transform/vue2ir.js +62 -2
- package/package.json +1 -1
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
|
+
/** R104 脚本里声明过的名字(const/let/function)——未登记为 state 时走运行时求值 */
|
|
45
|
+
let scriptDeclaredNames = new Set();
|
|
44
46
|
/** R86 store 别名表:`ui` → StoreDef(每次 vueToIr 重建;与 vforConsts 同风格) */
|
|
45
47
|
let storeAliases = new Map();
|
|
46
48
|
/** R86 把 `alias.field.path` 拍平为 `alias_field.path`;getter/action 返回哨兵串 */
|
|
@@ -890,6 +892,12 @@ const TAG_MAP = {
|
|
|
890
892
|
small: { type: 'text', defaultProps: { fontSize: 12 } },
|
|
891
893
|
u: { type: 'text', defaultProps: { fontSize: 14 } },
|
|
892
894
|
code: { type: 'text', defaultProps: { fontSize: 13 } },
|
|
895
|
+
// R104:真实页面高频标签(原生端无对应语义,按最接近的容器/文本降级)
|
|
896
|
+
select: { type: 'group', defaultProps: { flexDirection: 'column' } },
|
|
897
|
+
option: { type: 'text', defaultProps: { fontSize: 14 } },
|
|
898
|
+
aside: { type: 'group', defaultProps: { flexDirection: 'column' } },
|
|
899
|
+
figure: { type: 'group', defaultProps: { flexDirection: 'column' } },
|
|
900
|
+
figcaption: { type: 'text', defaultProps: { fontSize: 12 } },
|
|
893
901
|
h1: { type: 'text', defaultProps: { fontSize: 24, fontWeight: 'bold' } },
|
|
894
902
|
h2: { type: 'text', defaultProps: { fontSize: 20, fontWeight: 'bold' } },
|
|
895
903
|
h3: { type: 'text', defaultProps: { fontSize: 17, fontWeight: 'medium' } },
|
|
@@ -908,6 +916,7 @@ export function vueToIr(source, opts = {}) {
|
|
|
908
916
|
compPosition = new Map();
|
|
909
917
|
vforConsts = {};
|
|
910
918
|
storeAliases = new Map();
|
|
919
|
+
scriptDeclaredNames = new Set();
|
|
911
920
|
storeConstArrays = {};
|
|
912
921
|
derivedNames = new Map();
|
|
913
922
|
storeGetterWarnings = [];
|
|
@@ -1002,6 +1011,17 @@ export function vueToIr(source, opts = {}) {
|
|
|
1002
1011
|
if (script && script.content.trim()) {
|
|
1003
1012
|
const src = script.content;
|
|
1004
1013
|
stateInfo = extractScreenState(src);
|
|
1014
|
+
// R104:记录脚本里所有顶层声明(插值命中但非 state 时下沉求值)
|
|
1015
|
+
for (const dm of src.matchAll(/(?:const|let|var|function)\s+([A-Za-z_$][\w$]*)/g)) {
|
|
1016
|
+
scriptDeclaredNames.add(dm[1]);
|
|
1017
|
+
}
|
|
1018
|
+
for (const dm of src.matchAll(/const\s*\{([^}]+)\}\s*=/g)) {
|
|
1019
|
+
for (const part of dm[1].split(',')) {
|
|
1020
|
+
const nm = part.split(':').pop()?.trim().replace(/=.*$/, '').trim();
|
|
1021
|
+
if (nm && /^[A-Za-z_$][\w$]*$/.test(nm))
|
|
1022
|
+
scriptDeclaredNames.add(nm);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1005
1025
|
const fns = extractFunctions(src);
|
|
1006
1026
|
// M1 已落地的 setter 函数(x.value = <expr>)升级为完整 handler:body 原样进运行时(含 localStorage/状态读写)
|
|
1007
1027
|
handlers = fns.map((f) => ({
|
|
@@ -1334,6 +1354,10 @@ function walk(node, classProps, comboRules, scopedRules, pseudoRules, warnings,
|
|
|
1334
1354
|
if (state && state.states.some((s) => s.name === name)) {
|
|
1335
1355
|
return [{ type: 'text', bind: { key: name } }];
|
|
1336
1356
|
}
|
|
1357
|
+
// R104:脚本里声明过但不是 ref 状态(computed/普通 const/解构来的值)→ 交给 JS 运行时求值
|
|
1358
|
+
if (scriptDeclaredNames.has(name)) {
|
|
1359
|
+
return [{ type: 'text', props: { bind: addJsExprState(nameRaw) } }];
|
|
1360
|
+
}
|
|
1337
1361
|
throw new VueCompileError(`插值 {{ ${name} }}:未在 <script setup> 中声明为 ref 状态(M1 白名单:仅支持已声明状态)`);
|
|
1338
1362
|
}
|
|
1339
1363
|
// R92 点路径兜底:{{ a.b ?? '默认' }} → fieldOf + fallback
|
|
@@ -1359,7 +1383,8 @@ function walk(node, classProps, comboRules, scopedRules, pseudoRules, warnings,
|
|
|
1359
1383
|
return [{ type: 'text', props: { text: ' ' } }];
|
|
1360
1384
|
}
|
|
1361
1385
|
}
|
|
1362
|
-
|
|
1386
|
+
// R104:静态翻译不了 → 交给 JS 运行时求值(与元素文本分支保持一致)
|
|
1387
|
+
return [{ type: 'text', props: { bind: addJsExprState(nameRaw) } }];
|
|
1363
1388
|
}
|
|
1364
1389
|
case NodeTypes.ELEMENT:
|
|
1365
1390
|
return [walkElement(node, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable, propRefs, loopScope, ancestorClasses)];
|
|
@@ -1562,12 +1587,47 @@ function buildComponentTree(comp, warnings) {
|
|
|
1562
1587
|
}
|
|
1563
1588
|
function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable = [], propRefs, loopScope = [], ancestorClasses = []) {
|
|
1564
1589
|
let elementClasses = []; // M5 后代选择器:本元素 class 清单(子节点递归传祖先链)
|
|
1590
|
+
// R104:<br> 换行 → 占位间距(原生端没有行内换行语义)
|
|
1591
|
+
if (node.tag === 'br') {
|
|
1592
|
+
return { type: 'spacer', props: { height: 8 } };
|
|
1593
|
+
}
|
|
1565
1594
|
// M2 v-for:任意元素(含 <template>)带 v-for → 包一层 for 节点,自身去指令后作项模板
|
|
1566
1595
|
const forDir = node.props.find((p) => p.type === NodeTypes.DIRECTIVE && p.name === 'for');
|
|
1567
1596
|
if (forDir) {
|
|
1568
1597
|
// AttributeNode 无 exp;DirectiveNode 才有(find 命中 DIRECTIVE 但 TS 不窄化 union)
|
|
1569
1598
|
const exp = 'exp' in forDir ? forDir.exp : undefined;
|
|
1570
|
-
const
|
|
1599
|
+
const forExp = exp && exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : '';
|
|
1600
|
+
// R105:`v-for="x in stats"` 里的 stats 是 computed/普通变量(不是 ref 状态)
|
|
1601
|
+
// → 下沉成运行时求值的 JSON 数组状态,列表照常渲染
|
|
1602
|
+
let sunkList;
|
|
1603
|
+
{
|
|
1604
|
+
const m = forExp.trim().match(/^(?:\(([^)]+)\)|([A-Za-z_$][\w$]*))\s+in\s+([A-Za-z_$][\w$]*)$/);
|
|
1605
|
+
if (m) {
|
|
1606
|
+
const src = m[3];
|
|
1607
|
+
const isState = (state?.states ?? []).some((st) => st.name === src);
|
|
1608
|
+
const isConst = vforConsts && src in vforConsts;
|
|
1609
|
+
if (!isState && !isConst && scriptDeclaredNames.has(src)) {
|
|
1610
|
+
sunkList = addJsExprState(`JSON.stringify(${src})`);
|
|
1611
|
+
const vfSunk = {
|
|
1612
|
+
listState: sunkList,
|
|
1613
|
+
itemKey: (m[1] ? m[1].split(',')[0] : m[2]).trim(),
|
|
1614
|
+
indexKey: m[1] ? m[1].split(',')[1]?.trim() : undefined,
|
|
1615
|
+
sunk: true,
|
|
1616
|
+
};
|
|
1617
|
+
const innerNodeSunk = {
|
|
1618
|
+
...node,
|
|
1619
|
+
props: node.props.filter((p) => !(p.type === NodeTypes.DIRECTIVE && p.name === 'for')),
|
|
1620
|
+
};
|
|
1621
|
+
const itemSunk = walkElement(innerNodeSunk, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable, propRefs, [
|
|
1622
|
+
...loopScope,
|
|
1623
|
+
{ itemKey: vfSunk.itemKey, indexKey: vfSunk.indexKey },
|
|
1624
|
+
], ancestorClasses);
|
|
1625
|
+
return { type: 'for', vfor: vfSunk, children: [itemSunk] };
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
const vf = parseVFor(forExp, warnings, vforConsts);
|
|
1630
|
+
void sunkList;
|
|
1571
1631
|
if (!vf) {
|
|
1572
1632
|
throw new VueCompileError('v-for 仅支持静态数组字面量(M2,如 v-for="item in [1,2,3]" / v-for="(item, i) in [\'a\',\'b\']";动态列表随 M3/M4)');
|
|
1573
1633
|
}
|