@dongmingzhixiu/vuexir 0.6.0 → 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/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +42 -4
- package/dist/cli.js +7 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.js +3 -0
- package/dist/gen-android.d.ts +5 -0
- package/dist/gen-android.js +9 -2
- package/dist/gen-ios.d.ts +5 -14
- package/dist/gen-ios.js +69 -27
- package/dist/shims.d.ts +2 -2
- package/dist/shims.js +6 -3
- package/package.json +5 -3
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
|
-
|
|
90
|
-
const names = [
|
|
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,
|
|
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;
|
|
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
|
*
|
|
@@ -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,
|
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 */
|
package/dist/config.js
CHANGED
|
@@ -45,6 +45,9 @@ export function resolveConfig(base, overrides = {}) {
|
|
|
45
45
|
const outDirRaw = overrides.outDir ?? base.outDir ?? 'native';
|
|
46
46
|
return {
|
|
47
47
|
projectRoot,
|
|
48
|
+
nativeNavigationBar: overrides.nativeNavigationBar ?? base.nativeNavigationBar,
|
|
49
|
+
tabBarComponent: overrides.tabBarComponent ?? base.tabBarComponent,
|
|
50
|
+
navigation: overrides.navigation ?? base.navigation,
|
|
48
51
|
entry: overrides.entry ?? base.entry,
|
|
49
52
|
outDir: isAbsolute(outDirRaw) ? outDirRaw : join(projectRoot, outDirRaw),
|
|
50
53
|
projectName: overrides.projectName ?? base.projectName,
|
package/dist/gen-android.d.ts
CHANGED
|
@@ -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) */
|
package/dist/gen-android.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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(显式入口优先)
|
|
@@ -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;
|
|
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
|
|
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(
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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.
|
|
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 :
|
|
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]
|
|
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
|
|
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.
|
|
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.
|
|
25
|
-
"@dongmingzhixiu/vuexir-templates": "^0.
|
|
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"
|