@dongmingzhixiu/vuexir 0.5.7 → 0.7.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/README.md CHANGED
@@ -8,7 +8,7 @@ npx vrn doctor # 环境自检:Xcode / xcodegen / JDK / ANDROID_HOME
8
8
  npx vrn preview # 多页预览器(P3 起由 @vrn/vite-plugin 承载)
9
9
  ```
10
10
 
11
- 配置(`vrn.config.ts`,P1 落地):
11
+ 配置(`vuexir.config.ts`,P1 落地):
12
12
 
13
13
  ```ts
14
14
  export default {
package/dist/bundle.d.ts CHANGED
@@ -23,3 +23,12 @@ export declare function bundlePageScript(pageFile: string, projectRoot: string):
23
23
  code: string;
24
24
  names: string[];
25
25
  } | null;
26
+ export interface NativeNavigationBinding {
27
+ store: string;
28
+ tab: string;
29
+ overlay?: string;
30
+ pushActions: string[];
31
+ backActions?: string[];
32
+ }
33
+ /** 声明式 store 导航 → 原生导航桥;不修改项目的 store 实现。 */
34
+ export declare function pageNavigationBridge(binding: NativeNavigationBinding | undefined, page: string, tabs: string[]): string;
package/dist/bundle.js CHANGED
@@ -8,6 +8,7 @@
8
8
  * 策略:逐个文件尝试打包,**失败的(依赖 dexie / DOM / 浏览器 API)自动跳过**并返回告警,
9
9
  * 保证「能打的都打上,打不了的明确说明」,不静默。
10
10
  */
11
+ import { babelParse } from '@vue/compiler-sfc';
11
12
  import { existsSync, readdirSync } from 'node:fs';
12
13
  import { join } from 'node:path';
13
14
  import { buildSync } from 'esbuild';
@@ -47,6 +48,7 @@ export function bundleProjectUtils(projectRoot) {
47
48
  globalName: `__vrnUtils_${included.length}`,
48
49
  platform: 'neutral',
49
50
  target: 'es2019',
51
+ loader: { '.jpg': 'dataurl', '.jpeg': 'dataurl', '.png': 'dataurl', '.webp': 'dataurl', '.svg': 'dataurl', '.vue': 'empty' },
50
52
  // 纯类型导入会被擦除;若确实依赖运行期外部包(dexie 等)则整包失败 → 跳过该文件
51
53
  external: [],
52
54
  logLevel: 'silent',
@@ -86,8 +88,29 @@ export function bundlePageScript(pageFile, projectRoot) {
86
88
  if (!m)
87
89
  return null;
88
90
  const body = m[1];
89
- // 顶层绑定名(const/let/function);导出的就是这些
90
- const names = [...new Set([...body.matchAll(/(?:const|let|var|function)\s+([A-Za-z_$][\w$]*)/g)].map((x) => x[1]))];
91
+ const ast = babelParse(body, { sourceType: 'module', plugins: ['typescript'] });
92
+ const names = [];
93
+ const collect = (n) => {
94
+ if (!n)
95
+ return;
96
+ if (n.type === 'Identifier')
97
+ names.push(n.name);
98
+ else if (n.type === 'ObjectPattern')
99
+ n.properties.forEach((p) => collect(p.value ?? p.argument));
100
+ else if (n.type === 'ArrayPattern')
101
+ n.elements.forEach(collect);
102
+ else if (n.type === 'AssignmentPattern')
103
+ collect(n.left);
104
+ };
105
+ for (const n of ast.program.body) {
106
+ if (n.type === 'VariableDeclaration')
107
+ n.declarations.forEach((d) => collect(d.id));
108
+ else if (n.type === 'FunctionDeclaration')
109
+ collect(n.id);
110
+ else if (n.type === 'ImportDeclaration' && n.importKind !== 'type' && !/\.(vue|png|jpe?g|webp|gif)$/.test(String(n.source.value)))
111
+ n.specifiers.forEach((p) => { if (p.importKind !== 'type')
112
+ collect(p.local); });
113
+ }
91
114
  if (names.length === 0)
92
115
  return null;
93
116
  const shimDir = mkdtempSync(join(tmpdir(), 'vrn-page-'));
@@ -99,7 +122,7 @@ export function bundlePageScript(pageFile, projectRoot) {
99
122
  writeFileSync(dexieShim, DEXIE_SHIM_SOURCE);
100
123
  // 入口必须放在工程内,否则 vue/vue-router 等裸导入解析不到 node_modules
101
124
  const entry = join(projectRoot, 'src', `.vrn-page-${Date.now()}.ts`);
102
- writeFileSync(entry, `${body}\nexport { ${names.join(', ')} };\n`);
125
+ writeFileSync(entry, `import { __vrnRunMounted as __vrnMountPage } from 'vue';\n${body}\nexport { __vrnMountPage, ${names.join(', ')} };\n`);
103
126
  const routerShim = join(shimDir, 'vue-router-shim.js');
104
127
  writeFileSync(routerShim, VUE_ROUTER_SHIM_SOURCE);
105
128
  try {
@@ -111,6 +134,7 @@ export function bundlePageScript(pageFile, projectRoot) {
111
134
  globalName: '__vrnPage',
112
135
  platform: 'neutral',
113
136
  target: 'es2019',
137
+ loader: { '.jpg': 'dataurl', '.jpeg': 'dataurl', '.png': 'dataurl', '.webp': 'dataurl', '.svg': 'dataurl', '.vue': 'empty' },
114
138
  logLevel: 'silent',
115
139
  alias: {
116
140
  '@': join(projectRoot, 'src'),
@@ -124,7 +148,7 @@ export function bundlePageScript(pageFile, projectRoot) {
124
148
  // 页面导出提升为全局:Swift onTapGesture → Runtime.invoke("goDetail(...)") 裸调用
125
149
  // 需在全局作用域找到函数;__vrnPage 是 esbuild IIFE 的 globalName(模块导出对象)
126
150
  const promoted = code +
127
- `\n;(function(){var m=__vrnPage;if(!m)return;for(var k in m){try{if(typeof globalThis[k]==='undefined'){if(m[k]&&m[k].__v_isRef){Object.defineProperty(globalThis,k,{get:function(){return m[k].value;},configurable:true});}else{globalThis[k]=m[k];}}}catch(e){}}})();`;
151
+ `\n;(function(){var m=__vrnPage;if(!m)return;Object.keys(m).forEach(function(k){try{if(typeof globalThis[k]==='undefined'){if(m[k]&&(m[k].__v_isRef||m[k].__vrnRef)){Object.defineProperty(globalThis,k,{get:function(){return m[k].value;},set:function(v){m[k].value=v;},configurable:true});}else{globalThis[k]=m[k];}}}catch(e){}});})();`;
128
152
  return promoted ? { code: promoted, names } : null;
129
153
  }
130
154
  catch (e) {
@@ -141,3 +165,17 @@ export function bundlePageScript(pageFile, projectRoot) {
141
165
  }
142
166
  }
143
167
  }
168
+ /** 声明式 store 导航 → 原生导航桥;不修改项目的 store 实现。 */
169
+ export function pageNavigationBridge(binding, page, tabs) {
170
+ if (!binding)
171
+ return '';
172
+ return `\n(function(){
173
+ var spec=${JSON.stringify(binding)}, own=${JSON.stringify(page)}, tabs=${JSON.stringify(tabs)};
174
+ var object=typeof __vrnPage!=='undefined' ? __vrnPage[spec.store] : null;if(!object)return;
175
+ function route(){return (spec.overlay && object[spec.overlay]) || object[spec.tab];}
176
+ function notify(back){if(back){if(typeof __routerBack==='function')__routerBack();}else if(typeof __routerPush==='function')__routerPush(String(route()));}
177
+ (spec.pushActions||[]).forEach(function(key){var fn=object[key];if(typeof fn!=='function'||fn.__vrnNavigation)return;var wrapped=function(){var result=fn.apply(object,arguments);notify(false);return result;};wrapped.__vrnNavigation=true;object[key]=wrapped;});
178
+ (spec.backActions||[]).forEach(function(key){var fn=object[key];if(typeof fn!=='function'||fn.__vrnNavigation)return;var wrapped=function(){var result=fn.apply(object,arguments);notify(true);return result;};wrapped.__vrnNavigation=true;object[key]=wrapped;});
179
+ globalThis.__vrnSetNativeRoute=function(key){key=key||own;if(tabs.indexOf(key)>=0){object[spec.tab]=key;if(spec.overlay)object[spec.overlay]=null;}else if(spec.overlay)object[spec.overlay]=key;if(typeof __vrnSyncPage==='function')__vrnSyncPage();};
180
+ })();\n`;
181
+ }
package/dist/cli.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { readFileSync as readTabBarFile } from 'node:fs';
2
+ import { parseTabBar, loadProjectStyles } from '@dongmingzhixiu/vuexir-compiler';
1
3
  /**
2
4
  * @dongmingzhixiu/vuexir 命令实现(R-PKG:CLI 是唯一负责路径/IO/进程的层)
3
5
  *
@@ -5,7 +7,7 @@
5
7
  * vrn build ios|android [--run] # gen + (--run 时)调用 xcodegen/xcodebuild | gradle
6
8
  * vrn doctor # 环境自检
7
9
  *
8
- * 配置来源见 config.ts(vrn.config.* + CLI 覆盖)。生成器在导入前设置 VRN_NO_AUTO_RUN=1,
10
+ * 配置来源见 config.ts(vuexir.config.* + CLI 覆盖)。生成器在导入前设置 VRN_NO_AUTO_RUN=1,
9
11
  * 避免模块加载时的自动执行。
10
12
  */
11
13
  import { existsSync } from 'node:fs';
@@ -89,13 +91,17 @@ export async function generate(args, platform) {
89
91
  process.env.VRN_NO_AUTO_RUN = '1';
90
92
  const { generateIos } = await import('./gen-ios.js');
91
93
  const { generateAndroid } = await import('./gen-android.js');
94
+ const tabBar = cfg.tabBarComponent ? parseTabBar(readTabBarFile(join(cfg.projectRoot, cfg.tabBarComponent), 'utf8'), loadProjectStyles(cfg.projectRoot)) : undefined;
92
95
  if (platform === 'android') {
93
96
  if (!multiPage) {
94
97
  throw new Error('[vrn] Android 生成目前要求多页入口 router.ts(单页 App.vue 请先补 router.ts)');
95
98
  }
96
- return generateAndroid({ input: inputDir, outDirRoot: outRoot, projectName, bundleId, ...(routerFile ? { routerFile } : {}) });
99
+ return generateAndroid({ navigation: cfg.navigation, tabBar, nativeNavigationBar: cfg.nativeNavigationBar, input: routerFile ?? inputDir, outDirRoot: outRoot, projectName, bundleId, ...(routerFile ? { routerFile } : {}) });
97
100
  }
98
101
  const out = generateIos({
102
+ navigation: cfg.navigation,
103
+ tabBar,
104
+ nativeNavigationBar: cfg.nativeNavigationBar,
99
105
  input: inputDir,
100
106
  outDirRoot: outRoot,
101
107
  projectName,
@@ -197,7 +203,7 @@ export async function run(argv) {
197
203
  vrn build ios|android [入口] [--run] # 生成后可选调用平台构建
198
204
  vrn doctor # 环境自检
199
205
 
200
- 配置:<projectRoot>/vrn.config.ts(entry / outDir / projectName / bundleId)
206
+ 配置:<projectRoot>/vuexir.config.ts(entry / outDir / projectName / bundleId)
201
207
  `);
202
208
  return 0;
203
209
  }
package/dist/config.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  export interface VrnConfig {
2
+ /** 页面已有自绘导航时关闭系统导航栏。 */
3
+ nativeNavigationBar?: boolean;
4
+ /** 自绘导航外观来源:静态 {key,label,icon} 数组的 Vue 组件。 */
5
+ tabBarComponent?: string;
6
+ navigation?: import('./bundle.js').NativeNavigationBinding;
2
7
  /** Vue 工程根(默认 cwd) */
3
8
  projectRoot: string;
4
9
  /** 入口:router.ts(多页)或单个 .vue(单页),相对 projectRoot */
@@ -20,7 +25,7 @@ export interface VrnConfig {
20
25
  };
21
26
  };
22
27
  }
23
- /** 读取 vrn.config(json 直接解析;ts/mjs 用动态 import,失败则忽略并提示) */
28
+ /** 读取 vuexir.config(JSON 直接解析;ts/mjs 先用 esbuild 转译再 import) */
24
29
  export declare function loadConfig(projectRoot: string): Promise<Partial<VrnConfig>>;
25
30
  /** 合并默认值 + 配置文件 + CLI 覆盖 */
26
31
  export declare function resolveConfig(base: Partial<VrnConfig> & {
package/dist/config.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @dongmingzhixiu/vuexir 配置解析(R-PKG:CLI 只认「项目根 + vrn.config」两个事实来源)
2
+ * @dongmingzhixiu/vuexir 配置解析(R-PKG:CLI 只认「项目根 + vuexir.config」两个事实来源)
3
3
  *
4
4
  * 解析顺序(后者覆盖前者):
5
5
  * 1. 内置默认值(projectRoot = process.cwd(),outDir = <root>/native)
6
- * 2. vrn.config.ts / vrn.config.mts / vrn.config.js / vrn.config.mjs / vrn.config.json
6
+ * 2. vuexir.config.ts / .mts / .js / .mjs / .json(旧名 vuexir.config.* 仍兼容)
7
7
  * 3. 命令行参数(--out / --entry / --name / --bundle 等)
8
8
  *
9
9
  * 兼容期:未提供 vrn.config 时,若 <root>/router.ts 或 <root>/App.vue 存在,按「目录即项目」处理;
@@ -11,8 +11,8 @@
11
11
  */
12
12
  import { existsSync, readFileSync, statSync } from 'node:fs';
13
13
  import { dirname, isAbsolute, join, resolve } from 'node:path';
14
- const CONFIG_FILES = ['vuexir.config.ts', 'vuexir.config.mts', 'vuexir.config.js', 'vuexir.config.mjs', 'vuexir.config.json', 'vrn.config.ts', 'vrn.config.mts', 'vrn.config.js', 'vrn.config.mjs', 'vrn.config.json'];
15
- /** 读取 vrn.config(json 直接解析;ts/mjs 用动态 import,失败则忽略并提示) */
14
+ const CONFIG_FILES = ['vuexir.config.ts', 'vuexir.config.mts', 'vuexir.config.js', 'vuexir.config.mjs', 'vuexir.config.json', 'vuexir.config.ts', 'vrn.config.mts', 'vrn.config.js', 'vrn.config.mjs', 'vrn.config.json'];
15
+ /** 读取 vuexir.config(JSON 直接解析;ts/mjs 先用 esbuild 转译再 import) */
16
16
  export async function loadConfig(projectRoot) {
17
17
  for (const file of CONFIG_FILES) {
18
18
  const p = join(projectRoot, file);
@@ -22,6 +22,14 @@ export async function loadConfig(projectRoot) {
22
22
  if (file.endsWith('.json')) {
23
23
  return JSON.parse(readFileSync(p, 'utf-8'));
24
24
  }
25
+ // R144:配置文件是 TS 时,Node 无法直接 import → 先用 esbuild 转成 ESM 再加载
26
+ if (/\.m?ts$/.test(file)) {
27
+ const { transformSync } = await import('esbuild');
28
+ const js = transformSync(readFileSync(p, 'utf-8'), { loader: 'ts', format: 'esm', target: 'es2020' }).code;
29
+ const url = `data:text/javascript;base64,${Buffer.from(js, 'utf-8').toString('base64')}`;
30
+ const mod = (await import(url));
31
+ return mod.default ?? {};
32
+ }
25
33
  const mod = (await import(`file://${p}`));
26
34
  return mod.default ?? {};
27
35
  }
@@ -37,6 +45,9 @@ export function resolveConfig(base, overrides = {}) {
37
45
  const outDirRaw = overrides.outDir ?? base.outDir ?? 'native';
38
46
  return {
39
47
  projectRoot,
48
+ nativeNavigationBar: overrides.nativeNavigationBar ?? base.nativeNavigationBar,
49
+ tabBarComponent: overrides.tabBarComponent ?? base.tabBarComponent,
50
+ navigation: overrides.navigation ?? base.navigation,
40
51
  entry: overrides.entry ?? base.entry,
41
52
  outDir: isAbsolute(outDirRaw) ? outDirRaw : join(projectRoot, outDirRaw),
42
53
  projectName: overrides.projectName ?? base.projectName,
@@ -81,7 +92,7 @@ export function resolveEntry(cfg) {
81
92
  candidates.push(a);
82
93
  }
83
94
  throw new Error(`未找到入口(多页 src/router.ts 或单页 src/App.vue)。已尝试:\n ${candidates.join('\n ')}\n` +
84
- `可在 vrn.config.ts 中用 entry 显式指定。`);
95
+ `可在 vuexir.config.ts 中用 entry 显式指定。`);
85
96
  }
86
97
  /** 平台输出目录:<outDir>/<platform>/<ProjectName> */
87
98
  export function platformOutDir(cfg, platform, projectName) {
@@ -1,4 +1,9 @@
1
+ import type { TabBarSpec } from '@dongmingzhixiu/vuexir-compiler';
2
+ import { type NativeNavigationBinding } from './bundle.js';
1
3
  export interface GenerateOptions {
4
+ navigation?: NativeNavigationBinding;
5
+ tabBar?: TabBarSpec;
6
+ nativeNavigationBar?: boolean;
2
7
  /** 入口:样例名 / 目录 / 显式路径(默认 vuexir-demo) */
3
8
  input?: string;
4
9
  /** 产物根(默认 VRN_OUT_DIR 或仓库内 compiler/generated/android) */
@@ -13,7 +13,8 @@
13
13
  import { existsSync } from 'node:fs';
14
14
  import { join, dirname } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
- import { runTabs, resolveInput } from '@dongmingzhixiu/vuexir-compiler';
16
+ import { bundleProjectUtils, bundlePageScript, pageNavigationBridge } from './bundle.js';
17
+ import { runTabs, resolveInput, findProjectRoot, RUNTIME_BOOTSTRAP_SOURCE } from '@dongmingzhixiu/vuexir-compiler';
17
18
  import { TEMPLATE_VERSION, buildGradle } from '@dongmingzhixiu/vuexir-templates';
18
19
  // R-PKG:产物根不再假设仓库布局——VRN_OUT_DIR 优先(CLI 注入),否则回退到仓库内 compiler/generated/android
19
20
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
@@ -23,9 +24,12 @@ const SAMPLES_ROOT = process.env.VRN_SAMPLES_ROOT ?? join(ROOT, 'examples');
23
24
  /** 生成 Android(Compose)工程;返回产物目录 */
24
25
  export function generateAndroid(opts = {}) {
25
26
  const input = resolveInput(opts.input);
27
+ const root = findProjectRoot(dirname(input));
28
+ const utils = bundleProjectUtils(root);
29
+ const buildPageRuntime = (pageFile, runtime, pageKey) => RUNTIME_BOOTSTRAP_SOURCE + '\n' + utils.code + '\n' + (bundlePageScript(pageFile, root)?.code ?? '') + '\n' + runtime + '\n' + pageNavigationBridge(opts.navigation, pageKey ?? 'home', opts.tabBar?.items.map((i) => i.id) ?? []);
26
30
  // 直接给路由文件(src/router/index.ts 等)→ 用其目录 + 显式 routerFile
27
31
  if (/\.(ts|js|mts)$/.test(input) && existsSync(input)) {
28
- return runTabs(dirname(input), { routerFile: input, outDirRoot: opts.outDirRoot, projectName: opts.projectName, bundleId: opts.bundleId, renderGradle: (p, b) => buildGradle(b), templateVersion: TEMPLATE_VERSION });
32
+ return runTabs(dirname(input), { buildPageRuntime, tabBar: opts.tabBar, nativeNavigationBar: opts.nativeNavigationBar, routerFile: input, outDirRoot: opts.outDirRoot, projectName: opts.projectName, bundleId: opts.bundleId, renderGradle: (p, b) => buildGradle(b), templateVersion: TEMPLATE_VERSION });
29
33
  }
30
34
  // 目录入口:兼容 router.ts / index.ts(Vue 工程标准 src/router/index.ts)
31
35
  const routerFile = ['router.ts', 'router.js', 'index.ts', 'index.js'].find((f) => existsSync(join(input, f)));
@@ -33,6 +37,9 @@ export function generateAndroid(opts = {}) {
33
37
  throw new Error(`[vrn] ${input} 缺少 router.ts/index.ts(多页模式需要路由文件;单页请用 gen-ios 路径或补 router.ts)`);
34
38
  }
35
39
  return runTabs(input, {
40
+ buildPageRuntime,
41
+ tabBar: opts.tabBar,
42
+ nativeNavigationBar: opts.nativeNavigationBar,
36
43
  routerFile: join(input, routerFile),
37
44
  outDirRoot: opts.outDirRoot,
38
45
  projectName: opts.projectName,
package/dist/gen-ios.d.ts CHANGED
@@ -1,18 +1,9 @@
1
- /**
2
- * gen-ios.ts —— iOS 工程生成脚本(P1-lite 主入口)
3
- *
4
- * 输入:Vue SFC(默认 compiler/samples/hello/Hello.vue)或 IR JSON(.json 后缀)
5
- * 管线:Vue SFC → (vueToIr) → IR → (validateIr) → SwiftUI 源码 + 独立 Xcode 工程
6
- *
7
- * 运行:
8
- * npx vite-node compiler/scripts/gen-ios.ts # 默认 Hello.vue
9
- * npx vite-node compiler/scripts/gen-ios.ts path/My.vue # 指定 Vue 文件
10
- * npx vite-node compiler/scripts/gen-ios.ts path/screen.ir.json # 或手写 IR
11
- *
12
- * 产物:compiler/generated/ios/<ProjectName>/{App.swift, ScreenView.swift, recite.json, project.yml}
13
- * 后续:cd compiler/generated/ios/<ProjectName> && xcodegen generate && xcodebuild ...
14
- */
1
+ import { type TabBarSpec } from '@dongmingzhixiu/vuexir-compiler';
2
+ import { type NativeNavigationBinding } from './bundle.js';
15
3
  export interface GenerateIosOptions {
4
+ navigation?: NativeNavigationBinding;
5
+ tabBar?: TabBarSpec;
6
+ nativeNavigationBar?: boolean;
16
7
  /** 显式路由文件(src/router/index.ts 等非标准命名) */
17
8
  routerFile?: string;
18
9
  /** 样例名 / 目录 / 显式 .vue 或 .json 路径;'all' 批量;省略 = 默认 vuexir-demo */
package/dist/gen-ios.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { swiftVector, swiftColorExpression, generateSwiftBoxModifiers } from '@dongmingzhixiu/vuexir-compiler';
1
2
  /**
2
3
  * gen-ios.ts —— iOS 工程生成脚本(P1-lite 主入口)
3
4
  *
@@ -15,12 +16,12 @@
15
16
  import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, copyFileSync, rmSync } from 'node:fs';
16
17
  import { basename, dirname, join } from 'node:path';
17
18
  import { fileURLToPath } from 'node:url';
18
- import { bundlePageScript, bundleProjectUtils } from './bundle.js';
19
+ import { bundlePageScript, bundleProjectUtils, pageNavigationBridge } from './bundle.js';
19
20
  import { TEMPLATE_VERSION, VRN_BRIDGE_SWIFT, projectYml as PROJECT_YML, templateFiles } from '@dongmingzhixiu/vuexir-templates';
20
21
  import { parseRouter as parseRouterV2, findProjectRoot } from '@dongmingzhixiu/vuexir-compiler';
21
22
  import { validateIr } from '@dongmingzhixiu/vuexir-compiler';
22
23
  import { generateScreenSource, generateComponentSource, collectTheme, buildTheme, generateThemeSource, VRN_HELPERS_SWIFT } from '@dongmingzhixiu/vuexir-compiler';
23
- import { vueToIr, VueCompileError } from '@dongmingzhixiu/vuexir-compiler';
24
+ import { vueToIr, VueCompileError, RUNTIME_BOOTSTRAP_SOURCE, extractCssVariables, loadProjectStyles, loadProjectAssets, materializeProjectAssets } from '@dongmingzhixiu/vuexir-compiler';
24
25
  // R-PKG:产物与样例根不再假设仓库布局(CLI 通过 VRN_OUT_DIR / VRN_SAMPLES_ROOT 注入)
25
26
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
26
27
  // 安装形态下 <pkg>/dist/../../.. 不是仓库根,样例根不可靠 → 存在才用,否则退回 cwd(显式入口优先)
@@ -35,7 +36,7 @@ const SAMPLES_ROOT = (() => {
35
36
  const TOOLCHAIN_VERSION = '0.1.0';
36
37
  /** 样例根(可写副本:generateIos 可用 opts.samplesRoot 覆盖) */
37
38
  let SAMPLES_ROOT_MUT = SAMPLES_ROOT;
38
- /** CLI 注入的工程名/包名覆盖(vrn.config 的 projectName/bundleId) */
39
+ /** CLI 注入的工程名/包名覆盖(vuexir.config 的 projectName/bundleId) */
39
40
  let FORCED_PROJECT_NAME;
40
41
  let FORCED_BUNDLE_ID;
41
42
  const DEFAULT_INPUT = join(SAMPLES_ROOT_MUT, 'vuexir-demo'); // 多页目录模式默认样例
@@ -226,6 +227,11 @@ const RUNTIME_SWIFT = `final class Runtime {
226
227
  NSLog("[vrn] __nativeFetch value: %@", hfType)
227
228
  }
228
229
 
230
+ func evaluateTemplate(_ expression: String, _ bindings: [String: Any]) -> String {
231
+ guard let data = try? JSONSerialization.data(withJSONObject: bindings), let json = String(data: data, encoding: .utf8) else { return "" }
232
+ return ctx.objectForKeyedSubscript("__vrnEvalTemplate")?.call(withArguments: [expression, json])?.toString() ?? ""
233
+ }
234
+
229
235
  /// M3 注册 @State 绑定:JS stateStore.<name> 写回 → <setter>(onAppear 时由 View 注册)
230
236
  func bindState(_ name: String, _ setter: @escaping (Any) -> Void) {
231
237
  // R-THEME:ui_skin 变化 → ThemeColor.currentSkin + UserDefaults 同步 → 各视图 @AppStorage("vrn.skin") 自动重绘换肤
@@ -252,7 +258,7 @@ const RUNTIME_SWIFT = `final class Runtime {
252
258
  } else if let n = value as? Int { lit = String(n) }
253
259
  else if let b = value as? Bool { lit = b ? "true" : "false" }
254
260
  else { lit = "\\(value)" }
255
- ctx.evaluateScript("__fromSwift = true; stateStore.\\(name) = \\(lit); __fromSwift = false")
261
+ ctx.evaluateScript("__fromSwift = true; __vrnWrite(\\\"\\(name)\\\", \\(lit)); __fromSwift = false; if(typeof __vrnRecompute==='function')__vrnRecompute(); if(typeof __vrnSyncPage==='function')__vrnSyncPage();")
256
262
  }
257
263
 
258
264
  /// 调用 <script> 里声明的 handler(@click="inc" → inc();带参表达式如 inc(1) 原样执行)
@@ -465,6 +471,11 @@ function runOne(input) {
465
471
  screenId: toIdentifier(idSource),
466
472
  title: idSource,
467
473
  // R41 组件表:同目录 components/*.vue(PascalCase 文件名 → SFC 源码)
474
+ inlineComponents: true,
475
+ liveStoreObjects: true,
476
+ pageScriptBundle: true,
477
+ assets: loadProjectAssets(input, findProjectRoot(dirname(input))),
478
+ globalStyles: loadProjectStyles(findProjectRoot(dirname(input))),
468
479
  components: loadComponents(input),
469
480
  });
470
481
  screen = r.screen;
@@ -509,6 +520,7 @@ function runOne(input) {
509
520
  // 写前清空源目录(重生成不留旧组件/运行时文件)
510
521
  rmSync(srcDir, { recursive: true, force: true });
511
522
  mkdirSync(srcDir, { recursive: true });
523
+ materializeProjectAssets(screen.ui, srcDir, 'ios');
512
524
  const genWarnings = [];
513
525
  // R42:收集页面 + 组件全树颜色/字体 → Theme.swift token;codegen 引用 Theme.*
514
526
  const themeTokens = collectTheme(screen.ui, ...components.map((c) => c.ui));
@@ -560,7 +572,7 @@ function runOne(input) {
560
572
  return outDir;
561
573
  }
562
574
  /** M4a:TabView 多页 App.swift(router.ts 顶层 tab routes → 原生 TabView) */
563
- const TAB_APP_SWIFT = (projectName, tabs, details = [], rootBg = '#0a0f1e') => `import SwiftUI
575
+ const TAB_APP_SWIFT = (projectName, tabs, details = [], rootBg = '#0a0f1e', tabBar) => `import SwiftUI
564
576
  import UIKit
565
577
  import JavaScriptCore
566
578
 
@@ -574,14 +586,14 @@ ${RUNTIME_SWIFT}
574
586
  /// 由容器(WindowGroup 直接子视图)onAppear 统一注册绑定 + 触发各页 onMounted。
575
587
  struct ContentView: View {
576
588
  @State private var selected = 0
577
- ${details.length > 0 ? ` @State private var pushedRoute: String?` : ''}
589
+ ${details.length > 0 ? ` @State private var pushedRoutes: [String] = []` : ''}
578
590
  ${tabs
579
591
  .flatMap((t) => t.states.map((s) => ({ ...s, page: t.id })))
580
592
  .map((s, i) => ` @State private var p${i}_${s.name}: ${s.type === 'string' ? 'String' : s.type === 'boolean' ? 'Bool' : 'Int'} = ${s.type === 'string' ? `"${swiftStrEscape(String(s.init))}"` : s.type === 'boolean' ? (s.init ? 'true' : 'false') : typeof s.init === 'number' ? String(s.init) : '0'}`)
581
593
  .join('\n')}
582
594
 
583
595
  var body: some View {
584
- NavigationStack {
596
+ NavigationStack${details.length ? "(path: $pushedRoutes)" : ""} {
585
597
  VStack(spacing: 0) {
586
598
  ZStack(alignment: .topLeading) {
587
599
  ${tabs
@@ -597,24 +609,23 @@ ${tabs
597
609
  HStack(spacing: 0) {
598
610
  ${tabs
599
611
  .map((t, i) => ` Button(action: { selected = ${i} }) {
600
- VStack(spacing: 2) {
601
- Image(systemName: "${t.icon}")
602
- Text("${t.title}").font(.system(size: 10))
612
+ VStack(spacing: ${tabBar?.itemStyle.gap ?? 2}) {
613
+ ${tabBar?.items.find((item) => item.id.toLowerCase() === t.id.toLowerCase()) ? `_vrnTabIcon${i}` : `Image(systemName: "${t.icon}")`}
614
+ Text("${t.title}").font(.system(size: ${tabBar?.itemStyle.fontSize ?? 10}))
603
615
  }
604
- .foregroundColor(selected == ${i} ? Color(red: 217/255.0, green: 179/255.0, blue: 108/255.0) : Color(red: 168/255.0, green: 174/255.0, blue: 194/255.0))
616
+ .foregroundColor(selected == ${i} ? ${tabBar ? swiftColorExpression(tabBar.activeStyle.color ?? '#f3dca0') : 'Color(red: 217/255.0, green: 179/255.0, blue: 108/255.0)'} : ${tabBar ? swiftColorExpression(tabBar.itemStyle.color ?? '#5b647e') : 'Color(red: 168/255.0, green: 174/255.0, blue: 194/255.0)'})
605
617
  .frame(maxWidth: .infinity)
618
+ ${tabBar ? '.padding(.vertical, 6)' : ''}
606
619
  }
607
620
  .buttonStyle(.plain)`)
608
621
  .join('\n')}
609
622
  }
610
- .frame(height: 56)
611
- .background(ThemeColor.color("surface-2"))
612
- .overlay(Divider(), alignment: .top)
623
+ ${tabBar ? `.padding(.vertical, 8).padding(.horizontal, 10) ${generateSwiftBoxModifiers(tabBar.style).join(' ')} .frame(maxWidth: ${tabBar.maxWidth ?? 10000}).padding(.horizontal, ${tabBar.widthInset / 2}).padding(.bottom, ${tabBar.bottom})` : '.frame(height: 56).background(ThemeColor.color("surface-2")).overlay(Divider(), alignment: .top)'}
613
624
  }
614
625
  // R68:容器背景兜底——页面内容不足或底部安全区不露白(R-THEME:默认深色 → ThemeColor 皮肤 bg)
615
626
  .background(${rootBg === '#0a0f1e' || rootBg === '#0a0F1E' || !rootBg ? 'ThemeColor.color("bg")' : swiftColorExpr(rootBg)})
616
627
  ${details.length > 0
617
- ? ` .navigationDestination(item: $pushedRoute) { route in
628
+ ? ` .navigationDestination(for: String.self) { route in
618
629
  Group {
619
630
  ${details
620
631
  .map((d) => ` if route == "${d.path}" { ${d.view}() }`)
@@ -632,13 +643,25 @@ ${tabs
632
643
  .filter((t) => t.onMounted)
633
644
  .map((t) => ` Runtime.forPage("${t.id}").invoke("__onMounted")`)
634
645
  .join('\n')}
635
- ${details.length > 0
636
- ? ` Runtime.routerPushHandler = { path in pushedRoute = path }
637
- Runtime.routerBackHandler = { pushedRoute = nil }`
638
- : ''}
646
+ Runtime.routerPushHandler = { route in
647
+ switch route {
648
+ ${tabs.map((t, i) => ` case "${t.id.toLowerCase()}": selected = ${i}${details.length ? '; pushedRoutes = []' : ''}`).join('\n')}
649
+ ${details.map((d) => ` case "${d.id}", "${d.path}": if pushedRoutes.last != "${d.path}" { pushedRoutes.append("${d.path}") }`).join('\n')}
650
+ default: break
651
+ }
652
+ }
653
+ ${details.length ? ' Runtime.routerBackHandler = { if !pushedRoutes.isEmpty { pushedRoutes.removeLast() } }' : ''}
639
654
  }
640
655
  }
641
656
  }
657
+ ${tabs.map((t, i) => {
658
+ const item = tabBar?.items.find((item) => item.id.toLowerCase() === t.id.toLowerCase());
659
+ if (!item)
660
+ return '';
661
+ const tint = `selected == ${i} ? ThemeColor.color("gold-2") : ThemeColor.color("text-3")`;
662
+ const code = swiftVector({ vector: item.icon, color: '__vrnTabTint' }, (color) => color === '__vrnTabTint' ? tint : swiftColorExpr(color));
663
+ return ` private var _vrnTabIcon${i}: some View {\n ${code.split('\n').join('\n ')}.frame(width: 22, height: 22)\n }`;
664
+ }).join('\n')}
642
665
  }
643
666
 
644
667
  @main
@@ -686,14 +709,15 @@ const TAB_ICONS = ['house.fill', 'book.fill', 'gearshape.fill', 'person.fill', '
686
709
  /** 加载项目全局 CSS 变量(tokens.css 等 :root 定义)→ 供 parseColor 解析 var(--xxx) */
687
710
  function loadCssVars(appDir) {
688
711
  const root = findProjectRoot(appDir); // appDir 可能是 src/router 等子目录 → 回退到项目根
689
- const map = new Map();
712
+ const map = new Map(Object.entries(extractCssVariables(loadProjectStyles(root))));
690
713
  const roots = [join(root, 'src/styles/tokens.css'), join(root, 'src/tokens.css'), join(root, 'styles/tokens.css'), join(root, 'src/styles/app.css')];
691
714
  for (const p of roots) {
692
715
  if (!existsSync(p))
693
716
  continue;
694
717
  const css = readFileSync(p, 'utf-8');
695
718
  for (const m of css.matchAll(/(--[\w-]+)\s*:\s*([^;{}]+);/g)) {
696
- map.set(m[1], m[2].trim());
719
+ if (!map.has(m[1]))
720
+ map.set(m[1], m[2].trim());
697
721
  }
698
722
  }
699
723
  return map;
@@ -715,7 +739,7 @@ function injectSkinBridge(js) {
715
739
  */
716
740
  function loadSkinVars(appDir) {
717
741
  const root = findProjectRoot(appDir);
718
- const skins = new Map();
742
+ const skins = new Map([['warm', new Map(Object.entries(extractCssVariables(loadProjectStyles(root))))]]);
719
743
  const roots = [join(root, 'src/styles/tokens.css'), join(root, 'src/tokens.css'), join(root, 'styles/tokens.css')];
720
744
  for (const p of roots) {
721
745
  if (!existsSync(p))
@@ -806,8 +830,10 @@ function buildThemeColorsSwift(skins) {
806
830
  if (c)
807
831
  entries.push(` "${k.slice(2)}": ${c}`);
808
832
  }
809
- if (entries.length === 0)
833
+ if (entries.length === 0) {
834
+ lines.push(` private static let ${skin === 'warm' ? 'warmMap' : `${skin}Map`}: [String: Color] = [:]`);
810
835
  continue;
836
+ }
811
837
  lines.push(` private static let ${skin === 'warm' ? 'warmMap' : `${skin}Map`}: [String: Color] = [`);
812
838
  lines.push(entries.join(',\n'));
813
839
  lines.push(' ]');
@@ -854,10 +880,17 @@ function runTabs(appDir, opts = {}) {
854
880
  const r = vueToIr(src, {
855
881
  screenId: pageId,
856
882
  title: tab.title,
883
+ inlineComponents: true,
884
+ liveStoreObjects: true,
885
+ pageScriptBundle: true,
886
+ assets: loadProjectAssets(tab.file, findProjectRoot(dirname(tab.file))),
887
+ globalStyles: loadProjectStyles(findProjectRoot(dirname(tab.file))),
857
888
  components: loadComponents(tab.file),
858
889
  storeSources: loadStoreSources(tab.file),
859
890
  allowUnknownComponents: true,
860
891
  });
892
+ if (opts.nativeNavigationBar === false)
893
+ r.screen.navBar = { ...r.screen.navBar, hidden: true };
861
894
  if (r.warnings.length > 0) {
862
895
  console.log(`[gen-ios] 编译提示(${tab.id},${r.warnings.length} 条):`);
863
896
  for (const w of r.warnings)
@@ -870,6 +903,7 @@ function runTabs(appDir, opts = {}) {
870
903
  console.error(` ${i.path}: ${i.message}`);
871
904
  process.exit(1);
872
905
  }
906
+ materializeProjectAssets(r.screen.ui, srcDir, 'ios');
873
907
  themePieces.push({ ui: r.screen.ui, comps: r.components ?? [] });
874
908
  // 页面 View 源:运行时引用 per-page(JS 上下文隔离)
875
909
  // M4b:状态提升到容器(页面用 @Binding、无 onAppear;绑定+onMounted 由容器 onAppear 统一驱动——
@@ -891,7 +925,7 @@ function runTabs(appDir, opts = {}) {
891
925
  const pageBundle = bundlePageScript(tab.file, findProjectRoot(appDir));
892
926
  if (pageBundle)
893
927
  console.log(`[gen-ios] 页面脚本打包:${tab.id}(${pageBundle.names.length} 个绑定)`);
894
- writeFileSync(join(srcDir, `runtime_${pageId}.js`), injectSkinBridge(utilsBundle.code + '\n' + (pageBundle?.code ?? '') + '\n' + (r.runtime ?? '')));
928
+ writeFileSync(join(srcDir, `runtime_${pageId}.js`), injectSkinBridge(RUNTIME_BOOTSTRAP_SOURCE + '\n' + utilsBundle.code + '\n' + (pageBundle?.code ?? '') + '\n' + (r.runtime ?? '') + '\n' + pageNavigationBridge(opts.navigation, tab.id, tabs.map((t) => t.id))));
895
929
  pageFiles.push(`${pageId}View.swift`);
896
930
  pageRuntimes.push({ id: pageId, src: r.runtime ?? '' });
897
931
  pageStates.push({
@@ -908,7 +942,7 @@ function runTabs(appDir, opts = {}) {
908
942
  })),
909
943
  onMounted: r.screen.onMounted !== undefined,
910
944
  navBar: r.screen.navBar,
911
- rootBg: typeof r.screen.ui.props?.background === 'string' ? r.screen.ui.props.background : undefined,
945
+ rootBg: typeof r.screen.ui.props?.background === 'string' ? r.screen.ui.props.background : r.screen.ui.props?.gradient?.stops.at(-1)?.color,
912
946
  });
913
947
  console.log(`[gen-ios] - ${pageId}View.swift (${swift.length} bytes) · tab「${tab.title}」`);
914
948
  }
@@ -926,10 +960,17 @@ function runTabs(appDir, opts = {}) {
926
960
  const r = vueToIr(src, {
927
961
  screenId: `${pageId}Detail`,
928
962
  title: d.title,
963
+ inlineComponents: true,
964
+ liveStoreObjects: true,
965
+ pageScriptBundle: true,
966
+ assets: loadProjectAssets(d.file, findProjectRoot(dirname(d.file))),
967
+ globalStyles: loadProjectStyles(findProjectRoot(dirname(d.file))),
929
968
  components: loadComponents(d.file),
930
969
  storeSources: loadStoreSources(d.file),
931
970
  allowUnknownComponents: true,
932
971
  });
972
+ if (opts.nativeNavigationBar === false)
973
+ r.screen.navBar = { ...r.screen.navBar, hidden: true };
933
974
  for (const w of r.warnings)
934
975
  console.log(`[gen-ios] 编译提示(${d.id},push 页): ${w}`);
935
976
  const v = validateIr(r.screen);
@@ -938,6 +979,7 @@ function runTabs(appDir, opts = {}) {
938
979
  console.error(` ${i.path}: ${i.message}`);
939
980
  process.exit(1);
940
981
  }
982
+ materializeProjectAssets(r.screen.ui, srcDir, 'ios');
941
983
  themePieces.push({ ui: r.screen.ui, comps: r.components ?? [] });
942
984
  const genWarnings = [];
943
985
  const swift = generateScreenSource(r.screen, {
@@ -951,7 +993,7 @@ function runTabs(appDir, opts = {}) {
951
993
  for (const w of genWarnings)
952
994
  console.log(`[gen-ios] 生成提示(${d.id},push 页): ${w}`);
953
995
  writeFileSync(join(srcDir, viewFile), swift.replace(/Text\("(_vrn[EAC][0-9]+)"\)/g, 'Text($1)'));
954
- writeFileSync(join(srcDir, `runtime_${pageId}Detail.js`), injectSkinBridge(utilsBundle.code + '\n' + (r.runtime ?? '')));
996
+ writeFileSync(join(srcDir, `runtime_${pageId}Detail.js`), injectSkinBridge(RUNTIME_BOOTSTRAP_SOURCE + '\n' + utilsBundle.code + '\n' + (bundlePageScript(d.file, findProjectRoot(appDir))?.code ?? '') + '\n' + (r.runtime ?? '') + '\n' + pageNavigationBridge(opts.navigation, d.id, tabs.map((t) => t.id))));
955
997
  pageFiles.push(viewFile);
956
998
  pageRuntimes.push({ id: `${pageId}Detail`, src: r.runtime ?? '' });
957
999
  detailPages.push({ id: d.id, title: d.title, view: viewName, path: d.path });
@@ -985,7 +1027,7 @@ function runTabs(appDir, opts = {}) {
985
1027
  console.log(`[gen-ios] - ${comp.name}View.swift (${src.length} bytes) · R41 component view`);
986
1028
  }
987
1029
  }
988
- writeFileSync(join(srcDir, 'App.swift'), TAB_APP_SWIFT(projectName, pageStates, detailPages, pageStates[0]?.rootBg ?? '#0a0f1e'));
1030
+ writeFileSync(join(srcDir, 'App.swift'), TAB_APP_SWIFT(projectName, pageStates, detailPages, pageStates[0]?.rootBg ?? '#0a0f1e', opts.tabBar));
989
1031
  writeFileSync(join(outDir, 'project.yml'), PROJECT_YML(projectName, bundleId));
990
1032
  writeManifest(outDir, 'ios', projectName, bundleId, [
991
1033
  ...pageFiles,
@@ -1028,7 +1070,7 @@ export function generateIos(opts = {}) {
1028
1070
  if (opts.bundleId)
1029
1071
  FORCED_BUNDLE_ID = opts.bundleId;
1030
1072
  if (opts.routerFile)
1031
- return runTabs(dirname(opts.routerFile), { routerFile: opts.routerFile });
1073
+ return runTabs(dirname(opts.routerFile), { routerFile: opts.routerFile, nativeNavigationBar: opts.nativeNavigationBar, tabBar: opts.tabBar, navigation: opts.navigation });
1032
1074
  return runMain(opts.input);
1033
1075
  }
1034
1076
  /** P2:写 .vrn/manifest.json(工具链/模板版本 + 文件清单)并对齐陈旧文件 */
package/dist/shims.d.ts CHANGED
@@ -16,12 +16,12 @@ export declare const DEXIE_SHIM_SOURCE = "\nfunction __vrnDbKey(db, table) { ret
16
16
  * · onMounted → 收集回调,由宿主在页面挂载后调用
17
17
  * · watch → 简单轮询式比较(同一个事件循环 tick 内触发)
18
18
  */
19
- export declare const VUE_SHIM_SOURCE = "\nvar __vrnMounted = [];\nexport function ref(v) {\n return { __vrnRef: true, value: v };\n}\nexport function shallowRef(v) { return ref(v); }\nexport function reactive(obj) { return obj; }\nexport function readonly(obj) { return obj; }\nexport function computed(fn) {\n // R120\uFF1A\u4E0D\u505A\u7F13\u5B58\u2014\u2014\u539F\u751F\u7AEF\u6CA1\u6709\u4F9D\u8D56\u8FFD\u8E2A\uFF0Cstore.init() \u662F\u5F02\u6B65\u7684\uFF0C\n // \u9996\u6B21\u6C42\u503C\uFF08\u6570\u636E\u672A\u5230\uFF09\u4F1A\u88AB\u6C38\u4E45\u7F13\u5B58\u6210\u7A7A\u503C\u3002\u6BCF\u6B21\u8BFB\u90FD\u91CD\u7B97\uFF0C\u914D\u5408 tick \u540C\u6B65\u5373\u53EF\u3002\n return {\n __vrnComputed: true,\n get value() {\n try {\n return fn();\n } catch (e) {\n return undefined;\n }\n },\n set value(_v) {},\n };\n}\nexport function onMounted(fn) { __vrnMounted.push(fn); }\nexport function onUnmounted() {}\nexport function onBeforeUnmount() {}\nexport function watch(source, cb) {\n var last = typeof source === \"function\" ? source() : source && source.value;\n return setInterval(function () {\n var cur = typeof source === \"function\" ? source() : source && source.value;\n if (cur !== last) { var prev = last; last = cur; cb(cur, prev); }\n }, 16);\n}\nexport function nextTick(fn) { return Promise.resolve().then(fn); }\nexport function toRefs(obj) {\n var out = {};\n Object.keys(obj || {}).forEach(function (k) { out[k] = ref(obj[k]); });\n return out;\n}\nexport function unref(v) { return v && v.__vrnRef ? v.value : v; }\nexport function isRef(v) { return !!(v && v.__vrnRef); }\nexport function __vrnRunMounted() {\n var list = __vrnMounted.slice();\n __vrnMounted.length = 0;\n list.forEach(function (fn) { try { fn(); } catch (e) {} });\n}\nexport default {\n ref: ref, shallowRef: shallowRef, reactive: reactive, readonly: readonly, computed: computed,\n onMounted: onMounted, onUnmounted: onUnmounted, onBeforeUnmount: onBeforeUnmount,\n watch: watch, nextTick: nextTick, toRefs: toRefs, unref: unref, isRef: isRef,\n};\n";
19
+ export declare const VUE_SHIM_SOURCE = "\nvar __vrnMounted = [];\nexport function ref(v) {\n return { __v_isRef: true, __vrnRef: true, value: v };\n}\nexport function shallowRef(v) { return ref(v); }\nexport function reactive(obj) { return obj; }\nexport function readonly(obj) { return obj; }\nexport function computed(fn) {\n // R120\uFF1A\u4E0D\u505A\u7F13\u5B58\u2014\u2014\u539F\u751F\u7AEF\u6CA1\u6709\u4F9D\u8D56\u8FFD\u8E2A\uFF0Cstore.init() \u662F\u5F02\u6B65\u7684\uFF0C\n // \u9996\u6B21\u6C42\u503C\uFF08\u6570\u636E\u672A\u5230\uFF09\u4F1A\u88AB\u6C38\u4E45\u7F13\u5B58\u6210\u7A7A\u503C\u3002\u6BCF\u6B21\u8BFB\u90FD\u91CD\u7B97\uFF0C\u914D\u5408 tick \u540C\u6B65\u5373\u53EF\u3002\n return {\n __v_isRef: true, __vrnRef: true, __vrnComputed: true,\n get value() {\n try {\n return fn();\n } catch (e) {\n return undefined;\n }\n },\n set value(_v) {},\n };\n}\nexport function onMounted(fn) { __vrnMounted.push(fn); }\nexport function onUnmounted() {}\nexport function onBeforeUnmount() {}\nexport function watch(source, cb) {\n var last = typeof source === \"function\" ? source() : source && source.value;\n return setInterval(function () {\n var cur = typeof source === \"function\" ? source() : source && source.value;\n if (cur !== last) { var prev = last; last = cur; cb(cur, prev); }\n }, 16);\n}\nexport function nextTick(fn) { return Promise.resolve().then(fn); }\nexport function toRefs(obj) {\n var out = {};\n Object.keys(obj || {}).forEach(function (k) { out[k] = ref(obj[k]); });\n return out;\n}\nexport function unref(v) { return v && v.__vrnRef ? v.value : v; }\nexport function isRef(v) { return !!(v && v.__vrnRef); }\nexport function __vrnRunMounted() {\n var list = __vrnMounted.slice();\n __vrnMounted.length = 0;\n list.forEach(function (fn) { try { fn(); } catch (e) {} });\n}\nexport default {\n ref: ref, shallowRef: shallowRef, reactive: reactive, readonly: readonly, computed: computed,\n onMounted: onMounted, onUnmounted: onUnmounted, onBeforeUnmount: onBeforeUnmount,\n watch: watch, nextTick: nextTick, toRefs: toRefs, unref: unref, isRef: isRef,\n};\n";
20
20
  /**
21
21
  * R100 P4:`pinia` 的最小 shim —— 让 `stores/*.ts` 的 defineStore 能在运行时可加载。
22
22
  * 支持 options 写法(state/getters/actions)与 setup 写法(函数返回 state 与 action)。
23
23
  * 单例语义:同一 id 只创建一次(与 Pinia 一致),跨页面共享。
24
24
  */
25
- export declare const PINIA_SHIM_SOURCE = "\nvar __vrnStores = (typeof globalThis !== \"undefined\" && globalThis.__vrnStores) || (globalThis.__vrnStores = {});\nfunction __vrnBind(store, obj) {\n Object.keys(obj || {}).forEach(function (k) {\n var v = obj[k];\n if (typeof v === \"function\") {\n // R119\uFF1Aaction \u5185\u90E8\u5F02\u5E38\u5728\u539F\u751F\u7AEF\u5B8C\u5168\u4E0D\u53EF\u89C1 \u2192 \u8BB0\u5F55\u5230\u5168\u5C40\u9519\u8BEF\u8868\u5E76 toast \u51FA\u6765\n store[k] = function () {\n var self = this;\n var rec = function (e) {\n try {\n var errs = (globalThis.__vrnErrs = globalThis.__vrnErrs || []);\n errs.push(k + \": \" + String((e && e.message) || e));\n if (typeof bridge !== \"undefined\" && bridge && bridge.toast) bridge.toast(\"E(\" + k + \"): \" + String((e && e.message) || e).slice(0, 40));\n } catch (_) {}\n };\n try {\n var r = v.apply(self, arguments);\n if (r && typeof r.then === \"function\") r.then(null, rec);\n return r;\n } catch (e) {\n rec(e);\n throw e;\n }\n };\n } else store[k] = v;\n });\n}\nexport function defineStore(id, def) {\n return function useStore() {\n if (__vrnStores[id]) return __vrnStores[id];\n var store = {};\n if (typeof def === \"function\") {\n var ret = def();\n __vrnBind(store, ret);\n Object.keys(ret || {}).forEach(function (k) {\n if (ret[k] && ret[k].__vrnRef) store[k] = ret[k].value;\n });\n } else {\n var state = typeof def.state === \"function\" ? def.state() : def.state || {};\n Object.keys(state).forEach(function (k) { store[k] = state[k]; });\n Object.keys(def.getters || {}).forEach(function (k) {\n var g = def.getters[k];\n Object.defineProperty(store, k, {\n enumerable: true,\n get: function () {\n try {\n return g.call(store, store);\n } catch (e) {\n // R124\uFF1Agetter \u5F02\u5E38\u5728\u539F\u751F\u7AEF\u4E0D\u53EF\u89C1 \u2192 \u8BB0\u5F55\u5E76 toast \u4E00\u6B21\n try {\n if (!globalThis.__vrnGetErr) {\n globalThis.__vrnGetErr = {};\n }\n if (!globalThis.__vrnGetErr[k]) {\n globalThis.__vrnGetErr[k] = 1;\n if (typeof bridge !== \"undefined\" && bridge && bridge.toast) {\n bridge.toast(\"G(\" + k + \"): \" + String((e && e.message) || e).slice(0, 36));\n }\n }\n } catch (_) {}\n return undefined;\n }\n },\n });\n });\n __vrnBind(store, def.actions);\n }\n __vrnStores[id] = store;\n return store;\n };\n}\nexport function createPinia() { return {}; }\nexport function storeToRefs(store) {\n var out = {};\n Object.keys(store || {}).forEach(function (k) {\n var v = store[k];\n if (typeof v !== \"function\") out[k] = { __vrnRef: true, get value() { return store[k]; }, set value(x) { store[k] = x; } };\n });\n return out;\n}\nexport default { defineStore: defineStore, createPinia: createPinia, storeToRefs: storeToRefs };\n";
25
+ export declare const PINIA_SHIM_SOURCE = "\nvar __vrnStores = (typeof globalThis !== \"undefined\" && globalThis.__vrnStores) || (globalThis.__vrnStores = {});\nfunction __vrnBind(store, obj) {\n Object.keys(obj || {}).forEach(function (k) {\n var v = obj[k];\n if (typeof v === \"function\") {\n // R119\uFF1Aaction \u5185\u90E8\u5F02\u5E38\u5728\u539F\u751F\u7AEF\u5B8C\u5168\u4E0D\u53EF\u89C1 \u2192 \u8BB0\u5F55\u5230\u5168\u5C40\u9519\u8BEF\u8868\u5E76 toast \u51FA\u6765\n store[k] = function () {\n var self = this;\n var rec = function (e) {\n try {\n var errs = (globalThis.__vrnErrs = globalThis.__vrnErrs || []);\n errs.push(k + \": \" + String((e && e.message) || e));\n if (typeof bridge !== \"undefined\" && bridge && bridge.toast) bridge.toast(\"E(\" + k + \"): \" + String((e && e.message) || e).slice(0, 40));\n } catch (_) {}\n };\n try {\n var r = v.apply(self, arguments);\n if (r && typeof r.then === \"function\") r.then(null, rec);\n return r;\n } catch (e) {\n rec(e);\n throw e;\n }\n };\n } else store[k] = v;\n });\n}\nexport function defineStore(id, def) {\n return function useStore() {\n if (__vrnStores[id]) return __vrnStores[id];\n var store = {};\n if (typeof def === \"function\") {\n var ret = def();\n __vrnBind(store, ret);\n Object.keys(ret || {}).forEach(function (k) {\n if (ret[k] && (ret[k].__vrnRef || ret[k].__v_isRef)) Object.defineProperty(store, k, {\n configurable:true, enumerable:true,\n get:function(){return ret[k].value;}, set:function(v){ret[k].value=v;}\n });\n });\n } else {\n var state = typeof def.state === \"function\" ? def.state() : def.state || {};\n Object.keys(state).forEach(function (k) { store[k] = state[k]; });\n Object.keys(def.getters || {}).forEach(function (k) {\n var g = def.getters[k];\n Object.defineProperty(store, k, {\n enumerable: true,\n get: function () {\n try {\n return g.call(store, store);\n } catch (e) {\n // R124\uFF1Agetter \u5F02\u5E38\u5728\u539F\u751F\u7AEF\u4E0D\u53EF\u89C1 \u2192 \u8BB0\u5F55\u5E76 toast \u4E00\u6B21\n try {\n if (!globalThis.__vrnGetErr) {\n globalThis.__vrnGetErr = {};\n }\n if (!globalThis.__vrnGetErr[k]) {\n globalThis.__vrnGetErr[k] = 1;\n if (typeof bridge !== \"undefined\" && bridge && bridge.toast) {\n bridge.toast(\"G(\" + k + \"): \" + String((e && e.message) || e).slice(0, 36));\n }\n }\n } catch (_) {}\n return undefined;\n }\n },\n });\n });\n __vrnBind(store, def.actions);\n }\n __vrnStores[id] = store;\n return store;\n };\n}\nexport function createPinia() { return {}; }\nexport function storeToRefs(store) {\n var out = {};\n Object.keys(store || {}).forEach(function (k) {\n var v = store[k];\n if (typeof v !== \"function\") out[k] = { __vrnRef: true, get value() { return store[k]; }, set value(x) { store[k] = x; } };\n });\n return out;\n}\nexport default { defineStore: defineStore, createPinia: createPinia, storeToRefs: storeToRefs };\n";
26
26
  /** R118:vue-router 最小 shim(页面里的 useRoute/useRouter 在原生端由编译器接管导航) */
27
27
  export declare const VUE_ROUTER_SHIM_SOURCE = "\nexport function useRoute() {\n return {\n params: (typeof globalThis.__vrnRouteParams === \"object\" && globalThis.__vrnRouteParams) || {},\n query: {},\n path: (globalThis.__vrnRoutePath || \"/\"),\n name: \"\",\n };\n}\nexport function useRouter() {\n return {\n push: function (p) { try { if (typeof __routerPush === \"function\") __routerPush(typeof p === \"string\" ? p : (p && (p.name || p.path)) || \"\"); } catch (e) {} },\n replace: function (p) { return this.push(p); },\n back: function () { try { if (typeof __routerBack === \"function\") __routerBack(); } catch (e) {} },\n };\n}\nexport function createRouter(o) { return { options: o, install: function () {} }; }\nexport function createWebHashHistory() { return {}; }\nexport function createWebHistory() { return {}; }\nexport default { useRoute: useRoute, useRouter: useRouter, createRouter: createRouter };\n";
package/dist/shims.js CHANGED
@@ -91,7 +91,7 @@ export default class Dexie {
91
91
  export const VUE_SHIM_SOURCE = `
92
92
  var __vrnMounted = [];
93
93
  export function ref(v) {
94
- return { __vrnRef: true, value: v };
94
+ return { __v_isRef: true, __vrnRef: true, value: v };
95
95
  }
96
96
  export function shallowRef(v) { return ref(v); }
97
97
  export function reactive(obj) { return obj; }
@@ -100,7 +100,7 @@ export function computed(fn) {
100
100
  // R120:不做缓存——原生端没有依赖追踪,store.init() 是异步的,
101
101
  // 首次求值(数据未到)会被永久缓存成空值。每次读都重算,配合 tick 同步即可。
102
102
  return {
103
- __vrnComputed: true,
103
+ __v_isRef: true, __vrnRef: true, __vrnComputed: true,
104
104
  get value() {
105
105
  try {
106
106
  return fn();
@@ -181,7 +181,10 @@ export function defineStore(id, def) {
181
181
  var ret = def();
182
182
  __vrnBind(store, ret);
183
183
  Object.keys(ret || {}).forEach(function (k) {
184
- if (ret[k] && ret[k].__vrnRef) store[k] = ret[k].value;
184
+ if (ret[k] && (ret[k].__vrnRef || ret[k].__v_isRef)) Object.defineProperty(store, k, {
185
+ configurable:true, enumerable:true,
186
+ get:function(){return ret[k].value;}, set:function(v){ret[k].value=v;}
187
+ });
185
188
  });
186
189
  } else {
187
190
  var state = typeof def.state === "function" ? def.state() : def.state || {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongmingzhixiu/vuexir",
3
- "version": "0.5.7",
3
+ "version": "0.7.0",
4
4
  "description": "vrn 命令行:build ios|android、preview、doctor",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,8 +21,10 @@
21
21
  "LICENSE"
22
22
  ],
23
23
  "dependencies": {
24
- "@dongmingzhixiu/vuexir-compiler": "^0.5.4",
25
- "@dongmingzhixiu/vuexir-templates": "^0.5.4"
24
+ "@dongmingzhixiu/vuexir-compiler": "^0.7.0",
25
+ "@dongmingzhixiu/vuexir-templates": "^0.7.0",
26
+ "@vue/compiler-sfc": "^3.5.43",
27
+ "esbuild": "^0.21.5"
26
28
  },
27
29
  "engines": {
28
30
  "node": ">=18"