@dongmingzhixiu/vuexir-compiler 0.3.2 → 0.3.4

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.
@@ -48,6 +48,7 @@ export declare function generateRuntimeSource(states: {
48
48
  name: string;
49
49
  init: number | string | boolean;
50
50
  list?: boolean;
51
+ obj?: boolean;
51
52
  computed?: string;
52
53
  }[], handlers: {
53
54
  name: string;
@@ -27,6 +27,20 @@ let storeGetterWarnings = [];
27
27
  let derivedNames = new Map();
28
28
  /** R88 store 文件导出的常量数组(v-for 编译期内联用) */
29
29
  let storeConstArrays = {};
30
+ /** R98 P3 表达式下沉:无法静态翻译的模板表达式 → 运行时求值状态(复用 R96 computed 通道) */
31
+ let jsExprSeq = 0;
32
+ let jsExprStates = [];
33
+ let jsExprStateNames = [];
34
+ /** 把一段 JS 表达式注册为运行时求值状态,返回状态名(模板 bind 用) */
35
+ function addJsExprState(expr) {
36
+ const name = `_vrnE${jsExprSeq++}`;
37
+ // ① ref.value → stateStore.x;② 裸状态名 → stateStore.x(对象态/字符串态都要走 stateStore,
38
+ // 否则 JS 里没有这个变量);③ 形如 fn( 的调用不重写(那是 utils bundle 提供的全局函数)
39
+ let js = expr.replace(/\b([A-Za-z_$][\w$]*)\.value\b/g, (all, id) => jsExprStateNames.includes(id) ? `stateStore.${id}` : all);
40
+ js = js.replace(/(?<![.\w$])([A-Za-z_$][\w$]*)(?!\s*\()/g, (all, id) => jsExprStateNames.includes(id) ? `stateStore.${id}` : all);
41
+ jsExprStates.push({ name, init: '', computed: js });
42
+ return name;
43
+ }
30
44
  /** R86 store 别名表:`ui` → StoreDef(每次 vueToIr 重建;与 vforConsts 同风格) */
31
45
  let storeAliases = new Map();
32
46
  /** R86 把 `alias.field.path` 拍平为 `alias_field.path`;getter/action 返回哨兵串 */
@@ -293,26 +307,34 @@ export function generateRuntimeSource(states, handlers, onMounted, onMountedAsyn
293
307
  // R96 computed 求值:页面里的 computed(() => expr) 原样在 JS 里算,结果写回状态
294
308
  function __vrnRecompute() {
295
309
  ${computedStates
296
- .map((st) => ` try { stateStore.${st.name} = String(${st.computed}); } catch (e) { /* 求值失败保持初值 */ }`)
310
+ .map((st) =>
311
+ // 注意:求值状态在 IR 里以空串声明(原生 @State 是 String),必须字符串化后再推送,
312
+ // 否则 iOS 侧类型不匹配会丢掉值;代价是这条链上的算术会退化成字符串拼接(见 RULES R98 限制)
313
+ ` try { var __v = (${st.computed}); stateStore.${st.name} = (__v === null || __v === undefined) ? '' : String(__v); } catch (e) { /* 求值失败保持初值 */ }`)
297
314
  .join('\n')}
298
315
  }
299
316
  __vrnRecompute();
300
317
  `;
301
318
  const initPairs = states.map((s) => {
302
319
  // M4b 动态列表态:list → JS 真数组('[]' 或 '["a","b"]' JSON 字面量,Swift JSONSerialization 可解码)
303
- const v = s.list
304
- ? String(s.init)
305
- : s.init === '[]'
306
- ? '[]'
307
- : typeof s.init === 'number'
308
- ? String(s.init)
309
- : typeof s.init === 'boolean'
310
- ? (s.init ? 'true' : 'false')
311
- : `"${s.init.replace(/"/g, '\\"')}"`;
320
+ // R98:对象态在 JS 侧要是真对象(原生侧仍收 JSON 字符串),否则 {{ form.name }} 求值失败
321
+ const v = s.obj && typeof s.init === 'string'
322
+ ? `JSON.parse(${JSON.stringify(s.init)})`
323
+ : s.list
324
+ ? String(s.init)
325
+ : s.init === '[]'
326
+ ? '[]'
327
+ : typeof s.init === 'number'
328
+ ? String(s.init)
329
+ : typeof s.init === 'boolean'
330
+ ? (s.init ? 'true' : 'false')
331
+ : `"${s.init.replace(/"/g, '\\"')}"`;
312
332
  return ` ${s.name}: ${v}`;
313
333
  });
314
334
  const handlerSrc = handlers
315
- .map((h) => `${h.async ? 'async ' : ''}function ${h.name}(${h.params}) {\n${indentBody(h.body)}\n}`)
335
+ .map((h) => `${h.async ? 'async ' : ''}function ${h.name}(${h.params}) {\n${indentBody(h.body)}\n${
336
+ // R96/R98:改完状态后重算派生值(computed / 表达式下沉),否则界面不会跟着变
337
+ (states.some((st) => st.computed) ? ' if (typeof __vrnRecompute === "function") __vrnRecompute();\n' : '')}}`)
316
338
  .join('\n');
317
339
  return `// vue-render-native · M3 状态运行时(JavaScriptCore 宿主;语义与 QuickJS 对齐,R12)
318
340
  // stateStore:Swift @State 与 JS 侧状态注册表的双向同步桥(JS 改 → __setState 写回原生;Swift push 经 __fromSwift 防回环)
@@ -880,6 +902,8 @@ export function vueToIr(source, opts = {}) {
880
902
  storeConstArrays = {};
881
903
  derivedNames = new Map();
882
904
  storeGetterWarnings = [];
905
+ jsExprSeq = 0;
906
+ jsExprStates = [];
883
907
  const warnings = [];
884
908
  const { descriptor, errors } = parse(source, { filename: 'Screen.vue' });
885
909
  if (errors.length > 0) {
@@ -1022,16 +1046,9 @@ export function vueToIr(source, opts = {}) {
1022
1046
  if (stateInfo.states.some((st) => st.name === name))
1023
1047
  continue;
1024
1048
  const jsExpr = rawExpr.replace(/\b([A-Za-z_$][\w$]*)\.value\b/g, (all, id) => stateNames.includes(id) ? `stateStore.${id}` : all);
1025
- const idents = [...jsExpr.matchAll(/(?<![.\w$])([A-Za-z_$][\w$]*)/g)].map((m2) => m2[1]);
1026
- const unknown = idents.filter((id) => !SAFE.has(id) && !/^stateStore$/.test(id) && !stateNames.includes(id));
1027
- // 未知标识符只允许出现在字符串字面量里(粗筛:去掉引号内容再判一次)
1028
- const stripped = jsExpr.replace(/'[^']*'|"[^"]*"|`[^`]*`/g, '""');
1029
- const unknown2 = [...stripped.matchAll(/(?<![.\w$])([A-Za-z_$][\w$]*)/g)]
1030
- .map((m2) => m2[1])
1031
- .filter((id) => !SAFE.has(id) && !stateNames.includes(id));
1032
- if (unknown.length === 0 || unknown2.length === 0) {
1033
- stateInfo.states.push({ name, init: '', computed: jsExpr });
1034
- }
1049
+ // R97:不再要求「标识符全部已知」——外部函数由 utils bundle(esbuild)提供,
1050
+ // 运行时找不到时由 try/catch 兜住(保持 init 空串),不静默也不阻断编译
1051
+ stateInfo.states.push({ name, init: '', computed: jsExpr });
1035
1052
  }
1036
1053
  }
1037
1054
  // R89 常量折叠:模块级纯常量(含可静态求值的模板字面量)注入为状态
@@ -1067,6 +1084,8 @@ export function vueToIr(source, opts = {}) {
1067
1084
  if (value !== undefined)
1068
1085
  stateInfo.states.push({ name, init: value });
1069
1086
  }
1087
+ // R98:把当前状态名快照给表达式下沉用(walk 阶段据此把裸状态名重写成 stateStore.x)
1088
+ jsExprStateNames = stateInfo.states.map((st) => st.name);
1070
1089
  // R74:状态名撞原生保留成员(SwiftUI `body`、Kotlin 关键字)→ 自动改名 + 警告(不再只警告导致后续编译失败)
1071
1090
  {
1072
1091
  const RESERVED = ['body', 'self', 'init', 'class', 'object', 'fun', 'val', 'var', 'when', 'is', 'in'];
@@ -1230,6 +1249,15 @@ export function vueToIr(source, opts = {}) {
1230
1249
  }
1231
1250
  // 5) <script setup> 逻辑源码 → 运行时 JS(R12:事件逻辑由原生 JS 引擎执行)
1232
1251
  // 仅支持 JS 子集(无 TS 类型标注、无 import);超出即编译期警告(后续阶段扩展)
1252
+ // R98 P3:把模板表达式下沉产生的求值状态并入页面状态,并重建 runtime(含 recompute)
1253
+ if (jsExprStates.length > 0 && stateInfo) {
1254
+ for (const st of jsExprStates) {
1255
+ if (!stateInfo.states.some((x) => x.name === st.name))
1256
+ stateInfo.states.push({ ...st });
1257
+ }
1258
+ runtime = generateRuntimeSource(stateInfo.states, handlers ?? [], onMountedBody, onMountedAsync);
1259
+ warnings.push(`模板表达式下沉:${jsExprStates.length} 处动态表达式改由 JS 运行时求值(R98)`);
1260
+ }
1233
1261
  for (const w of storeGetterWarnings)
1234
1262
  if (!warnings.includes(w))
1235
1263
  warnings.push(w);
@@ -1836,7 +1864,9 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
1836
1864
  parts.push({ text: ' ' });
1837
1865
  continue;
1838
1866
  }
1839
- throw new VueCompileError(`插值表达式 {{ ${name} }} 暂不支持(M1 仅支持纯状态名;三元/比较表达式 M2 白名单;动态列表项属性 M4b)`);
1867
+ // R98:元素文本里的复杂表达式同样下沉给 JS 运行时
1868
+ parts.push({ bind: addJsExprState(textOf(child.content).trim()) });
1869
+ continue;
1840
1870
  }
1841
1871
  // 混合段:静态文本之后的插值
1842
1872
  if (dyn)
@@ -1852,7 +1882,8 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
1852
1882
  parts.push({ fieldOf: { obj: mp.obj, field: mp.field } });
1853
1883
  }
1854
1884
  else {
1855
- throw new VueCompileError(`混合文本内插值 {{ ${name} }} 暂不支持(仅支持已声明状态 / 循环变量 / 动态列表项属性 / 已声明对象的点路径 R85)`);
1885
+ // R98:混合文本里的复杂表达式同样下沉
1886
+ parts.push({ bind: addJsExprState(textOf(child.content).trim()) });
1856
1887
  }
1857
1888
  }
1858
1889
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongmingzhixiu/vuexir-compiler",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Vue SFC → IR → 原生源码(SwiftUI / Compose)编译器核心:纯函数、零 IO",
5
5
  "type": "module",
6
6
  "license": "MIT",