@dongmingzhixiu/vuexir-compiler 0.3.10 → 0.3.12
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 +58 -3
- package/package.json +1 -1
package/dist/transform/vue2ir.js
CHANGED
|
@@ -1587,6 +1587,10 @@ function buildComponentTree(comp, warnings) {
|
|
|
1587
1587
|
}
|
|
1588
1588
|
function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable = [], propRefs, loopScope = [], ancestorClasses = []) {
|
|
1589
1589
|
let elementClasses = []; // M5 后代选择器:本元素 class 清单(子节点递归传祖先链)
|
|
1590
|
+
// R107:SVG 图标(svg/path/circle/line/rect/polygon/g/defs/use)纯装饰 → 忽略,不阻断编译
|
|
1591
|
+
if (['svg', 'path', 'circle', 'line', 'rect', 'polygon', 'polyline', 'g', 'defs', 'use', 'text', 'ellipse'].includes(node.tag) && node.tag !== 'text') {
|
|
1592
|
+
return { type: 'spacer', props: { height: 0 } };
|
|
1593
|
+
}
|
|
1590
1594
|
// R104:<br> 换行 → 占位间距(原生端没有行内换行语义)
|
|
1591
1595
|
if (node.tag === 'br') {
|
|
1592
1596
|
return { type: 'spacer', props: { height: 8 } };
|
|
@@ -1596,7 +1600,38 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
1596
1600
|
if (forDir) {
|
|
1597
1601
|
// AttributeNode 无 exp;DirectiveNode 才有(find 命中 DIRECTIVE 但 TS 不窄化 union)
|
|
1598
1602
|
const exp = 'exp' in forDir ? forDir.exp : undefined;
|
|
1599
|
-
const
|
|
1603
|
+
const forExp = exp && exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : '';
|
|
1604
|
+
// R105:`v-for="x in stats"` 里的 stats 是 computed/普通变量(不是 ref 状态)
|
|
1605
|
+
// → 下沉成运行时求值的 JSON 数组状态,列表照常渲染
|
|
1606
|
+
let sunkList;
|
|
1607
|
+
{
|
|
1608
|
+
const m = forExp.trim().match(/^(?:\(([^)]+)\)|([A-Za-z_$][\w$]*))\s+in\s+([A-Za-z_$][\w$]*)$/);
|
|
1609
|
+
if (m) {
|
|
1610
|
+
const src = m[3];
|
|
1611
|
+
const isState = (state?.states ?? []).some((st) => st.name === src);
|
|
1612
|
+
const isConst = vforConsts && src in vforConsts;
|
|
1613
|
+
if (!isState && !isConst && scriptDeclaredNames.has(src)) {
|
|
1614
|
+
sunkList = addJsExprState(`JSON.stringify(${src})`);
|
|
1615
|
+
const vfSunk = {
|
|
1616
|
+
listState: sunkList,
|
|
1617
|
+
itemKey: (m[1] ? m[1].split(',')[0] : m[2]).trim(),
|
|
1618
|
+
indexKey: m[1] ? m[1].split(',')[1]?.trim() : undefined,
|
|
1619
|
+
sunk: true,
|
|
1620
|
+
};
|
|
1621
|
+
const innerNodeSunk = {
|
|
1622
|
+
...node,
|
|
1623
|
+
props: node.props.filter((p) => !(p.type === NodeTypes.DIRECTIVE && p.name === 'for')),
|
|
1624
|
+
};
|
|
1625
|
+
const itemSunk = walkElement(innerNodeSunk, classProps, comboRules, scopedRules, pseudoRules, warnings, state, compTable, propRefs, [
|
|
1626
|
+
...loopScope,
|
|
1627
|
+
{ itemKey: vfSunk.itemKey, indexKey: vfSunk.indexKey },
|
|
1628
|
+
], ancestorClasses);
|
|
1629
|
+
return { type: 'for', vfor: vfSunk, children: [itemSunk] };
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
const vf = parseVFor(forExp, warnings, vforConsts);
|
|
1634
|
+
void sunkList;
|
|
1600
1635
|
if (!vf) {
|
|
1601
1636
|
throw new VueCompileError('v-for 仅支持静态数组字面量(M2,如 v-for="item in [1,2,3]" / v-for="(item, i) in [\'a\',\'b\']";动态列表随 M3/M4)');
|
|
1602
1637
|
}
|
|
@@ -1874,6 +1909,10 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
1874
1909
|
refOnly = name;
|
|
1875
1910
|
parts.push({ bind: name });
|
|
1876
1911
|
}
|
|
1912
|
+
else if (scriptDeclaredNames.has(name)) {
|
|
1913
|
+
// R106:computed / 普通 const / 解构值 → 运行时求值
|
|
1914
|
+
parts.push({ bind: addJsExprState(textOf(child.content).trim()) });
|
|
1915
|
+
}
|
|
1877
1916
|
else {
|
|
1878
1917
|
throw new VueCompileError(`插值 {{ ${name} }}:未在 <script setup> 中声明为 ref 状态(M1 白名单:仅支持已声明状态)`);
|
|
1879
1918
|
}
|
|
@@ -1928,6 +1967,16 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
1928
1967
|
else if (child.type === NodeTypes.COMMENT) {
|
|
1929
1968
|
// 忽略注释
|
|
1930
1969
|
}
|
|
1970
|
+
else if (child.type === NodeTypes.ELEMENT && mapping.type === 'text') {
|
|
1971
|
+
// R107:行内嵌套(<b>光<b/></b>、<span><svg/></span>)→ 有文字并入,纯图标忽略
|
|
1972
|
+
const innerText = elementInnerText(child).trim();
|
|
1973
|
+
if (innerText) {
|
|
1974
|
+
text += innerText;
|
|
1975
|
+
parts.push({ text: innerText }); // 同步进 parts,避免只走 bindParts 时丢失
|
|
1976
|
+
}
|
|
1977
|
+
else
|
|
1978
|
+
warnings.push(`<${node.tag}> 内嵌图标元素 <${child.tag}> 已忽略(R107)`);
|
|
1979
|
+
}
|
|
1931
1980
|
else {
|
|
1932
1981
|
throw new VueCompileError(`<${node.tag}> 内嵌子元素(行内混合)暂不支持(text 节点仅文本/插值)`);
|
|
1933
1982
|
}
|
|
@@ -1954,14 +2003,20 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
|
|
|
1954
2003
|
else {
|
|
1955
2004
|
const t = text.trim();
|
|
1956
2005
|
if (!t) {
|
|
1957
|
-
|
|
2006
|
+
// R106:空的行内元素(图标占位 <i></i>)→ 空文本,内容不丢但也不阻断编译
|
|
2007
|
+
warnings.push(`<${node.tag}> 无文本内容 → 已降级为空占位(R106)`);
|
|
2008
|
+
props.text = ' ';
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
props.text = t;
|
|
1958
2012
|
}
|
|
1959
|
-
props.text = t;
|
|
1960
2013
|
}
|
|
1961
2014
|
}
|
|
1962
2015
|
else {
|
|
1963
2016
|
for (const child of node.children) {
|
|
1964
2017
|
if (mapping.type === 'button') {
|
|
2018
|
+
if (child.type === NodeTypes.ELEMENT && (child.tag === 'br' || !elementInnerText(child).trim()))
|
|
2019
|
+
continue; // R106/R107:按钮内换行/纯图标忽略
|
|
1965
2020
|
if (child.type === NodeTypes.TEXT && child.content.trim()) {
|
|
1966
2021
|
props.label = ((props.label ?? '') + child.content.trim()).trim();
|
|
1967
2022
|
continue;
|