@dongmingzhixiu/vuexir-compiler 0.3.0 → 0.3.2

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.
@@ -93,6 +93,7 @@ class MainActivity : ComponentActivity() {
93
93
  super.onCreate(savedInstanceState)
94
94
  Store.init(applicationContext)
95
95
  VrnBridge.init(this)
96
+ VrnJs.init()
96
97
  // R-SYSTEMBAR:应用固定深色主题(InkBg/InkBar),与系统明暗设置无关。
97
98
  // enableEdgeToEdge() 默认用 SystemBarStyle.auto(...),会跟随系统明暗切换图标明暗——
98
99
  // 系统处于浅色模式时给出深色图标,落在深色顶栏/底栏上不可读(真机反馈:系统栏文字看不清)。
@@ -191,6 +192,75 @@ object Store {
191
192
  fun remove(key: String) { prefs?.edit()?.remove(key)?.apply() }
192
193
  }
193
194
 
195
+ /**
196
+ * R95 P1 Android JS 运行时(与 iOS JavaScriptCore 对位)。
197
+ *
198
+ * 设计:QuickJS 里跑业务逻辑,结果通过全局对象 __vrnOut 回传给原生——
199
+ * 不用 Java/JS 互调反射(QuickJS-Android 的反射桥对复杂类型支持有限,且易在混淆后失效)。
200
+ *
201
+ * VrnJs.eval("1+1") → "2"
202
+ * VrnJs.eval("__vrnOut.chars = countChars(raw)") -> 之后 VrnJs.drain() 取走 {"chars":"12"}
203
+ *
204
+ * drain() 会清空 __vrnOut,原生据此批量更新 Compose State。
205
+ */
206
+ object VrnJs {
207
+ private var js: app.cash.quickjs.QuickJs? = null
208
+ private val lock = Any()
209
+
210
+ /** 预置宿主 shim:console / localStorage(落 SharedPreferences)/ 输出对象 */
211
+ private const val PRELUDE = """
212
+ var __vrnOut = {};
213
+ var __vrnStore = {};
214
+ var localStorage = {
215
+ getItem: function (k) { return __vrnStore[k] === undefined ? null : __vrnStore[k]; },
216
+ setItem: function (k, v) { __vrnStore[k] = String(v); __vrnOut['__ls:' + k] = String(v); },
217
+ removeItem: function (k) { delete __vrnStore[k]; __vrnOut['__ls:' + k] = ''; }
218
+ };
219
+ var console = { log: function () {}, warn: function () {}, error: function () {} };
220
+ """
221
+
222
+ fun init() {
223
+ synchronized(lock) {
224
+ if (js == null) {
225
+ js = app.cash.quickjs.QuickJs.create()
226
+ js?.evaluate(PRELUDE)
227
+ }
228
+ }
229
+ }
230
+
231
+ /** 执行一段 JS;返回最后一个表达式的值(字符串化),异常返回 null 并打日志 */
232
+ fun eval(code: String): String? = synchronized(lock) {
233
+ val engine = js ?: return null
234
+ return try {
235
+ val v = engine.evaluate(code)
236
+ v?.toString()
237
+ } catch (e: Throwable) {
238
+ android.util.Log.w("VrnJs", "eval 失败: " + e.message)
239
+ null
240
+ }
241
+ }
242
+
243
+ /** 取走 JS 侧累积的状态变更(JSON 文本),并清空 */
244
+ fun drain(): String = synchronized(lock) {
245
+ val engine = js ?: return "{}"
246
+ val json = try {
247
+ engine.evaluate("(function(){var s=JSON.stringify(__vrnOut);__vrnOut={};return s;})()")?.toString()
248
+ } catch (e: Throwable) {
249
+ null
250
+ } ?: "{}"
251
+ json.ifEmpty { "{}" }
252
+ }
253
+
254
+ fun close() = synchronized(lock) {
255
+ try {
256
+ js?.close()
257
+ } catch (e: Throwable) {
258
+ // 忽略
259
+ }
260
+ js = null
261
+ }
262
+ }
263
+
194
264
  /**
195
265
  * R81 原生桥(Android)——编译期注入;Vue 侧 bridge.toast(...) 由编译器翻译为 VrnBridge.toast(...)
196
266
  * 与 @dongmingzhixiu/vuexir-templates 的 VRN_BRIDGE_KT 保持一致
@@ -265,6 +265,8 @@ export interface IrScreen {
265
265
  init: number | string | boolean;
266
266
  list?: boolean;
267
267
  obj?: boolean;
268
+ /** R96 computed 表达式(JS 运行时求值后写回该状态) */
269
+ computed?: string;
268
270
  }[];
269
271
  /** M3 <script setup> 提取的 handler 函数(@click="fn()" 引用;body 已做 .value → stateStore 重写)
270
272
  * R72:async:true 表示 `async function`(JS 侧保留 async 关键字;Android 侧按协程形状生成) */
@@ -8,6 +8,8 @@ export interface ScreenStateInfo {
8
8
  init: number | string | boolean;
9
9
  list?: boolean;
10
10
  obj?: boolean;
11
+ /** R96 computed 表达式(JS 运行时求值后写回该状态) */
12
+ computed?: string;
11
13
  }[];
12
14
  /** setter 函数名 → { target: 状态变量名, param: 形参名, expr: 无参函数体的赋值表达式 } */
13
15
  setters: Record<string, {
@@ -46,6 +48,7 @@ export declare function generateRuntimeSource(states: {
46
48
  name: string;
47
49
  init: number | string | boolean;
48
50
  list?: boolean;
51
+ computed?: string;
49
52
  }[], handlers: {
50
53
  name: string;
51
54
  params: string;
@@ -285,6 +285,19 @@ export function rewriteStateRefs(body, names) {
285
285
  }
286
286
  /** 生成 runtime.js 完整内容(M3:stateStore Proxy + handlers + onMounted + localStorage shim) */
287
287
  export function generateRuntimeSource(states, handlers, onMounted, onMountedAsync = false) {
288
+ // R96 computed:JS 运行时按表达式求值并写回 stateStore(写回经 Proxy → pushState 直达原生)
289
+ const computedStates = states.filter((st) => st.computed);
290
+ const computedBlock = computedStates.length === 0
291
+ ? ''
292
+ : `
293
+ // R96 computed 求值:页面里的 computed(() => expr) 原样在 JS 里算,结果写回状态
294
+ function __vrnRecompute() {
295
+ ${computedStates
296
+ .map((st) => ` try { stateStore.${st.name} = String(${st.computed}); } catch (e) { /* 求值失败保持初值 */ }`)
297
+ .join('\n')}
298
+ }
299
+ __vrnRecompute();
300
+ `;
288
301
  const initPairs = states.map((s) => {
289
302
  // M4b 动态列表态:list → JS 真数组('[]' 或 '["a","b"]' JSON 字面量,Swift JSONSerialization 可解码)
290
303
  const v = s.list
@@ -424,7 +437,7 @@ var router = {
424
437
  };
425
438
  // handlers(<script setup> 函数体:ref.value → stateStore 重写)
426
439
  ${handlerSrc}
427
- // onMounted(Swift onAppear → invoke("__onMounted"))
440
+ ${computedBlock}// onMounted(Swift onAppear → invoke("__onMounted"))
428
441
  ${onMounted !== undefined ? `${onMountedAsync ? 'async ' : ''}function __onMounted() {\n${indentBody(onMounted)}\n}` : ''}`;
429
442
  }
430
443
  /** 简单缩进(handler 体统一 2 空格缩进) */
@@ -994,6 +1007,33 @@ export function vueToIr(source, opts = {}) {
994
1007
  derivedNames.set(cm[1], flat);
995
1008
  }
996
1009
  }
1010
+ // R96 computed 求值:`const X = computed(() => expr)` 状态自足时注册为「运行时求值状态」
1011
+ // expr 里的 ref.value 会被重写成 stateStore.X;含未知标识符(外部函数/未声明变量)则跳过,
1012
+ // 留给后续 P2 打包阶段(需要 utils 进 bundle 才能算)
1013
+ {
1014
+ const stateNames = stateInfo.states.map((st) => st.name);
1015
+ const SAFE = new Set([
1016
+ 'Math', 'String', 'Number', 'Boolean', 'Date', 'JSON', 'parseInt', 'parseFloat',
1017
+ 'undefined', 'null', 'true', 'false', 'Array', 'Object', 'stateStore',
1018
+ ]);
1019
+ for (const cm of src.matchAll(/const\s+([A-Za-z_$][\w$]*)\s*=\s*computed\s*\(\s*\(\s*\)\s*=>\s*([^\n]+?)\s*\)\s*$/gm)) {
1020
+ const name = cm[1];
1021
+ const rawExpr = cm[2].trim();
1022
+ if (stateInfo.states.some((st) => st.name === name))
1023
+ continue;
1024
+ 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
+ }
1035
+ }
1036
+ }
997
1037
  // R89 常量折叠:模块级纯常量(含可静态求值的模板字面量)注入为状态
998
1038
  for (const km of src.matchAll(/const\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*(`[^`]*`|'[^']*'|"[^"]*"|-?\d+(?:\.\d+)?)\s*$/gm)) {
999
1039
  const raw = km[2];
@@ -1068,7 +1108,8 @@ export function vueToIr(source, opts = {}) {
1068
1108
  if (onMountedBody !== undefined)
1069
1109
  onMountedBody = renameSrc(onMountedBody);
1070
1110
  }
1071
- if (handlers.length > 0 || onMountedBody !== undefined) {
1111
+ const hasComputed = (stateInfo?.states ?? []).some((st) => st.computed);
1112
+ if (handlers.length > 0 || onMountedBody !== undefined || hasComputed) {
1072
1113
  runtime = generateRuntimeSource(stateInfo?.states ?? [], handlers, onMountedBody, onMountedAsync);
1073
1114
  }
1074
1115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongmingzhixiu/vuexir-compiler",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Vue SFC → IR → 原生源码(SwiftUI / Compose)编译器核心:纯函数、零 IO",
5
5
  "type": "module",
6
6
  "license": "MIT",