@aiot-toolkit/aiotpack 2.0.6-beta.9 → 2.1.0-prender.1
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/lib/afterCompile/ux/UxAfterCompile.d.ts +4 -0
- package/lib/afterCompile/ux/UxAfterCompile.js +90 -2
- package/lib/compiler/javascript/JavascriptCompiler.js +11 -4
- package/lib/compiler/javascript/TemplateCompiler.d.ts +29 -0
- package/lib/compiler/javascript/TemplateCompiler.js +564 -0
- package/lib/compiler/javascript/ViteCompiler.d.ts +13 -0
- package/lib/compiler/javascript/ViteCompiler.js +414 -0
- package/lib/compiler/javascript/interface/IJavascriptCompileOption.d.ts +26 -0
- package/lib/compiler/javascript/vela/VelaWebpackConfigurator.d.ts +3 -1
- package/lib/compiler/javascript/vela/VelaWebpackConfigurator.js +16 -1
- package/lib/compiler/javascript/vela/interface/IManifest.d.ts +12 -0
- package/lib/compiler/javascript/vela/plugin/WrapPlugin.d.ts +10 -1
- package/lib/compiler/javascript/vela/plugin/WrapPlugin.js +241 -57
- package/lib/compiler/javascript/vela/utils/UxCompileUtil.d.ts +3 -2
- package/lib/compiler/javascript/vela/utils/UxCompileUtil.js +12 -4
- package/lib/compiler/javascript/vela/utils/VruUtil.d.ts +50 -0
- package/lib/compiler/javascript/vela/utils/VruUtil.js +128 -0
- package/lib/compiler/javascript/vela/utils/ZipUtil.d.ts +9 -0
- package/lib/compiler/javascript/vela/utils/ZipUtil.js +112 -6
- package/lib/compiler/javascript/vela/utils/webpackLoader/WebpackJsLoader.js +1 -1
- package/lib/config/UxConfig.d.ts +12 -5
- package/lib/config/UxConfig.js +7 -6
- package/lib/loader/ux/JsLoader.d.ts +7 -0
- package/lib/loader/ux/JsLoader.js +38 -8
- package/lib/loader/ux/vela/HmlLoader.d.ts +6 -6
- package/lib/loader/ux/vela/HmlLoader.js +30 -13
- package/lib/prerender/PrerenderVM.d.ts +86 -0
- package/lib/prerender/PrerenderVM.js +677 -0
- package/lib/prerender/StyleSerializer.d.ts +18 -0
- package/lib/prerender/StyleSerializer.js +92 -0
- package/lib/prerender/TemplateSerializer.d.ts +26 -0
- package/lib/prerender/TemplateSerializer.js +122 -0
- package/lib/prerender/index.d.ts +20 -0
- package/lib/prerender/index.js +519 -0
- package/lib/prerender/interface/IPrerenderOption.d.ts +15 -0
- package/lib/prerender/interface/IPrerenderOption.js +1 -0
- package/lib/utils/BeforeCompileUtils.d.ts +1 -1
- package/lib/utils/BeforeCompileUtils.js +52 -9
- package/lib/utils/ux/ManifestSchema.js +0 -1
- package/lib/utils/ux/UxFileUtils.js +1 -1
- package/lib/utils/ux/UxLoaderUtils.js +8 -3
- package/package.json +9 -6
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _vm = _interopRequireDefault(require("vm"));
|
|
8
|
+
var _sharedUtils = require("@aiot-toolkit/shared-utils");
|
|
9
|
+
var _StyleSerializer = require("./StyleSerializer");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
/** 预渲染 DOM 节点 */
|
|
12
|
+
|
|
13
|
+
/** 绑定标记:文本/属性绑定 */
|
|
14
|
+
|
|
15
|
+
/** 循环标记 */
|
|
16
|
+
|
|
17
|
+
/** 事件标记 */
|
|
18
|
+
|
|
19
|
+
/** 组件定义 */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* PrerenderVM - 在 Node.js VM 沙箱中执行编译后的模板函数,收集 DOM 树
|
|
23
|
+
*/
|
|
24
|
+
class PrerenderVM {
|
|
25
|
+
components = (() => new Map())();
|
|
26
|
+
styleMap = (() => new Map())();
|
|
27
|
+
bindCounter = 0;
|
|
28
|
+
styleSerializer = (() => new _StyleSerializer.StyleSerializer())();
|
|
29
|
+
accessedKeys = [];
|
|
30
|
+
|
|
31
|
+
/** 创建 data Proxy,拦截访问并返回绑定标记(支持嵌套和数组) */
|
|
32
|
+
createDataProxy(data, parentPath) {
|
|
33
|
+
if (!parentPath) this.accessedKeys = [];
|
|
34
|
+
const accessedKeys = this.accessedKeys;
|
|
35
|
+
const self = this;
|
|
36
|
+
return new Proxy(data, {
|
|
37
|
+
get(target, prop) {
|
|
38
|
+
if (typeof prop === 'symbol') return undefined;
|
|
39
|
+
if (prop === '__raw__') return target;
|
|
40
|
+
if (prop === '$item' || prop === '$idx') return `{{${prop}}}`;
|
|
41
|
+
const fullPath = parentPath ? `${parentPath}.${prop}` : prop;
|
|
42
|
+
accessedKeys.push(fullPath);
|
|
43
|
+
if (!(prop in target)) {
|
|
44
|
+
console.warn(`prerender: access undefined value in data! key: ${fullPath}`);
|
|
45
|
+
return self.createBindingMarker(fullPath);
|
|
46
|
+
}
|
|
47
|
+
const val = target[prop];
|
|
48
|
+
// Pass through functions (e.g. $t, $tc, $r)
|
|
49
|
+
if (typeof val === 'function') {
|
|
50
|
+
return val;
|
|
51
|
+
}
|
|
52
|
+
// Nested object or array: return recursive Proxy
|
|
53
|
+
if (val !== null && typeof val === 'object') {
|
|
54
|
+
return self.createDataProxy(val, fullPath);
|
|
55
|
+
}
|
|
56
|
+
// Include the static value in the binding marker for SSR snapshot
|
|
57
|
+
return self.createBindingMarker(fullPath, val);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 获取已访问的 key 列表 */
|
|
63
|
+
getAccessedKeys() {
|
|
64
|
+
return [...this.accessedKeys];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Track binding markers accessed during a function call */
|
|
68
|
+
_bindingAccess = [];
|
|
69
|
+
|
|
70
|
+
/** Create a binding marker with toString for string concatenation */
|
|
71
|
+
createBindingMarker(key, staticValue) {
|
|
72
|
+
const self = this;
|
|
73
|
+
const marker = {
|
|
74
|
+
$value: key,
|
|
75
|
+
bind: 1,
|
|
76
|
+
toString: () => {
|
|
77
|
+
self._bindingAccess.push(key);
|
|
78
|
+
// Return static value for string concatenation (enables correct snapshot)
|
|
79
|
+
if (staticValue !== undefined) return String(staticValue);
|
|
80
|
+
return `{{${key}}}`;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (staticValue !== undefined) {
|
|
84
|
+
marker._staticValue = staticValue;
|
|
85
|
+
}
|
|
86
|
+
return marker;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 文本绑定标记 */
|
|
90
|
+
static createTextBinding(key) {
|
|
91
|
+
return {
|
|
92
|
+
$value: key,
|
|
93
|
+
bind: 1
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 属性绑定标记 */
|
|
98
|
+
static createAttrBinding(attr, key) {
|
|
99
|
+
return {
|
|
100
|
+
[`$${attr}`]: key,
|
|
101
|
+
bind: 1
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 循环标记 */
|
|
106
|
+
static createRepeatMarker(listName) {
|
|
107
|
+
return {
|
|
108
|
+
$repeat: listName,
|
|
109
|
+
repeat: []
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 事件标记 */
|
|
114
|
+
static createEventMarker(event, handler) {
|
|
115
|
+
return {
|
|
116
|
+
[`$${event}`]: handler
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 执行编译后的 JS,收集组件定义
|
|
122
|
+
*/
|
|
123
|
+
execute(jsCode, onLog) {
|
|
124
|
+
this.components.clear();
|
|
125
|
+
this.styleMap.clear();
|
|
126
|
+
this.bindCounter = 0;
|
|
127
|
+
const bootstrapName = [];
|
|
128
|
+
const sandbox = this.createSandbox(name => {
|
|
129
|
+
bootstrapName.push(name);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 编译后的 JS 被 WrapPlugin 包裹为 export default function(global,...){...}
|
|
133
|
+
// 去掉 export default 包裹,直接执行内部代码
|
|
134
|
+
const unwrapped = this.unwrapCode(jsCode);
|
|
135
|
+
try {
|
|
136
|
+
const script = new _vm.default.Script(unwrapped, {
|
|
137
|
+
filename: 'prerender.js'
|
|
138
|
+
});
|
|
139
|
+
script.runInNewContext(sandbox, {
|
|
140
|
+
timeout: 5000
|
|
141
|
+
});
|
|
142
|
+
} catch (e) {
|
|
143
|
+
onLog?.([{
|
|
144
|
+
level: _sharedUtils.Loglevel.WARN,
|
|
145
|
+
message: [`prerender: VM execution error - ${e?.message || e}`]
|
|
146
|
+
}]);
|
|
147
|
+
return {
|
|
148
|
+
tree: null,
|
|
149
|
+
styles: {}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 找到 bootstrap 的主组件
|
|
154
|
+
let mainComp;
|
|
155
|
+
const mainName = bootstrapName[bootstrapName.length - 1];
|
|
156
|
+
if (mainName) {
|
|
157
|
+
mainComp = this.components.get(mainName);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Vela format: $app_exports$['entry'] sets template/style on $app_exports$.default
|
|
161
|
+
if (!mainComp?.template && typeof sandbox.$app_exports$?.entry === 'function') {
|
|
162
|
+
const velaExports = {
|
|
163
|
+
default: {}
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
sandbox.$app_exports$.entry(velaExports);
|
|
167
|
+
} catch {/* ignore */}
|
|
168
|
+
const comp = velaExports.default;
|
|
169
|
+
if (comp?.template) {
|
|
170
|
+
mainComp = comp;
|
|
171
|
+
if (comp.style) {
|
|
172
|
+
this.collectStyles(comp.style);
|
|
173
|
+
// Vela styles may not have @info/styleObjectId, assign default
|
|
174
|
+
if (this.styleMap.size === 0) {
|
|
175
|
+
this.styleMap.set(0, comp.style);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!mainComp?.template) {
|
|
181
|
+
// app.ux sets style directly on $app_exports$.default.style (no $app_define$)
|
|
182
|
+
if (this.styleMap.size === 0 && sandbox.$app_exports$?.default?.style) {
|
|
183
|
+
const appStyle = sandbox.$app_exports$.default.style;
|
|
184
|
+
this.collectStyles(appStyle);
|
|
185
|
+
if (this.styleMap.size === 0) {
|
|
186
|
+
this.styleMap.set(0, appStyle);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Still return collected styles even without template (e.g. app.ux)
|
|
190
|
+
const styles = {};
|
|
191
|
+
for (const [id, s] of this.styleMap) {
|
|
192
|
+
styles[id] = this.normalizeStyleSheet(s);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
tree: null,
|
|
196
|
+
styles
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 构建 mock VM 实例
|
|
201
|
+
const mockVm = this.createMockVM(mainComp);
|
|
202
|
+
|
|
203
|
+
// 执行模板函数
|
|
204
|
+
const tree = this.executeTemplate(mainComp.template, mockVm);
|
|
205
|
+
|
|
206
|
+
// 收集样式
|
|
207
|
+
const styles = {};
|
|
208
|
+
for (const [id, s] of this.styleMap) {
|
|
209
|
+
styles[id] = this.normalizeStyleSheet(s);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
tree,
|
|
213
|
+
styles
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
unwrapCode(code) {
|
|
217
|
+
// 去掉 export default function(global, globalThis, window, $app_exports$, $app_evaluate$){...}
|
|
218
|
+
// 新格式 $app_define$(...) 不需要 unwrap,直接返回
|
|
219
|
+
if (code.trimStart().startsWith('$app_define$')) {
|
|
220
|
+
return code;
|
|
221
|
+
}
|
|
222
|
+
const match = code.match(/export\s+default\s+function\s*\([^)]*\)\s*\{([\s\S]*)\}\s*$/);
|
|
223
|
+
if (match) {
|
|
224
|
+
return match[1];
|
|
225
|
+
}
|
|
226
|
+
return code;
|
|
227
|
+
}
|
|
228
|
+
createSandbox(onBootstrap) {
|
|
229
|
+
const self = this;
|
|
230
|
+
const sandbox = {
|
|
231
|
+
console,
|
|
232
|
+
setTimeout: () => {},
|
|
233
|
+
setInterval: () => {},
|
|
234
|
+
clearTimeout: () => {},
|
|
235
|
+
clearInterval: () => {},
|
|
236
|
+
global: {},
|
|
237
|
+
globalThis: {},
|
|
238
|
+
window: {},
|
|
239
|
+
exports: {},
|
|
240
|
+
module: {
|
|
241
|
+
exports: {}
|
|
242
|
+
},
|
|
243
|
+
require: () => ({}),
|
|
244
|
+
$app_exports$: {},
|
|
245
|
+
$app_evaluate$: () => {},
|
|
246
|
+
$app_require$: () => ({}),
|
|
247
|
+
$app_define$: (name, _deps, factory) => {
|
|
248
|
+
const mod = {
|
|
249
|
+
exports: {}
|
|
250
|
+
};
|
|
251
|
+
try {
|
|
252
|
+
factory(sandbox.$app_require$, mod.exports, mod);
|
|
253
|
+
} catch {
|
|
254
|
+
// ignore factory errors
|
|
255
|
+
}
|
|
256
|
+
let comp = mod.exports.default || mod.exports;
|
|
257
|
+
|
|
258
|
+
// If the export is a page factory function (old format), invoke it to get the component
|
|
259
|
+
if (typeof comp === 'function') {
|
|
260
|
+
const pageExports = {
|
|
261
|
+
default: {}
|
|
262
|
+
};
|
|
263
|
+
try {
|
|
264
|
+
comp(pageExports);
|
|
265
|
+
} catch {
|
|
266
|
+
// ignore
|
|
267
|
+
}
|
|
268
|
+
comp = pageExports.default || pageExports;
|
|
269
|
+
}
|
|
270
|
+
self.components.set(name, comp);
|
|
271
|
+
|
|
272
|
+
// 收集 style 中的 styleObjectId
|
|
273
|
+
if (comp.style) {
|
|
274
|
+
self.collectStyles(comp.style);
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
$app_bootstrap$: name => {
|
|
278
|
+
onBootstrap(name);
|
|
279
|
+
},
|
|
280
|
+
aiot: {
|
|
281
|
+
__ce__: (type, opts, children) => self.createNode(type, opts, children),
|
|
282
|
+
__cc__: (type, opts, children) => self.createNode(type, opts, children, true),
|
|
283
|
+
__ci__: (opts, renderFn) => self.createConditionalNode(opts, renderFn),
|
|
284
|
+
__cf__: (opts, renderFn) => self.createForNode(opts, renderFn),
|
|
285
|
+
__cb__: opts => self.createBlockNode(opts)
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// global 上也挂载 aiot
|
|
290
|
+
sandbox.global.aiot = sandbox.aiot;
|
|
291
|
+
sandbox.global.$app_require$ = sandbox.$app_require$;
|
|
292
|
+
sandbox.global.setTimeout = sandbox.setTimeout;
|
|
293
|
+
sandbox.global.setInterval = sandbox.setInterval;
|
|
294
|
+
sandbox.global.clearTimeout = sandbox.clearTimeout;
|
|
295
|
+
sandbox.global.clearInterval = sandbox.clearInterval;
|
|
296
|
+
return sandbox;
|
|
297
|
+
}
|
|
298
|
+
collectStyles(style) {
|
|
299
|
+
if (Array.isArray(style)) {
|
|
300
|
+
for (const item of style) {
|
|
301
|
+
if (Array.isArray(item) && item.length >= 2) {
|
|
302
|
+
// 格式: [[[selectorType, selectorName]], {prop: value}]
|
|
303
|
+
// 不直接用,后面从 @info 中取 styleObjectId
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// 查找 @info
|
|
307
|
+
const info = style.find(item => !Array.isArray(item) && item?.['@info']);
|
|
308
|
+
if (info?.['@info']?.styleObjectId != null) {
|
|
309
|
+
this.styleMap.set(info['@info'].styleObjectId, style);
|
|
310
|
+
} else if (style.length > 0) {
|
|
311
|
+
// Vela format: no @info, use default styleObjectId 0
|
|
312
|
+
if (this.styleMap.size === 0) {
|
|
313
|
+
this.styleMap.set(0, style);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} else if (style?.['@info']?.styleObjectId != null) {
|
|
317
|
+
this.styleMap.set(style['@info'].styleObjectId, style);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
normalizeStyleSheet(style) {
|
|
321
|
+
if (Array.isArray(style)) {
|
|
322
|
+
return this.styleSerializer.parseStyleNodes(style);
|
|
323
|
+
}
|
|
324
|
+
return {};
|
|
325
|
+
}
|
|
326
|
+
createMockVM(comp) {
|
|
327
|
+
let data = {};
|
|
328
|
+
|
|
329
|
+
// 获取初始数据
|
|
330
|
+
if (typeof comp.data === 'function') {
|
|
331
|
+
try {
|
|
332
|
+
data = comp.data() || {};
|
|
333
|
+
} catch {/* ignore */}
|
|
334
|
+
} else if (comp.data) {
|
|
335
|
+
data = {
|
|
336
|
+
...comp.data
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (comp.private) {
|
|
340
|
+
data = {
|
|
341
|
+
...data,
|
|
342
|
+
...comp.private
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Add common VM helper methods that return binding markers when called
|
|
347
|
+
const self = this;
|
|
348
|
+
const bindingFn = function () {
|
|
349
|
+
const expr = `$t(${JSON.stringify((arguments.length <= 0 ? undefined : arguments[0]) ?? '')})`;
|
|
350
|
+
return self.createBindingMarker(expr);
|
|
351
|
+
};
|
|
352
|
+
for (const method of ['$t', '$tc', '$r', '$set', '$delete', '$emit', '$dispatch', '$broadcast', '$watch', '$forceUpdate']) {
|
|
353
|
+
if (!(method in data)) {
|
|
354
|
+
data[method] = function () {
|
|
355
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
356
|
+
args[_key] = arguments[_key];
|
|
357
|
+
}
|
|
358
|
+
const expr = `${method}(${args.map(a => JSON.stringify(a)).join(',')})`;
|
|
359
|
+
return self.createBindingMarker(expr);
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 用 Proxy 拦截数据访问,返回绑定标记
|
|
365
|
+
return this.createDataProxy(data);
|
|
366
|
+
}
|
|
367
|
+
createNode(type, opts, children, isComponent) {
|
|
368
|
+
const node = {
|
|
369
|
+
type
|
|
370
|
+
};
|
|
371
|
+
const vmOpts = opts?.__opts__ || {};
|
|
372
|
+
|
|
373
|
+
// 处理 class
|
|
374
|
+
if (vmOpts.classList?.length) {
|
|
375
|
+
node.class = vmOpts.classList.join(' ');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 处理 style(内联样式)
|
|
379
|
+
if (vmOpts.style) {
|
|
380
|
+
node.style = typeof vmOpts.style === 'string' ? this.parseInlineStyle(vmOpts.style) : vmOpts.style;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// For custom components, capture props for attr
|
|
384
|
+
if (isComponent) {
|
|
385
|
+
const skipKeys = new Set(['classList', 'style', 'events', 'attr', '__vm__', 'is', 'remotewidget']);
|
|
386
|
+
const props = {};
|
|
387
|
+
for (const [key, val] of Object.entries(vmOpts)) {
|
|
388
|
+
if (skipKeys.has(key)) continue;
|
|
389
|
+
if (typeof val === 'function') {
|
|
390
|
+
props[`$${key}`] = val.toString().replace(/_vm_\./g, 'this.');
|
|
391
|
+
} else if (val !== undefined) {
|
|
392
|
+
props[key] = val;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (Object.keys(props).length > 0) {
|
|
396
|
+
node.attr = {
|
|
397
|
+
...(node.attr || {}),
|
|
398
|
+
...props
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
node.bind = 3;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 处理 attr
|
|
405
|
+
if (vmOpts.attr) {
|
|
406
|
+
node.attr = {};
|
|
407
|
+
for (const [key, val] of Object.entries(vmOpts.attr)) {
|
|
408
|
+
if (typeof val === 'function') {
|
|
409
|
+
// 动态绑定 - call the function to get the binding marker with static value
|
|
410
|
+
try {
|
|
411
|
+
this._bindingAccess = [];
|
|
412
|
+
const result = val();
|
|
413
|
+
if (result && typeof result === 'object' && result.bind === 1) {
|
|
414
|
+
// Pure binding marker (e.g. vm.appName) - no static value needed
|
|
415
|
+
node.attr[`$${key}`] = result.$value;
|
|
416
|
+
} else if (this._bindingAccess.length > 0) {
|
|
417
|
+
// String result from concatenation involving bindings (e.g. vm.temp + "°")
|
|
418
|
+
// The result string IS the static snapshot value
|
|
419
|
+
node.attr[`$${key}`] = this.extractBindingExpression(val);
|
|
420
|
+
node.attr[key] = result;
|
|
421
|
+
} else {
|
|
422
|
+
node.attr[`$${key}`] = this.extractBindingExpression(val);
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
node.attr[`$${key}`] = this.extractBindingExpression(val);
|
|
426
|
+
}
|
|
427
|
+
if (!node.bind) {
|
|
428
|
+
this.bindCounter++;
|
|
429
|
+
node.bind = this.bindCounter;
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
node.attr[key] = val;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Vela format: value/type/etc. may be directly on __opts__ instead of inside attr
|
|
438
|
+
for (const key of ['value', 'type', 'src', 'href', 'id', 'show', 'if']) {
|
|
439
|
+
if (key in vmOpts && vmOpts[key] !== undefined) {
|
|
440
|
+
if (!node.attr) node.attr = {};
|
|
441
|
+
const val = vmOpts[key];
|
|
442
|
+
if (typeof val === 'function') {
|
|
443
|
+
try {
|
|
444
|
+
this._bindingAccess = [];
|
|
445
|
+
const result = val();
|
|
446
|
+
if (result && typeof result === 'object' && result.bind === 1) {
|
|
447
|
+
node.attr[`$${key}`] = result.$value;
|
|
448
|
+
if (!node.bind) {
|
|
449
|
+
this.bindCounter++;
|
|
450
|
+
node.bind = this.bindCounter;
|
|
451
|
+
}
|
|
452
|
+
} else if (this._bindingAccess.length > 0) {
|
|
453
|
+
// String from concatenation with bindings - store expression only
|
|
454
|
+
node.attr[`$${key}`] = this.extractBindingExpression(val);
|
|
455
|
+
if (!node.bind) {
|
|
456
|
+
this.bindCounter++;
|
|
457
|
+
node.bind = this.bindCounter;
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
node.attr[key] = result;
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
node.attr[key] = val;
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
node.attr[key] = val;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 处理事件 (Android format: vmOpts.on, Vela format: vmOpts.events)
|
|
472
|
+
const eventsObj = vmOpts.on || vmOpts.events;
|
|
473
|
+
if (eventsObj) {
|
|
474
|
+
node.events = {};
|
|
475
|
+
for (const [event, handler] of Object.entries(eventsObj)) {
|
|
476
|
+
if (typeof handler === 'function') {
|
|
477
|
+
node.events[`$${event}`] = handler.toString();
|
|
478
|
+
} else {
|
|
479
|
+
node.events[event] = String(handler);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// 处理 import(自定义组件)
|
|
485
|
+
if (vmOpts.import) {
|
|
486
|
+
node.import = vmOpts.import;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 处理 children
|
|
490
|
+
if (children?.length) {
|
|
491
|
+
node.children = children.flat().filter(Boolean);
|
|
492
|
+
}
|
|
493
|
+
return node;
|
|
494
|
+
}
|
|
495
|
+
createConditionalNode(opts, renderFn) {
|
|
496
|
+
// __ci__ 是条件渲染 - output $shown format (vivo style)
|
|
497
|
+
const shownFn = opts?.__opts__?.shown;
|
|
498
|
+
const actualRenderFn = renderFn || opts?.__render__;
|
|
499
|
+
if (typeof shownFn === 'function' && typeof actualRenderFn === 'function') {
|
|
500
|
+
const fnStr = shownFn.toString();
|
|
501
|
+
const varMatch = fnStr.match(/_vm_\.(\w+)/);
|
|
502
|
+
const shownVar = varMatch ? varMatch[1] : fnStr;
|
|
503
|
+
let shownValue = false;
|
|
504
|
+
try {
|
|
505
|
+
shownValue = !!shownFn();
|
|
506
|
+
} catch {}
|
|
507
|
+
try {
|
|
508
|
+
const rendered = actualRenderFn();
|
|
509
|
+
const children = Array.isArray(rendered) ? rendered.flat().filter(Boolean) : rendered ? [rendered] : [];
|
|
510
|
+
if (children.length > 0 && children[0]) {
|
|
511
|
+
const firstChild = children[0];
|
|
512
|
+
firstChild['$shown'] = shownVar;
|
|
513
|
+
firstChild['shown'] = shownValue;
|
|
514
|
+
if (!firstChild.bind) {
|
|
515
|
+
this.bindCounter++;
|
|
516
|
+
firstChild.bind = this.bindCounter;
|
|
517
|
+
}
|
|
518
|
+
return children;
|
|
519
|
+
}
|
|
520
|
+
} catch {}
|
|
521
|
+
}
|
|
522
|
+
if (typeof actualRenderFn === 'function') {
|
|
523
|
+
try {
|
|
524
|
+
const children = actualRenderFn();
|
|
525
|
+
return Array.isArray(children) ? children.flat().filter(Boolean) : children;
|
|
526
|
+
} catch {}
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
createForNode(opts, externalRenderFn) {
|
|
531
|
+
// __cf__ 是循环渲染 - output $repeat template (vivo style) + repeat_items snapshot
|
|
532
|
+
const expFn = opts?.__opts__?.exp;
|
|
533
|
+
const actualRenderFn = externalRenderFn || opts.__render__;
|
|
534
|
+
if (typeof actualRenderFn !== 'function') {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Get the list expression name (e.g. "activities", "shortcuts")
|
|
539
|
+
let listName = '';
|
|
540
|
+
let rawListData = null;
|
|
541
|
+
if (typeof expFn === 'function') {
|
|
542
|
+
try {
|
|
543
|
+
const expResult = expFn();
|
|
544
|
+
const raw = expResult?.__raw__ || expResult;
|
|
545
|
+
if (Array.isArray(raw)) {
|
|
546
|
+
rawListData = raw;
|
|
547
|
+
}
|
|
548
|
+
} catch {/* ignore */}
|
|
549
|
+
listName = this.extractBindingExpression(expFn);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Create $item proxy that returns binding markers for $item.xxx access
|
|
553
|
+
const self = this;
|
|
554
|
+
const itemProxy = new Proxy({}, {
|
|
555
|
+
get: (_target, prop) => {
|
|
556
|
+
if (typeof prop === 'symbol') return undefined;
|
|
557
|
+
return self.createBindingMarker(`$item.${prop}`);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
try {
|
|
561
|
+
const children = actualRenderFn(0, itemProxy);
|
|
562
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
563
|
+
const firstChild = children[0];
|
|
564
|
+
if (firstChild && typeof firstChild === 'object') {
|
|
565
|
+
firstChild.$repeat = listName || '$repeat';
|
|
566
|
+
firstChild.repeat = [];
|
|
567
|
+
if (!firstChild.bind) {
|
|
568
|
+
this.bindCounter++;
|
|
569
|
+
firstChild.bind = this.bindCounter;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Generate repeat_items static snapshot from actual data
|
|
573
|
+
if (rawListData && rawListData.length > 0) {
|
|
574
|
+
const repeatItems = [];
|
|
575
|
+
for (let i = 0; i < rawListData.length; i++) {
|
|
576
|
+
try {
|
|
577
|
+
const itemResult = actualRenderFn(i, rawListData[i]);
|
|
578
|
+
if (Array.isArray(itemResult) && itemResult[0]) {
|
|
579
|
+
const item = itemResult[0];
|
|
580
|
+
// Bake $item references in event handlers to actual values
|
|
581
|
+
this.bakeItemRefsInEvents(item, rawListData[i]);
|
|
582
|
+
repeatItems.push(item);
|
|
583
|
+
}
|
|
584
|
+
} catch {/* skip failed items */}
|
|
585
|
+
}
|
|
586
|
+
if (repeatItems.length > 0) {
|
|
587
|
+
firstChild.repeat_items = repeatItems;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return children.flat().filter(Boolean);
|
|
592
|
+
}
|
|
593
|
+
} catch {/* ignore */}
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
createBlockNode(opts) {
|
|
597
|
+
if (typeof opts.__render__ === 'function') {
|
|
598
|
+
try {
|
|
599
|
+
const children = opts.__render__();
|
|
600
|
+
return Array.isArray(children) ? children.flat().filter(Boolean) : children;
|
|
601
|
+
} catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
executeTemplate(templateFn, mockVm) {
|
|
608
|
+
try {
|
|
609
|
+
const result = templateFn(mockVm);
|
|
610
|
+
if (result && typeof result === 'object') {
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
} catch {
|
|
614
|
+
// template execution failed
|
|
615
|
+
}
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
parseInlineStyle(styleStr) {
|
|
619
|
+
return Object.fromEntries(styleStr.split(';').filter(s => s.trim()).map(s => {
|
|
620
|
+
const [key, ...rest] = s.split(':');
|
|
621
|
+
return [key.trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase()), rest.join(':').trim()];
|
|
622
|
+
}));
|
|
623
|
+
}
|
|
624
|
+
extractBindingExpression(fn) {
|
|
625
|
+
const str = fn.toString();
|
|
626
|
+
// 尝试提取 return 后的表达式
|
|
627
|
+
const match = str.match(/return\s+(.+?)[\s;]*}/);
|
|
628
|
+
if (match) {
|
|
629
|
+
return match[1].replace(/^this\./, '').replace(/_vm_\./g, '');
|
|
630
|
+
}
|
|
631
|
+
return str;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Replace $item.xxx references in event handler strings with actual values */
|
|
635
|
+
bakeItemRefsInEvents(node, itemData) {
|
|
636
|
+
if (node.events) {
|
|
637
|
+
for (const [key, handler] of Object.entries(node.events)) {
|
|
638
|
+
// Replace $item.prop with the JSON-stringified actual value
|
|
639
|
+
node.events[key] = handler.replace(/\$item\.(\w+)/g, (_match, prop) => {
|
|
640
|
+
const val = itemData[prop];
|
|
641
|
+
return typeof val === 'string' ? JSON.stringify(val) : String(val ?? 'undefined');
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (node.children) {
|
|
646
|
+
for (const child of node.children) {
|
|
647
|
+
this.bakeItemRefsInEvents(child, itemData);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Get all registered sub-component templates (excluding the bootstrapped main component)
|
|
654
|
+
*/
|
|
655
|
+
getSubComponentTemplates(mainName) {
|
|
656
|
+
const results = new Map();
|
|
657
|
+
for (const [name, comp] of this.components) {
|
|
658
|
+
if (name === mainName) continue;
|
|
659
|
+
if (!comp.template) continue;
|
|
660
|
+
const mockVm = this.createMockVM(comp);
|
|
661
|
+
try {
|
|
662
|
+
const tree = this.executeTemplate(comp.template, mockVm);
|
|
663
|
+
const compStyles = {};
|
|
664
|
+
if (comp.style) {
|
|
665
|
+
const normalized = this.normalizeStyleSheet(comp.style);
|
|
666
|
+
compStyles[0] = normalized;
|
|
667
|
+
}
|
|
668
|
+
results.set(name, {
|
|
669
|
+
tree,
|
|
670
|
+
styles: compStyles
|
|
671
|
+
});
|
|
672
|
+
} catch {}
|
|
673
|
+
}
|
|
674
|
+
return results;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
exports.default = PrerenderVM;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** css.json format: { [styleObjectId]: { ".class": { prop: value } } } */
|
|
2
|
+
export type CssJsonMap = Record<string, Record<string, Record<string, string>>>;
|
|
3
|
+
/**
|
|
4
|
+
* StyleSerializer - Converts compiled IStyleNode[] ($app_style$) into css.json format.
|
|
5
|
+
*/
|
|
6
|
+
export declare class StyleSerializer {
|
|
7
|
+
private cache;
|
|
8
|
+
selectorToString(selectors: (number | string)[][]): string;
|
|
9
|
+
parseStyleNodes(styleNodes: any[]): Record<string, Record<string, string>>;
|
|
10
|
+
private generateId;
|
|
11
|
+
serialize(styleNodes: any[]): {
|
|
12
|
+
id: string;
|
|
13
|
+
styles: Record<string, Record<string, string>>;
|
|
14
|
+
};
|
|
15
|
+
toCssJson(): CssJsonMap;
|
|
16
|
+
reset(): void;
|
|
17
|
+
}
|
|
18
|
+
export default StyleSerializer;
|