@dongmingzhixiu/vuexir-compiler 0.5.6 → 0.6.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/dist/codegen/compose.js +71 -9
- package/package.json +1 -1
package/dist/codegen/compose.js
CHANGED
|
@@ -392,12 +392,39 @@ class NavTest {
|
|
|
392
392
|
}
|
|
393
393
|
`;
|
|
394
394
|
/* ================= IR → Compose 生成 ================= */
|
|
395
|
+
/** R-THEME-ANDROID:项目 tokens.css 的 var(--x) 表(默认皮肤 warm 优先,后块不覆盖) */
|
|
396
|
+
let currentCssVars;
|
|
397
|
+
function loadCssVarsAndroid(root) {
|
|
398
|
+
const map = new Map();
|
|
399
|
+
const roots = [join(root, 'src/styles/tokens.css'), join(root, 'src/tokens.css'), join(root, 'styles/tokens.css')];
|
|
400
|
+
for (const p of roots) {
|
|
401
|
+
if (!existsSync(p))
|
|
402
|
+
continue;
|
|
403
|
+
const css = readFileSync(p, 'utf-8');
|
|
404
|
+
for (const m of css.matchAll(/(--[\w-]+)\s*:\s*([^;{}]+);/g)) {
|
|
405
|
+
// 首个值优先(:root/.skin-warm 块在前 → 默认皮肤色)
|
|
406
|
+
if (!map.has(m[1]))
|
|
407
|
+
map.set(m[1], m[2].trim());
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return map;
|
|
411
|
+
}
|
|
395
412
|
/**
|
|
396
413
|
* 颜色 → Compose Color 表达式。
|
|
397
414
|
* 支持 #rgb/#rrggbb 与 rgb()/rgba()(rgba 必须带 alpha,否则会拼出 `0xFFRGBA(...)` 这种非法字面量)。
|
|
415
|
+
* R-THEME-ANDROID:var(--x) → 解析 tokens.css 值(默认皮肤)。
|
|
398
416
|
*/
|
|
399
417
|
function colorExpr(c) {
|
|
400
418
|
const raw = c.trim();
|
|
419
|
+
if (raw.startsWith('var(')) {
|
|
420
|
+
const m = raw.match(/^var\(\s*(--[\w-]+)/);
|
|
421
|
+
if (m && currentCssVars) {
|
|
422
|
+
const v = currentCssVars.get(m[1]);
|
|
423
|
+
if (v !== undefined)
|
|
424
|
+
return colorExpr(v);
|
|
425
|
+
}
|
|
426
|
+
return 'Color(0xFF000000)';
|
|
427
|
+
}
|
|
401
428
|
const fn = raw.match(/^rgba?\(([^)]+)\)$/i);
|
|
402
429
|
if (fn) {
|
|
403
430
|
const parts = fn[1].split(',').map((t) => Number(t.trim()));
|
|
@@ -439,10 +466,17 @@ function safeBind(name, ctx, loop) {
|
|
|
439
466
|
return '""';
|
|
440
467
|
}
|
|
441
468
|
function kotlinCondExpr(expr, ctx, loop) {
|
|
469
|
+
// R-ELSE-AND:v-else 下沉互补状态(_vrnE#)在组件内不可求值 → true(R15 恒显语义,对位 iOS;否则未定义标识符编译失败)
|
|
470
|
+
if (/^_vrnE\d+$/.test(expr.trim()))
|
|
471
|
+
return 'true';
|
|
442
472
|
// R128:下沉的条件状态 → 判真
|
|
443
473
|
const sunk = expr.match(/^__vrnCond:(\w+)$/);
|
|
444
|
-
if (sunk)
|
|
474
|
+
if (sunk) {
|
|
475
|
+
// v-else 互补状态下沉为 _vrnE# → 组件内不可求值,恒显(R15,对位 iOS)
|
|
476
|
+
if (/^_vrnE\d+$/.test(sunk[1]))
|
|
477
|
+
return 'true';
|
|
445
478
|
return `vrnTruthy(${sunk[1]}.toString())`;
|
|
479
|
+
}
|
|
446
480
|
// R114:条件里引用了原生端不存在的标识符 → 回退为 true(保留内容)并告警,避免整页编译失败
|
|
447
481
|
{
|
|
448
482
|
// 先剥掉字符串字面量,避免把 "success" 之类当成标识符
|
|
@@ -1050,6 +1084,8 @@ function subtreeHasWeight(node) {
|
|
|
1050
1084
|
function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentStretch = false) {
|
|
1051
1085
|
const pad = ' '.repeat(depth);
|
|
1052
1086
|
const p = (node.props ?? {});
|
|
1087
|
+
/** R-COMP-ROOT-ABS:组件根(useParamModifier && depth===1)去掉 Modifier.align(顶层无 BoxScope,编译非法);保留 offset 平移 */
|
|
1088
|
+
const absM = (pp) => (useParamModifier && depth <= 2 ? absModifiers(pp).filter((m) => !m.includes('.align(')) : absModifiers(pp));
|
|
1053
1089
|
const alphaMod = alphaModifier(p);
|
|
1054
1090
|
const alphaArr = alphaMod ? [alphaMod] : [];
|
|
1055
1091
|
/** 容器类型:存在 absolute 子节点 → Box(ZStack 语义,对位 iOS);否则按 flexDirection */
|
|
@@ -1154,7 +1190,7 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
|
|
|
1154
1190
|
const chain = modChain([
|
|
1155
1191
|
...stretchModifiers(parentStretch, node),
|
|
1156
1192
|
...sizeModifiers(p),
|
|
1157
|
-
...
|
|
1193
|
+
...absM(p),
|
|
1158
1194
|
...clipModifiers(p),
|
|
1159
1195
|
...groupBackground(node, p),
|
|
1160
1196
|
...borderModifiers(p),
|
|
@@ -1251,7 +1287,7 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
|
|
|
1251
1287
|
return out;
|
|
1252
1288
|
}
|
|
1253
1289
|
case 'button': {
|
|
1254
|
-
const chain = modChain([...stretchModifiers(parentStretch, node), ...sizeModifiers(p), ...
|
|
1290
|
+
const chain = modChain([...stretchModifiers(parentStretch, node), ...sizeModifiers(p), ...absM(p), ...clipModifiers(p), ...visualModifiers(p), ...borderModifiers(p), ...paddingModifiers(p), ...alphaArr, ...transformModifiers(p)], pad + ' ');
|
|
1255
1291
|
// R108:动态按钮文案(props.bind)优先;Kotlin 里用字符串模板插值
|
|
1256
1292
|
const label = p.bind !== undefined ? String(p.bind) : (p.label ?? '');
|
|
1257
1293
|
const click = kotlinClickAction(p, ctx, loop);
|
|
@@ -1281,7 +1317,7 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
|
|
|
1281
1317
|
ctx.warnings.push('input: v-model「' + String(st) + '」非页面级 string 状态(Android 暂只支持该形态),已生成占位注释');
|
|
1282
1318
|
return pad + '// input: v-model ' + kotlinStr(String(st)) + ' 未支持(Android 仅页面级 string state)';
|
|
1283
1319
|
}
|
|
1284
|
-
const chain = modChain([...stretchModifiers(parentStretch, node), ...sizeModifiers(p), ...
|
|
1320
|
+
const chain = modChain([...stretchModifiers(parentStretch, node), ...sizeModifiers(p), ...absM(p), ...paddingModifiers(p), ...alphaArr, ...transformModifiers(p)], pad + ' ');
|
|
1285
1321
|
const ts = [];
|
|
1286
1322
|
if (p.fontSize !== undefined)
|
|
1287
1323
|
ts.push('fontSize = ' + sp(p.fontSize));
|
|
@@ -1300,7 +1336,7 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
|
|
|
1300
1336
|
return out;
|
|
1301
1337
|
}
|
|
1302
1338
|
case 'image': {
|
|
1303
|
-
const chain = modChain([...stretchModifiers(parentStretch, node), ...sizeModifiers(p), ...
|
|
1339
|
+
const chain = modChain([...stretchModifiers(parentStretch, node), ...sizeModifiers(p), ...absM(p), ...clipModifiers(p), ...visualModifiers(p), ...paddingModifiers(p), ...alphaArr, ...transformModifiers(p)], pad + ' ');
|
|
1304
1340
|
if (p.src)
|
|
1305
1341
|
ctx.warnings.push('image src="' + p.src + '" 资源桥未实现(A5 TODO),生成占位 Box');
|
|
1306
1342
|
let out = pad + '// image src=' + (p.src ?? '') + '(A5 资源桥 TODO)\n';
|
|
@@ -1382,7 +1418,7 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
|
|
|
1382
1418
|
...stretchModifiers(parentStretch, node),
|
|
1383
1419
|
...sizeModifiers(p),
|
|
1384
1420
|
flexModifier(p),
|
|
1385
|
-
...
|
|
1421
|
+
...absM(p),
|
|
1386
1422
|
...alphaArr,
|
|
1387
1423
|
...transformModifiers(p),
|
|
1388
1424
|
].concat(click ? ['Modifier.clickable { ' + click + ' }'] : []);
|
|
@@ -1447,10 +1483,34 @@ function loadComponents(pageFile) {
|
|
|
1447
1483
|
return table;
|
|
1448
1484
|
}
|
|
1449
1485
|
/** 收集组件 IR 中的 ref 参数名(text props.ref → 组件函数参数) */
|
|
1486
|
+
/** R-COMP-PARAMS:表达式 → 根标识符(dialog.title → dialog;!allOpen → allOpen;__vrnField__:dialog:title → dialog;下沉条件 → '')对位 iOS swiftui.ts rootIdent */
|
|
1487
|
+
function rootIdent(expr) {
|
|
1488
|
+
const e = expr.replace(/^!/, '');
|
|
1489
|
+
const f = e.match(/^__vrnField__:(\w+)/);
|
|
1490
|
+
if (f)
|
|
1491
|
+
return f[1];
|
|
1492
|
+
if (e.startsWith('__vrnCond:') || e.startsWith('__vrnField__:'))
|
|
1493
|
+
return '';
|
|
1494
|
+
const id = e.match(/^([A-Za-z_$][\w$]*)/);
|
|
1495
|
+
return id ? id[1] : '';
|
|
1496
|
+
}
|
|
1497
|
+
/** 收集组件 IR 树中 text ref / v-if 条件 / v-for 列表 → prop 名(组件 Composable 参数集合,对位 iOS) */
|
|
1450
1498
|
function collectRefs(node, acc = new Set()) {
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1499
|
+
if (node.type === 'text' && node.props?.ref !== undefined) {
|
|
1500
|
+
const r = rootIdent(node.props.ref);
|
|
1501
|
+
if (r)
|
|
1502
|
+
acc.add(r);
|
|
1503
|
+
}
|
|
1504
|
+
if (node.condition) {
|
|
1505
|
+
const r = rootIdent(node.condition);
|
|
1506
|
+
if (r)
|
|
1507
|
+
acc.add(r);
|
|
1508
|
+
}
|
|
1509
|
+
if (node.type === 'for' && node.vfor?.listState && !/^_vrnE\d+$/.test(node.vfor.listState)) {
|
|
1510
|
+
const r = rootIdent(node.vfor.listState);
|
|
1511
|
+
if (r)
|
|
1512
|
+
acc.add(r);
|
|
1513
|
+
}
|
|
1454
1514
|
for (const c of node.children ?? [])
|
|
1455
1515
|
collectRefs(c, acc);
|
|
1456
1516
|
return acc;
|
|
@@ -1659,6 +1719,8 @@ const ICON_MAP = {
|
|
|
1659
1719
|
explore: 'Icons.Filled.Explore', work: 'Icons.Filled.Work', settings: 'Icons.Filled.Settings',
|
|
1660
1720
|
};
|
|
1661
1721
|
function runTabs(appDir, opts = {}) {
|
|
1722
|
+
// R-THEME-ANDROID:加载项目 tokens.css var 表(colorExpr var(--x) 解析用)
|
|
1723
|
+
currentCssVars = loadCssVarsAndroid(findProjectRoot(appDir));
|
|
1662
1724
|
const routerSrc = readFileSync(opts.routerFile ?? join(appDir, 'router.ts'), 'utf-8');
|
|
1663
1725
|
const { tabs, details, warnings: routerWarnings } = parseRouter(routerSrc, findProjectRoot(appDir), opts.routerFile ?? join(appDir, 'router.ts'));
|
|
1664
1726
|
for (const w of routerWarnings)
|