@aiot-toolkit/aiotpack 2.0.6-beta.8 → 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.
Files changed (43) hide show
  1. package/lib/afterCompile/ux/UxAfterCompile.d.ts +4 -0
  2. package/lib/afterCompile/ux/UxAfterCompile.js +90 -2
  3. package/lib/compiler/javascript/JavascriptCompiler.js +11 -4
  4. package/lib/compiler/javascript/TemplateCompiler.d.ts +29 -0
  5. package/lib/compiler/javascript/TemplateCompiler.js +564 -0
  6. package/lib/compiler/javascript/ViteCompiler.d.ts +13 -0
  7. package/lib/compiler/javascript/ViteCompiler.js +414 -0
  8. package/lib/compiler/javascript/interface/IJavascriptCompileOption.d.ts +26 -0
  9. package/lib/compiler/javascript/vela/VelaWebpackConfigurator.d.ts +3 -1
  10. package/lib/compiler/javascript/vela/VelaWebpackConfigurator.js +16 -1
  11. package/lib/compiler/javascript/vela/interface/IManifest.d.ts +12 -0
  12. package/lib/compiler/javascript/vela/plugin/WrapPlugin.d.ts +10 -1
  13. package/lib/compiler/javascript/vela/plugin/WrapPlugin.js +241 -57
  14. package/lib/compiler/javascript/vela/utils/UxCompileUtil.d.ts +3 -2
  15. package/lib/compiler/javascript/vela/utils/UxCompileUtil.js +12 -4
  16. package/lib/compiler/javascript/vela/utils/VruUtil.d.ts +50 -0
  17. package/lib/compiler/javascript/vela/utils/VruUtil.js +128 -0
  18. package/lib/compiler/javascript/vela/utils/ZipUtil.d.ts +9 -0
  19. package/lib/compiler/javascript/vela/utils/ZipUtil.js +112 -6
  20. package/lib/compiler/javascript/vela/utils/webpackLoader/WebpackJsLoader.js +1 -1
  21. package/lib/config/UxConfig.d.ts +12 -5
  22. package/lib/config/UxConfig.js +7 -6
  23. package/lib/loader/ux/JsLoader.d.ts +9 -0
  24. package/lib/loader/ux/JsLoader.js +47 -8
  25. package/lib/loader/ux/vela/HmlLoader.d.ts +6 -6
  26. package/lib/loader/ux/vela/HmlLoader.js +30 -13
  27. package/lib/prerender/PrerenderVM.d.ts +86 -0
  28. package/lib/prerender/PrerenderVM.js +677 -0
  29. package/lib/prerender/StyleSerializer.d.ts +18 -0
  30. package/lib/prerender/StyleSerializer.js +92 -0
  31. package/lib/prerender/TemplateSerializer.d.ts +26 -0
  32. package/lib/prerender/TemplateSerializer.js +122 -0
  33. package/lib/prerender/index.d.ts +20 -0
  34. package/lib/prerender/index.js +519 -0
  35. package/lib/prerender/interface/IPrerenderOption.d.ts +15 -0
  36. package/lib/prerender/interface/IPrerenderOption.js +1 -0
  37. package/lib/utils/BeforeCompileUtils.d.ts +1 -1
  38. package/lib/utils/BeforeCompileUtils.js +52 -9
  39. package/lib/utils/ux/ManifestSchema.js +0 -1
  40. package/lib/utils/ux/UxFileUtils.js +1 -1
  41. package/lib/utils/ux/UxLoaderUtils.d.ts +6 -0
  42. package/lib/utils/ux/UxLoaderUtils.js +22 -10
  43. 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;