@dongmingzhixiu/vuexir 0.5.4 → 0.5.6

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
@@ -228,7 +228,14 @@ const RUNTIME_SWIFT = `final class Runtime {
228
228
 
229
229
  /// M3 注册 @State 绑定:JS stateStore.<name> 写回 → <setter>(onAppear 时由 View 注册)
230
230
  func bindState(_ name: String, _ setter: @escaping (Any) -> Void) {
231
- stateBindings[name] = setter
231
+ // R-THEME:ui_skin 变化 → ThemeColor.currentSkin + UserDefaults 同步 → 各视图 @AppStorage("vrn.skin") 自动重绘换肤
232
+ stateBindings[name] = { v in
233
+ setter(v)
234
+ if name == "ui_skin" || name == "ui.skin", let s = v as? String {
235
+ ThemeColor.currentSkin = s
236
+ UserDefaults.standard.set(s, forKey: "vrn.skin")
237
+ }
238
+ }
232
239
  }
233
240
 
234
241
  /// M3 Swift → JS 推送:@State 变更(v-model 输出等)→ stateStore 同步(__fromSwift 防回环)
@@ -254,6 +261,17 @@ const RUNTIME_SWIFT = `final class Runtime {
254
261
  if name.contains("(") { ctx.evaluateScript(name) }
255
262
  else { ctx.evaluateScript("\\(name)()") }
256
263
  }
264
+
265
+ /// R-LOOPITEM:v-for 项点击时把当前 item 注入 JS 全局 __vrnLoop,
266
+ /// invoke 表达式里的循环变量引用(goDetail(row.item.id))才能求值
267
+ func setLoopItem(_ value: Any) {
268
+ guard let json = try? JSONSerialization.data(withJSONObject: value),
269
+ let s = String(data: json, encoding: .utf8) else { return }
270
+ let escaped = s.replacingOccurrences(of: "\\\\", with: "\\\\\\\\")
271
+ .replacingOccurrences(of: "\\"", with: "\\\\\\"")
272
+ .replacingOccurrences(of: "\\n", with: "\\\\n")
273
+ ctx.evaluateScript("__vrnLoop = JSON.parse(\\"\\(escaped)\\")")
274
+ }
257
275
  }
258
276
  `;
259
277
  const APP_SWIFT = (projectName, viewName, screenTitle) => `import SwiftUI
@@ -282,18 +300,18 @@ struct ${projectName}App: App {
282
300
  }
283
301
  }
284
302
 
285
- /// R68:全局 UINavigationBar 深色外观(背景 #0a0f1e、浅色标题、金色 tint)
303
+ /// R68:全局 UINavigationBar 外观(R-THEME:bg/ink/primary 走 ThemeColor 皮肤表)
286
304
  func configureGlobalNavigationBar() {
287
305
  let appearance = UINavigationBarAppearance()
288
306
  appearance.configureWithOpaqueBackground()
289
- appearance.backgroundColor = UIColor(red: 10/255.0, green: 15/255.0, blue: 30/255.0, alpha: 1)
290
- let titleColor = UIColor(red: 233/255.0, green: 230/255.0, blue: 221/255.0, alpha: 1)
307
+ appearance.backgroundColor = UIColor(ThemeColor.color("bg"))
308
+ let titleColor = UIColor(ThemeColor.color("ink"))
291
309
  appearance.titleTextAttributes = [.foregroundColor: titleColor]
292
310
  appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
293
311
  UINavigationBar.appearance().standardAppearance = appearance
294
312
  UINavigationBar.appearance().scrollEdgeAppearance = appearance
295
313
  UINavigationBar.appearance().compactAppearance = appearance
296
- UINavigationBar.appearance().tintColor = UIColor(red: 217/255.0, green: 179/255.0, blue: 108/255.0, alpha: 1)
314
+ UINavigationBar.appearance().tintColor = UIColor(ThemeColor.color("primary"))
297
315
  }
298
316
  `;
299
317
  /** 屏/工程名 → PascalCase(my-app → MyApp;用于 Swift 类型名与 bundle id 派生) */
@@ -496,7 +514,10 @@ function runOne(input) {
496
514
  const themeTokens = collectTheme(screen.ui, ...components.map((c) => c.ui));
497
515
  const theme = buildTheme(themeTokens);
498
516
  const themeSrc = generateThemeSource(theme);
499
- const swift = generateScreenSource(screen, { warnings: genWarnings, theme });
517
+ const cssVars = loadCssVars(input);
518
+ // R-THEME:皮肤 token 表(var(--x) 按皮肤分组 → ThemeColors.swift,换肤原生跟随)
519
+ writeFileSync(join(srcDir, 'ThemeColors.swift'), buildThemeColorsSwift(loadSkinVars(input)));
520
+ const swift = generateScreenSource(screen, { warnings: genWarnings, theme, cssVars });
500
521
  if (genWarnings.length > 0) {
501
522
  console.log(`[gen-ios] 布局降级提示(${genWarnings.length} 条,R-H3/H4/H5):`);
502
523
  for (const w of genWarnings)
@@ -506,12 +527,12 @@ function runOne(input) {
506
527
  writeFileSync(join(srcDir, 'App.swift'), APP_SWIFT(projectName, viewName, screen.title));
507
528
  writeFileSync(join(srcDir, 'recite.json'), JSON.stringify(screen, null, 2));
508
529
  // 事件运行时(<script setup> 逻辑源码;无逻辑时生成空文件保持 Bundle 引用成立)
509
- writeFileSync(join(srcDir, 'runtime.js'), runtimeSrc ?? '');
530
+ writeFileSync(join(srcDir, 'runtime.js'), injectSkinBridge(runtimeSrc ?? ''));
510
531
  // R41 组件:每个 components/Xxx.vue → 独立 XxxView.swift(struct XxxView;页面引用 XxxView(attrs))
511
532
  const compFiles = [];
512
533
  for (const comp of components) {
513
534
  const compWarnings = [];
514
- const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme });
535
+ const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme, cssVars });
515
536
  writeFileSync(join(srcDir, `${comp.name}View.swift`), src);
516
537
  compFiles.push({ name: `${comp.name}View.swift`, bytes: src.length });
517
538
  }
@@ -587,11 +608,11 @@ ${tabs
587
608
  .join('\n')}
588
609
  }
589
610
  .frame(height: 56)
590
- .background(Color(red: 16/255.0, green: 24/255.0, blue: 40/255.0))
611
+ .background(ThemeColor.color("surface-2"))
591
612
  .overlay(Divider(), alignment: .top)
592
613
  }
593
- // R68:容器背景兜底——页面内容不足或底部安全区不露白
594
- .background(${swiftColorExpr(rootBg)})
614
+ // R68:容器背景兜底——页面内容不足或底部安全区不露白(R-THEME:默认深色 → ThemeColor 皮肤 bg)
615
+ .background(${rootBg === '#0a0f1e' || rootBg === '#0a0F1E' || !rootBg ? 'ThemeColor.color("bg")' : swiftColorExpr(rootBg)})
595
616
  ${details.length > 0
596
617
  ? ` .navigationDestination(item: $pushedRoute) { route in
597
618
  Group {
@@ -636,18 +657,18 @@ struct ${projectName}App: App {
636
657
  }
637
658
  }
638
659
 
639
- /// R68:全局 UINavigationBar 深色外观(背景 #0a0f1e、浅色标题、金色 tint)
660
+ /// R68:全局 UINavigationBar 外观(R-THEME:bg/ink/primary 走 ThemeColor 皮肤表)
640
661
  func configureGlobalNavigationBar() {
641
662
  let appearance = UINavigationBarAppearance()
642
663
  appearance.configureWithOpaqueBackground()
643
- appearance.backgroundColor = UIColor(red: 10/255.0, green: 15/255.0, blue: 30/255.0, alpha: 1)
644
- let titleColor = UIColor(red: 233/255.0, green: 230/255.0, blue: 221/255.0, alpha: 1)
664
+ appearance.backgroundColor = UIColor(ThemeColor.color("bg"))
665
+ let titleColor = UIColor(ThemeColor.color("ink"))
645
666
  appearance.titleTextAttributes = [.foregroundColor: titleColor]
646
667
  appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
647
668
  UINavigationBar.appearance().standardAppearance = appearance
648
669
  UINavigationBar.appearance().scrollEdgeAppearance = appearance
649
670
  UINavigationBar.appearance().compactAppearance = appearance
650
- UINavigationBar.appearance().tintColor = UIColor(red: 217/255.0, green: 179/255.0, blue: 108/255.0, alpha: 1)
671
+ UINavigationBar.appearance().tintColor = UIColor(ThemeColor.color("primary"))
651
672
  }
652
673
  `;
653
674
  /** 计算页面 state 在全部 state 平铺数组中的下标(TAB_APP_SWIFT 生成 @State 命名 p<i>_<name> 用) */
@@ -662,12 +683,148 @@ const TAB_ICONS = ['house.fill', 'book.fill', 'gearshape.fill', 'person.fill', '
662
683
  /** 编译期解析 router.ts 子集:import 组件映射 + routes 表(path/name/component/meta.tab/title/icon)
663
684
  * M4b:非 tab 路由(无 meta.tab)→ details 列表(router.push → NavigationStack push) */
664
685
  /** M4a 多页目录模式:samples/<name>/ 含 router.ts + pages/*.vue → TabView 工程 */
686
+ /** 加载项目全局 CSS 变量(tokens.css 等 :root 定义)→ 供 parseColor 解析 var(--xxx) */
687
+ function loadCssVars(appDir) {
688
+ const root = findProjectRoot(appDir); // appDir 可能是 src/router 等子目录 → 回退到项目根
689
+ const map = new Map();
690
+ 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
+ for (const p of roots) {
692
+ if (!existsSync(p))
693
+ continue;
694
+ const css = readFileSync(p, 'utf-8');
695
+ for (const m of css.matchAll(/(--[\w-]+)\s*:\s*([^;{}]+);/g)) {
696
+ map.set(m[1], m[2].trim());
697
+ }
698
+ }
699
+ return map;
700
+ }
701
+ /**
702
+ * R-THEME:JS 侧换肤桥——页面 bundle 的 ui.setSkin(skin) 调用后,
703
+ * 把皮肤广播进 stateStore.ui_skin → Proxy set → __setState → Swift bindState → ThemeColor.currentSkin → 原生换肤
704
+ * 注入位置:esbuild 产物中 ui store 的 setSkin 方法体开头(stateStore 在 RUNTIME 模板段,函数被调用时已就绪)
705
+ */
706
+ function injectSkinBridge(js) {
707
+ if (!js || js.indexOf('setSkin') === -1)
708
+ return js;
709
+ return js.replace(/setSkin\s*\(\s*skin\s*\)\s*\{/g, 'setSkin(skin) { if(typeof stateStore!=="undefined"&&stateStore){try{stateStore["ui_skin"]=skin;}catch(e){}}');
710
+ }
711
+ /**
712
+ * R-THEME:按皮肤分组解析 tokens.css 的 var(--x)
713
+ * 选择器 → 皮肤名::root/.skin-warm → warm;.skin-mint → mint;.skin-lavender → lavender;.skin-night → night
714
+ * 返回 Map<skin, Map<varName, value>>;:root 覆盖块(半径/字体等)并入 warm
715
+ */
716
+ function loadSkinVars(appDir) {
717
+ const root = findProjectRoot(appDir);
718
+ const skins = new Map();
719
+ const roots = [join(root, 'src/styles/tokens.css'), join(root, 'src/tokens.css'), join(root, 'styles/tokens.css')];
720
+ for (const p of roots) {
721
+ if (!existsSync(p))
722
+ continue;
723
+ const css = readFileSync(p, 'utf-8');
724
+ // 按 } 切块,取块头选择器判断皮肤归属
725
+ for (const block of css.split('}')) {
726
+ const selLine = block.split('{')[0] ?? '';
727
+ let skin = '';
728
+ if (/\.skin-warm/.test(selLine) || /:root/.test(selLine))
729
+ skin = 'warm';
730
+ else if (/\.skin-mint/.test(selLine))
731
+ skin = 'mint';
732
+ else if (/\.skin-lavender/.test(selLine))
733
+ skin = 'lavender';
734
+ else if (/\.skin-night/.test(selLine))
735
+ skin = 'night';
736
+ if (!skin)
737
+ continue;
738
+ const body = block.slice(block.indexOf('{') + 1);
739
+ let map = skins.get(skin);
740
+ if (!map) {
741
+ map = new Map();
742
+ skins.set(skin, map);
743
+ }
744
+ for (const m of body.matchAll(/(--[\w-]+)\s*:\s*([^;{}]+);/g)) {
745
+ map.set(m[1], m[2].trim());
746
+ }
747
+ }
748
+ }
749
+ return skins;
750
+ }
751
+ /** var 值 → Swift Color 字面量(hex/rgb/rgba;非颜色返回 null) */
752
+ function cssValueToSwiftColor(v) {
753
+ const t = v.trim();
754
+ if (t.startsWith('#')) {
755
+ let hex = t.slice(1);
756
+ if (hex.length === 3)
757
+ hex = hex.split('').map((h) => h + h).join('');
758
+ const n = parseInt(hex, 16);
759
+ return `Color(red: ${(n >> 16) & 255}/255.0, green: ${(n >> 8) & 255}/255.0, blue: ${n & 255}/255.0)`;
760
+ }
761
+ const m = t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/);
762
+ if (m) {
763
+ const a = m[4] !== undefined ? +m[4] : 1;
764
+ return `Color(red: ${+m[1]}/255.0, green: ${+m[2]}/255.0, blue: ${+m[3]}/255.0, opacity: ${a})`;
765
+ }
766
+ if (/^rgba?\(\s*[\d.]+\s*[,\s]/.test(t))
767
+ return null;
768
+ if (t.startsWith('var(') || /^[a-z]+$/i.test(t))
769
+ return null;
770
+ return null;
771
+ }
772
+ /** 生成 ThemeColors.swift:4 皮肤 token 表 + currentSkin + color(name) 解析 */
773
+ function buildThemeColorsSwift(skins) {
774
+ const order = ['warm', 'mint', 'lavender', 'night'];
775
+ const lines = [];
776
+ lines.push('import SwiftUI');
777
+ lines.push('');
778
+ lines.push('/// Vuexir 皮肤 token 表:由 tokens.css 的 var(--x) 按皮肤分组生成');
779
+ lines.push('/// 换肤:JS ui.setSkin → stateStore.ui_skin → __setState → ThemeColor.currentSkin → 视图重建自动取新色');
780
+ lines.push('enum ThemeColor {');
781
+ lines.push(' static var currentSkin = UserDefaults.standard.string(forKey: "vrn.skin") ?? "warm"');
782
+ lines.push(' static func color(_ name: String) -> Color {');
783
+ lines.push(' let skin = UserDefaults.standard.string(forKey: "vrn.skin") ?? currentSkin');
784
+ lines.push(' switch skin {');
785
+ const names = new Set();
786
+ for (const skin of order)
787
+ names.add(skin);
788
+ for (const skin of order) {
789
+ const map = skins.get(skin);
790
+ if (!map)
791
+ continue;
792
+ const ref = skin === 'warm' ? 'warmMap' : `${skin}Map`;
793
+ const fallback = skin === 'warm' ? 'Color.black' : 'warmMap[name] ?? Color.black';
794
+ lines.push(` case "${skin}": return ${ref}[name] ?? ${fallback}`);
795
+ }
796
+ lines.push(' default: return warmMap[name] ?? Color.black');
797
+ lines.push(' }');
798
+ lines.push(' }');
799
+ for (const skin of order) {
800
+ const map = skins.get(skin);
801
+ if (!map)
802
+ continue;
803
+ const entries = [];
804
+ for (const [k, v] of map) {
805
+ const c = cssValueToSwiftColor(v);
806
+ if (c)
807
+ entries.push(` "${k.slice(2)}": ${c}`);
808
+ }
809
+ if (entries.length === 0)
810
+ continue;
811
+ lines.push(` private static let ${skin === 'warm' ? 'warmMap' : `${skin}Map`}: [String: Color] = [`);
812
+ lines.push(entries.join(',\n'));
813
+ lines.push(' ]');
814
+ }
815
+ lines.push('}');
816
+ return lines.join('\n') + '\n';
817
+ }
665
818
  function runTabs(appDir, opts = {}) {
666
819
  const routerSrc = readFileSync(opts.routerFile ?? join(appDir, 'router.ts'), 'utf-8');
667
820
  const { tabs, details, warnings: routerWarnings } = parseRouterV2(routerSrc, findProjectRoot(appDir), opts.routerFile ?? join(appDir, 'router.ts'));
668
821
  for (const w of routerWarnings)
669
822
  console.log(`[gen-ios] 路由提示: ${w}`);
670
823
  console.log(`[gen-ios] 多页模式: router.ts → ${tabs.length} 个 tab 页面` + (details.length ? ` + ${details.length} 个 push 页面` : ''));
824
+ const cssVars = loadCssVars(appDir);
825
+ // R-THEME:皮肤 token 表(var(--x) 按皮肤分组 → ThemeColors.swift,换肤原生跟随)
826
+ const skinVars = loadSkinVars(appDir);
827
+ const themeColorsSwift = buildThemeColorsSwift(skinVars);
671
828
  const projectName = FORCED_PROJECT_NAME ?? toIdentifier(basename(appDir));
672
829
  const bundleId = FORCED_BUNDLE_ID ?? `com.vrn.${projectName.toLowerCase()}`;
673
830
  const outDir = join(OUT_ROOT, projectName);
@@ -675,6 +832,7 @@ function runTabs(appDir, opts = {}) {
675
832
  // 写前清空源目录:删除页面后残留的旧 View/runtime 仍会进 Xcode 工程(幽灵页面)
676
833
  rmSync(srcDir, { recursive: true, force: true });
677
834
  mkdirSync(srcDir, { recursive: true });
835
+ writeFileSync(join(srcDir, 'ThemeColors.swift'), themeColorsSwift);
678
836
  // R97 P2:把可独立运行的 utils 打成 IIFE,前置到每个页面 runtime 之前,
679
837
  // 让 computed 里引用的外部函数(countChars / normalize …)在原生 JS 运行时可用
680
838
  const utilsBundle = bundleProjectUtils(findProjectRoot(appDir));
@@ -720,6 +878,7 @@ function runTabs(appDir, opts = {}) {
720
878
  const swift = generateScreenSource(r.screen, {
721
879
  warnings: genWarnings,
722
880
  theme: undefined,
881
+ cssVars,
723
882
  // runtime 页键与 JS 资源名统一用标识符化 pageId(tab.id 可能含非法字符;也避免与 detail 撞名)
724
883
  runtime: `Runtime.forPage("${pageId}")`,
725
884
  bindingMode: 'external',
@@ -732,7 +891,7 @@ function runTabs(appDir, opts = {}) {
732
891
  const pageBundle = bundlePageScript(tab.file, findProjectRoot(appDir));
733
892
  if (pageBundle)
734
893
  console.log(`[gen-ios] 页面脚本打包:${tab.id}(${pageBundle.names.length} 个绑定)`);
735
- writeFileSync(join(srcDir, `runtime_${pageId}.js`), utilsBundle.code + '\n' + (pageBundle?.code ?? '') + '\n' + (r.runtime ?? ''));
894
+ writeFileSync(join(srcDir, `runtime_${pageId}.js`), injectSkinBridge(utilsBundle.code + '\n' + (pageBundle?.code ?? '') + '\n' + (r.runtime ?? '')));
736
895
  pageFiles.push(`${pageId}View.swift`);
737
896
  pageRuntimes.push({ id: pageId, src: r.runtime ?? '' });
738
897
  pageStates.push({
@@ -784,6 +943,7 @@ function runTabs(appDir, opts = {}) {
784
943
  const swift = generateScreenSource(r.screen, {
785
944
  warnings: genWarnings,
786
945
  theme: undefined,
946
+ cssVars,
787
947
  // runtime 页键与 tab 页同规则标识符化 + Detail 后缀——
788
948
  // tab/detail 同名时(R-VFOBJ-C)JS 资源文件与 forPage 键都独立,不会互相覆盖
789
949
  runtime: `Runtime.forPage("${pageId}Detail")`,
@@ -791,7 +951,7 @@ function runTabs(appDir, opts = {}) {
791
951
  for (const w of genWarnings)
792
952
  console.log(`[gen-ios] 生成提示(${d.id},push 页): ${w}`);
793
953
  writeFileSync(join(srcDir, viewFile), swift.replace(/Text\("(_vrn[EAC][0-9]+)"\)/g, 'Text($1)'));
794
- writeFileSync(join(srcDir, `runtime_${pageId}Detail.js`), utilsBundle.code + '\n' + (r.runtime ?? ''));
954
+ writeFileSync(join(srcDir, `runtime_${pageId}Detail.js`), injectSkinBridge(utilsBundle.code + '\n' + (r.runtime ?? '')));
795
955
  pageFiles.push(viewFile);
796
956
  pageRuntimes.push({ id: `${pageId}Detail`, src: r.runtime ?? '' });
797
957
  detailPages.push({ id: d.id, title: d.title, view: viewName, path: d.path });
@@ -820,7 +980,7 @@ function runTabs(appDir, opts = {}) {
820
980
  continue;
821
981
  compSeen.add(comp.name);
822
982
  const compWarnings = [];
823
- const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme });
983
+ const src = generateComponentSource(comp.name, comp.ui, { warnings: compWarnings, theme, cssVars });
824
984
  writeFileSync(join(srcDir, `${comp.name}View.swift`), src);
825
985
  console.log(`[gen-ios] - ${comp.name}View.swift (${src.length} bytes) · R41 component view`);
826
986
  }
@@ -858,8 +1018,7 @@ function buildThemeFromPieces(pieces) {
858
1018
  */
859
1019
  export function generateIos(opts = {}) {
860
1020
  // R141:直接给路由文件(src/router/index.ts)时,目录里并没有 router.ts
861
- if (opts.routerFile)
862
- return runTabs(dirname(opts.routerFile), { routerFile: opts.routerFile });
1021
+ // 先注入全局(OUT_ROOT / 工程名 / bundleId),再按入口形态分派
863
1022
  if (opts.outDirRoot)
864
1023
  OUT_ROOT = opts.outDirRoot;
865
1024
  if (opts.samplesRoot)
@@ -868,6 +1027,8 @@ export function generateIos(opts = {}) {
868
1027
  FORCED_PROJECT_NAME = opts.projectName;
869
1028
  if (opts.bundleId)
870
1029
  FORCED_BUNDLE_ID = opts.bundleId;
1030
+ if (opts.routerFile)
1031
+ return runTabs(dirname(opts.routerFile), { routerFile: opts.routerFile });
871
1032
  return runMain(opts.input);
872
1033
  }
873
1034
  /** 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.6",
4
4
  "description": "vrn 命令行:build ios|android、preview、doctor",
5
5
  "type": "module",
6
6
  "license": "MIT",