@dongmingzhixiu/vuexir 0.5.3 → 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 +5 -1
- package/dist/cli.js +25 -3
- package/dist/config.js +3 -2
- package/dist/gen-ios.d.ts +2 -0
- package/dist/gen-ios.js +37 -3
- package/package.json +3 -3
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
|
-
|
|
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
|
-
|
|
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,9 +93,15 @@ 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
|
-
const out = generateIos({
|
|
98
|
+
const out = generateIos({
|
|
99
|
+
input: inputDir,
|
|
100
|
+
outDirRoot: outRoot,
|
|
101
|
+
projectName,
|
|
102
|
+
bundleId,
|
|
103
|
+
...(routerFile ? { routerFile } : {}),
|
|
104
|
+
});
|
|
83
105
|
if (!out)
|
|
84
106
|
throw new Error('[vrn] iOS 生成未返回产物目录');
|
|
85
107
|
return out;
|
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:
|
|
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.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* 后续:cd compiler/generated/ios/<ProjectName> && xcodegen generate && xcodebuild ...
|
|
14
14
|
*/
|
|
15
15
|
export interface GenerateIosOptions {
|
|
16
|
+
/** 显式路由文件(src/router/index.ts 等非标准命名) */
|
|
17
|
+
routerFile?: string;
|
|
16
18
|
/** 样例名 / 目录 / 显式 .vue 或 .json 路径;'all' 批量;省略 = 默认 vuexir-demo */
|
|
17
19
|
input?: string;
|
|
18
20
|
/** 产物根(<outDir>/<ProjectName>) */
|
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
|
|
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
|
}
|
|
@@ -857,6 +887,8 @@ function buildThemeFromPieces(pieces) {
|
|
|
857
887
|
* 说明:本函数沿用既有 main 逻辑(多页/单页/批量),仅把路径来源从「仓库布局」改成可注入。
|
|
858
888
|
*/
|
|
859
889
|
export function generateIos(opts = {}) {
|
|
890
|
+
// R141:直接给路由文件(src/router/index.ts)时,目录里并没有 router.ts
|
|
891
|
+
// 先注入全局(OUT_ROOT / 工程名 / bundleId),再按入口形态分派
|
|
860
892
|
if (opts.outDirRoot)
|
|
861
893
|
OUT_ROOT = opts.outDirRoot;
|
|
862
894
|
if (opts.samplesRoot)
|
|
@@ -865,6 +897,8 @@ export function generateIos(opts = {}) {
|
|
|
865
897
|
FORCED_PROJECT_NAME = opts.projectName;
|
|
866
898
|
if (opts.bundleId)
|
|
867
899
|
FORCED_BUNDLE_ID = opts.bundleId;
|
|
900
|
+
if (opts.routerFile)
|
|
901
|
+
return runTabs(dirname(opts.routerFile), { routerFile: opts.routerFile });
|
|
868
902
|
return runMain(opts.input);
|
|
869
903
|
}
|
|
870
904
|
/** P2:写 .vrn/manifest.json(工具链/模板版本 + 文件清单)并对齐陈旧文件 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dongmingzhixiu/vuexir",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "vrn 命令行:build ios|android、preview、doctor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"LICENSE"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@dongmingzhixiu/vuexir-compiler": "^0.5.
|
|
25
|
-
"@dongmingzhixiu/vuexir-templates": "^0.5.
|
|
24
|
+
"@dongmingzhixiu/vuexir-compiler": "^0.5.4",
|
|
25
|
+
"@dongmingzhixiu/vuexir-templates": "^0.5.4"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=18"
|