@dongmingzhixiu/vuexir 0.5.4 → 0.5.5

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/bundle.js CHANGED
@@ -121,7 +121,11 @@ export function bundlePageScript(pageFile, projectRoot) {
121
121
  },
122
122
  });
123
123
  const code = out.outputFiles?.[0]?.text ?? '';
124
- return code ? { code, names } : null;
124
+ // 页面导出提升为全局:Swift onTapGesture → Runtime.invoke("goDetail(...)") 裸调用
125
+ // 需在全局作用域找到函数;__vrnPage 是 esbuild IIFE 的 globalName(模块导出对象)
126
+ 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){}}})();`;
128
+ return promoted ? { code: promoted, names } : null;
125
129
  }
126
130
  catch (e) {
127
131
  if (process.env.VRN_DEBUG_BUNDLE)
package/dist/cli.js CHANGED
@@ -65,7 +65,23 @@ export async function generate(args, platform) {
65
65
  const resolved = explicit ? { entry: explicit.entry, multiPage: explicit.multiPage } : resolveEntry(cfg);
66
66
  const { entry, multiPage } = resolved;
67
67
  /** 传给生成器的入口(约定:路由文件路径 / 含 router.ts 的目录 / 含 App.vue 的目录) */
68
- const inputDir = explicit ? explicit.dir : multiPage ? entry : dirname(entry);
68
+ // R142:多页入口统一解析成「具体的路由文件」,避免目录里没有 router.ts 时退回样例模式
69
+ let routerFile;
70
+ if (multiPage) {
71
+ if (/\.(ts|js|mts|mjs)$/.test(entry) && existsSync(entry)) {
72
+ routerFile = entry;
73
+ }
74
+ else {
75
+ for (const cand of ['router.ts', 'index.ts', 'router/index.ts', 'router.js']) {
76
+ const full = join(entry, cand);
77
+ if (existsSync(full)) {
78
+ routerFile = full;
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ const inputDir = routerFile ? dirname(routerFile) : explicit ? explicit.dir : multiPage ? entry : dirname(entry);
69
85
  const projectName = cfg.projectName ?? deriveProjectName(cfg, entry);
70
86
  const bundleId = cfg.bundleId ?? `com.vrn.${projectName.toLowerCase()}`;
71
87
  const outRoot = platformOutDir(cfg, platform, '').replace(/\/$/, '');
@@ -77,14 +93,14 @@ export async function generate(args, platform) {
77
93
  if (!multiPage) {
78
94
  throw new Error('[vrn] Android 生成目前要求多页入口 router.ts(单页 App.vue 请先补 router.ts)');
79
95
  }
80
- return generateAndroid({ input: inputDir, outDirRoot: outRoot, projectName, bundleId });
96
+ return generateAndroid({ input: inputDir, outDirRoot: outRoot, projectName, bundleId, ...(routerFile ? { routerFile } : {}) });
81
97
  }
82
98
  const out = generateIos({
83
99
  input: inputDir,
84
100
  outDirRoot: outRoot,
85
101
  projectName,
86
102
  bundleId,
87
- ...(explicit && explicit.multiPage ? { routerFile: explicit.entry } : {}),
103
+ ...(routerFile ? { routerFile } : {}),
88
104
  });
89
105
  if (!out)
90
106
  throw new Error('[vrn] iOS 生成未返回产物目录');
package/dist/config.js CHANGED
@@ -11,7 +11,7 @@
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 = ['vrn.config.ts', 'vrn.config.mts', 'vrn.config.js', 'vrn.config.mjs', 'vrn.config.json'];
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
15
  /** 读取 vrn.config(json 直接解析;ts/mjs 用动态 import,失败则忽略并提示) */
16
16
  export async function loadConfig(projectRoot) {
17
17
  for (const file of CONFIG_FILES) {
@@ -59,8 +59,9 @@ export function resolveEntry(cfg) {
59
59
  return { entry: a, multiPage: false };
60
60
  throw new Error(`entry 目录内没有 router.ts / App.vue:${p}`);
61
61
  }
62
+ // R142:任何 .ts/.js 入口都按「路由表」处理(src/router/index.ts 这类命名很常见)
62
63
  if (existsSync(p))
63
- return { entry: p, multiPage: p.endsWith('router.ts') || p.endsWith('router.js') };
64
+ return { entry: p, multiPage: /\.(ts|js|mts|mjs)$/.test(p) };
64
65
  throw new Error(`vrn.config.entry 指向的文件不存在:${p}`);
65
66
  }
66
67
  const routerCandidates = [
package/dist/gen-ios.js CHANGED
@@ -254,6 +254,17 @@ const RUNTIME_SWIFT = `final class Runtime {
254
254
  if name.contains("(") { ctx.evaluateScript(name) }
255
255
  else { ctx.evaluateScript("\\(name)()") }
256
256
  }
257
+
258
+ /// R-LOOPITEM:v-for 项点击时把当前 item 注入 JS 全局 __vrnLoop,
259
+ /// invoke 表达式里的循环变量引用(goDetail(row.item.id))才能求值
260
+ func setLoopItem(_ value: Any) {
261
+ guard let json = try? JSONSerialization.data(withJSONObject: value),
262
+ let s = String(data: json, encoding: .utf8) else { return }
263
+ let escaped = s.replacingOccurrences(of: "\\\\", with: "\\\\\\\\")
264
+ .replacingOccurrences(of: "\\"", with: "\\\\\\"")
265
+ .replacingOccurrences(of: "\\n", with: "\\\\n")
266
+ ctx.evaluateScript("__vrnLoop = JSON.parse(\\"\\(escaped)\\")")
267
+ }
257
268
  }
258
269
  `;
259
270
  const APP_SWIFT = (projectName, viewName, screenTitle) => `import SwiftUI
@@ -496,7 +507,8 @@ function runOne(input) {
496
507
  const themeTokens = collectTheme(screen.ui, ...components.map((c) => c.ui));
497
508
  const theme = buildTheme(themeTokens);
498
509
  const themeSrc = generateThemeSource(theme);
499
- const swift = generateScreenSource(screen, { warnings: genWarnings, theme });
510
+ const cssVars = loadCssVars(input);
511
+ const swift = generateScreenSource(screen, { warnings: genWarnings, theme, cssVars });
500
512
  if (genWarnings.length > 0) {
501
513
  console.log(`[gen-ios] 布局降级提示(${genWarnings.length} 条,R-H3/H4/H5):`);
502
514
  for (const w of genWarnings)
@@ -511,7 +523,7 @@ function runOne(input) {
511
523
  const compFiles = [];
512
524
  for (const comp of components) {
513
525
  const compWarnings = [];
514
- const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme });
526
+ const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme, cssVars });
515
527
  writeFileSync(join(srcDir, `${comp.name}View.swift`), src);
516
528
  compFiles.push({ name: `${comp.name}View.swift`, bytes: src.length });
517
529
  }
@@ -662,12 +674,28 @@ const TAB_ICONS = ['house.fill', 'book.fill', 'gearshape.fill', 'person.fill', '
662
674
  /** 编译期解析 router.ts 子集:import 组件映射 + routes 表(path/name/component/meta.tab/title/icon)
663
675
  * M4b:非 tab 路由(无 meta.tab)→ details 列表(router.push → NavigationStack push) */
664
676
  /** M4a 多页目录模式:samples/<name>/ 含 router.ts + pages/*.vue → TabView 工程 */
677
+ /** 加载项目全局 CSS 变量(tokens.css 等 :root 定义)→ 供 parseColor 解析 var(--xxx) */
678
+ function loadCssVars(appDir) {
679
+ const root = findProjectRoot(appDir); // appDir 可能是 src/router 等子目录 → 回退到项目根
680
+ const map = new Map();
681
+ const roots = [join(root, 'src/styles/tokens.css'), join(root, 'src/tokens.css'), join(root, 'styles/tokens.css'), join(root, 'src/styles/app.css')];
682
+ for (const p of roots) {
683
+ if (!existsSync(p))
684
+ continue;
685
+ const css = readFileSync(p, 'utf-8');
686
+ for (const m of css.matchAll(/(--[\w-]+)\s*:\s*([^;{}]+);/g)) {
687
+ map.set(m[1], m[2].trim());
688
+ }
689
+ }
690
+ return map;
691
+ }
665
692
  function runTabs(appDir, opts = {}) {
666
693
  const routerSrc = readFileSync(opts.routerFile ?? join(appDir, 'router.ts'), 'utf-8');
667
694
  const { tabs, details, warnings: routerWarnings } = parseRouterV2(routerSrc, findProjectRoot(appDir), opts.routerFile ?? join(appDir, 'router.ts'));
668
695
  for (const w of routerWarnings)
669
696
  console.log(`[gen-ios] 路由提示: ${w}`);
670
697
  console.log(`[gen-ios] 多页模式: router.ts → ${tabs.length} 个 tab 页面` + (details.length ? ` + ${details.length} 个 push 页面` : ''));
698
+ const cssVars = loadCssVars(appDir);
671
699
  const projectName = FORCED_PROJECT_NAME ?? toIdentifier(basename(appDir));
672
700
  const bundleId = FORCED_BUNDLE_ID ?? `com.vrn.${projectName.toLowerCase()}`;
673
701
  const outDir = join(OUT_ROOT, projectName);
@@ -720,6 +748,7 @@ function runTabs(appDir, opts = {}) {
720
748
  const swift = generateScreenSource(r.screen, {
721
749
  warnings: genWarnings,
722
750
  theme: undefined,
751
+ cssVars,
723
752
  // runtime 页键与 JS 资源名统一用标识符化 pageId(tab.id 可能含非法字符;也避免与 detail 撞名)
724
753
  runtime: `Runtime.forPage("${pageId}")`,
725
754
  bindingMode: 'external',
@@ -784,6 +813,7 @@ function runTabs(appDir, opts = {}) {
784
813
  const swift = generateScreenSource(r.screen, {
785
814
  warnings: genWarnings,
786
815
  theme: undefined,
816
+ cssVars,
787
817
  // runtime 页键与 tab 页同规则标识符化 + Detail 后缀——
788
818
  // tab/detail 同名时(R-VFOBJ-C)JS 资源文件与 forPage 键都独立,不会互相覆盖
789
819
  runtime: `Runtime.forPage("${pageId}Detail")`,
@@ -820,7 +850,7 @@ function runTabs(appDir, opts = {}) {
820
850
  continue;
821
851
  compSeen.add(comp.name);
822
852
  const compWarnings = [];
823
- const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme });
853
+ const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme, cssVars });
824
854
  writeFileSync(join(srcDir, `${comp.name}View.swift`), src);
825
855
  console.log(`[gen-ios] - ${comp.name}View.swift (${src.length} bytes) · R41 component view`);
826
856
  }
@@ -858,8 +888,7 @@ function buildThemeFromPieces(pieces) {
858
888
  */
859
889
  export function generateIos(opts = {}) {
860
890
  // R141:直接给路由文件(src/router/index.ts)时,目录里并没有 router.ts
861
- if (opts.routerFile)
862
- return runTabs(dirname(opts.routerFile), { routerFile: opts.routerFile });
891
+ // 先注入全局(OUT_ROOT / 工程名 / bundleId),再按入口形态分派
863
892
  if (opts.outDirRoot)
864
893
  OUT_ROOT = opts.outDirRoot;
865
894
  if (opts.samplesRoot)
@@ -868,6 +897,8 @@ export function generateIos(opts = {}) {
868
897
  FORCED_PROJECT_NAME = opts.projectName;
869
898
  if (opts.bundleId)
870
899
  FORCED_BUNDLE_ID = opts.bundleId;
900
+ if (opts.routerFile)
901
+ return runTabs(dirname(opts.routerFile), { routerFile: opts.routerFile });
871
902
  return runMain(opts.input);
872
903
  }
873
904
  /** P2:写 .vrn/manifest.json(工具链/模板版本 + 文件清单)并对齐陈旧文件 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongmingzhixiu/vuexir",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "vrn 命令行:build ios|android、preview、doctor",
5
5
  "type": "module",
6
6
  "license": "MIT",