@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.
- package/dist/transform/vue2ir.d.ts +1 -0
- package/dist/transform/vue2ir.js +54 -23
- package/package.json +1 -1
package/dist/transform/vue2ir.js
CHANGED
|
@@ -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) =>
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1885
|
+
// R98:混合文本里的复杂表达式同样下沉
|
|
1886
|
+
parts.push({ bind: addJsExprState(textOf(child.content).trim()) });
|
|
1856
1887
|
}
|
|
1857
1888
|
}
|
|
1858
1889
|
}
|