@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,564 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getStyleObjectId = getStyleObjectId;
7
+ exports.normalizeMediaQuery = normalizeMediaQuery;
8
+ exports.parseStyleArray = parseStyleArray;
9
+ exports.uxToTemplateJson = uxToTemplateJson;
10
+ var acorn = _interopRequireWildcard(require("acorn"));
11
+ var _astring = require("astring");
12
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
+ // Template.json compilation from parse5 AST
15
+
16
+ function uxToTemplateJson(templateNode, styleObjectId, styleSheet, importMap, pageDir) {
17
+ const children = templateNode.content?.childNodes || templateNode.childNodes || [];
18
+ const rootEl = children.find(n => n.tagName && n.nodeName !== '#text');
19
+ if (!rootEl) return {};
20
+ const result = elementToJson(rootEl, styleSheet, importMap, undefined, pageDir);
21
+ // Insert styleObjectId before children (vivo format)
22
+ if (styleObjectId !== undefined) {
23
+ const ordered = {};
24
+ for (const [k, v] of Object.entries(result)) {
25
+ if (k === 'children') {
26
+ ordered.styleObjectId = styleObjectId;
27
+ }
28
+ ordered[k] = v;
29
+ }
30
+ if (!ordered.styleObjectId) ordered.styleObjectId = styleObjectId;
31
+ return ordered;
32
+ }
33
+ return result;
34
+ }
35
+ /** Add this. prefix to variable references in an expression */
36
+ // blueos-pack's pathTestRE: matches simple paths like a.b.c, a['key'], a[0], a[$idx]
37
+ const pathTestRE = /^[A-Za-z_$][\w$]*(?:\.[\w$]+|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/;
38
+ /**
39
+ * Normalize a media query condition text from CSS Level 4 syntax to Level 3.
40
+ *
41
+ * Level 4 → Level 3:
42
+ * `(width <= 30)` → `(max-width: 30)`
43
+ * `(width >= 80)` → `(min-width: 80)`
44
+ * `(400 <= width <= 700)` → `(min-width: 400) and (max-width: 700)`
45
+ * `(width < 30)` → `(max-width: 29)` // strict → off-by-one
46
+ * Same for height/aspect-ratio. Other features pass through.
47
+ *
48
+ * `screen and ` is preserved; `not` / `only` are preserved as-is.
49
+ */
50
+ function normalizeMediaQuery(condition) {
51
+ if (!condition) return condition;
52
+ // Only operate on what's inside parens; leave operators (and/or/not/only) alone.
53
+ return condition.replace(/\(([^)]+)\)/g, (_match, inner) => {
54
+ const expr = String(inner).trim();
55
+ // Range form: <num> <op1> <feature> <op2> <num>, e.g. "400 <= width <= 700"
56
+ const range = expr.match(/^(-?\d+(?:\.\d+)?)\s*(<=?|>=?)\s*([a-zA-Z-]+)\s*(<=?|>=?)\s*(-?\d+(?:\.\d+)?)$/);
57
+ if (range) {
58
+ const [, lhs, op1, feat, op2, rhs] = range;
59
+ const lo = op1 === '<=' || op1 === '<' ? lhs : rhs;
60
+ const hi = op1 === '<=' || op1 === '<' ? rhs : lhs;
61
+ // Both operators must be the same direction; otherwise fall through.
62
+ if ((op1 === '<=' || op1 === '<') && (op2 === '<=' || op2 === '<') || (op1 === '>=' || op1 === '>') && (op2 === '>=' || op2 === '>')) {
63
+ const loNum = op1 === '<' || op2 === '<' ? +lo + 1 : +lo;
64
+ const hiNum = op1 === '<' || op2 === '<' ? +hi - 1 : +hi;
65
+ return `(min-${feat}: ${loNum}) and (max-${feat}: ${hiNum})`;
66
+ }
67
+ }
68
+ // Comparison form: <feature> <op> <num> or <num> <op> <feature>
69
+ const cmp = expr.match(/^(?:([a-zA-Z-]+)\s*(<=?|>=?)\s*(-?\d+(?:\.\d+)?)|(-?\d+(?:\.\d+)?)\s*(<=?|>=?)\s*([a-zA-Z-]+))$/);
70
+ if (cmp) {
71
+ let feat, op, num;
72
+ if (cmp[1]) {
73
+ feat = cmp[1];
74
+ op = cmp[2];
75
+ num = +cmp[3];
76
+ } else {
77
+ feat = cmp[6];
78
+ op = cmp[5];
79
+ num = +cmp[4];
80
+ // Flip operator since we're moving feature to the left
81
+ op = op === '<=' ? '>=' : op === '>=' ? '<=' : op === '<' ? '>' : '<';
82
+ }
83
+ // Strict comparisons get off-by-one (CSS spec: (width < 30) means width is at most 29 in integer dp)
84
+ if (op === '<') return `(max-${feat}: ${num - 1})`;
85
+ if (op === '>') return `(min-${feat}: ${num + 1})`;
86
+ if (op === '<=') return `(max-${feat}: ${num})`;
87
+ if (op === '>=') return `(min-${feat}: ${num})`;
88
+ }
89
+ return `(${expr})`;
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Parse compiled $app_style$ array. Each rule is either:
95
+ * [selectors, props] — base rule
96
+ * [{condition: "<media-query>"}, selectors, props] — conditional rule (@media or @import with query)
97
+ *
98
+ * Returns a flat map: selectors → props for base rules, plus
99
+ * `"@media <normalizedConditionText>"` keys whose values are the per-condition
100
+ * `{selector: props}` map. The condition text is normalized to Level 3 so
101
+ * runtime only needs a Level-3 matcher.
102
+ */
103
+ function parseStyleArray(code) {
104
+ const styleSheet = {};
105
+ const ingest = parsed => {
106
+ if (!Array.isArray(parsed)) return;
107
+ for (const rule of parsed) {
108
+ if (!Array.isArray(rule) || rule.length < 2) continue;
109
+ let selectors, props, conditionText;
110
+ if (rule.length >= 3 && rule[0] && typeof rule[0] === 'object' && !Array.isArray(rule[0]) && typeof rule[0].condition === 'string') {
111
+ conditionText = rule[0].condition;
112
+ selectors = rule[1];
113
+ props = rule[2];
114
+ } else {
115
+ selectors = rule[0];
116
+ props = rule[1];
117
+ }
118
+ if (!Array.isArray(selectors)) continue;
119
+ let target = styleSheet;
120
+ if (conditionText) {
121
+ const key = `@media ${normalizeMediaQuery(conditionText)}`;
122
+ target = styleSheet[key] = styleSheet[key] || {};
123
+ }
124
+ for (const sel of selectors) {
125
+ if (Array.isArray(sel) && sel.length >= 2) {
126
+ target[sel[0] === 0 ? `.${sel[1]}` : sel[1]] = props;
127
+ }
128
+ }
129
+ }
130
+ };
131
+ try {
132
+ const jsonLike = code.replace(/'/g, '"').replace(/([{,]\s*)([a-zA-Z_$][\w$]*)\s*:/g, '$1"$2":').replace(/,\s*([}\]])/g, '$1');
133
+ ingest(JSON.parse(jsonLike));
134
+ } catch {
135
+ try {
136
+ ingest(new Function(`return ${code}`)());
137
+ } catch {}
138
+ }
139
+ return styleSheet;
140
+ }
141
+ function addThisPrefix(expr, extraReserved) {
142
+ const reserved = new Set(['true', 'false', 'null', 'undefined', 'NaN', 'Infinity', 'typeof', 'instanceof', 'new', 'delete', 'void', 'in', 'of', 'if', 'else', 'return', 'this', 'function', 'var', 'let', 'const', '$event', 'evt', 'event', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Date', 'RegExp', 'Error', 'Promise', 'parseInt', 'parseFloat', 'console']);
143
+ try {
144
+ const generate = _astring.generate;
145
+ const ast = acorn.parseExpressionAt(expr, 0, {
146
+ ecmaVersion: 2020
147
+ });
148
+ // Walk AST and add this. prefix to free identifiers
149
+ function walk(node, parent) {
150
+ if (!node || typeof node !== 'object') return;
151
+ if (node.type === 'Identifier' && !reserved.has(node.name) && !extraReserved?.has(node.name)) {
152
+ if (parent?.type === 'MemberExpression' && parent.property === node && !parent.computed) return;
153
+ if (parent?.type === 'Property' && parent.key === node && !parent.computed) return;
154
+ const origName = node.name;
155
+ node.type = 'MemberExpression';
156
+ node.object = {
157
+ type: 'ThisExpression'
158
+ };
159
+ node.property = {
160
+ type: 'Identifier',
161
+ name: origName
162
+ };
163
+ node.computed = false;
164
+ delete node.name;
165
+ }
166
+ for (const key of Object.keys(node)) {
167
+ if (key === 'type' || key === 'start' || key === 'end') continue;
168
+ const child = node[key];
169
+ if (Array.isArray(child)) child.forEach(c => c?.type && walk(c, node));else if (child?.type) walk(child, node);
170
+ }
171
+ }
172
+ walk(ast, null);
173
+ return generate(ast, {
174
+ indent: '',
175
+ lineEnd: ' '
176
+ }).trim();
177
+ } catch {
178
+ // Fallback to regex for unparseable expressions
179
+ const parts = expr.split(/('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")/);
180
+ return parts.map((part, i) => {
181
+ if (i % 2 === 1) return part;
182
+ return part.replace(/(?<![.'"\w$/])([a-zA-Z_$][a-zA-Z0-9_$]*)/g, (match, id) => {
183
+ if (reserved.has(id) || extraReserved?.has(id)) return match;
184
+ return `this.${id}`;
185
+ });
186
+ }).join('');
187
+ }
188
+ }
189
+ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
190
+ const tagName = el.tagName === 'img' ? 'image' : el.tagName;
191
+ const node = {
192
+ type: tagName
193
+ };
194
+ const attrs = el.attrs || [];
195
+ const attrObj = {};
196
+ const events = {};
197
+ let className = '';
198
+ let hasBind = false;
199
+ // Static inline style values (from style="key: val") are merged into node.style alongside
200
+ // tag/class selector styles to match vivo's behavior — only dynamic values stay in inlineStyle
201
+ const staticInlineStyle = {};
202
+ // Check if this is an imported sub-component
203
+ const isComponent = importMap && importMap[el.tagName];
204
+ for (const attr of attrs) {
205
+ let {
206
+ name,
207
+ value
208
+ } = attr;
209
+ // :attr shorthand for dynamic binding
210
+ if (name.startsWith(':') && !name.startsWith('::')) {
211
+ name = name.slice(1);
212
+ if (!value.includes('{{')) value = `{{${value}}}`;
213
+ }
214
+ if (name === 'is' && tagName === 'component') {
215
+ continue; // is attribute handled at runtime
216
+ } else if (name === 'class') {
217
+ className = value.trim();
218
+ } else if (name.startsWith('@') || name.startsWith('on') && name.length > 2) {
219
+ const rawEvtName = name.startsWith('@') ? name.slice(1) : name.replace(/^on/, '');
220
+ const evtName = rawEvtName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
221
+ // Events with parameters use $event format with function wrapper
222
+ const evtVal = value.trim();
223
+ if (evtVal.includes('(')) {
224
+ // Add evt as last parameter
225
+ const withEvt = evtVal.replace(/\)$/, evtVal.includes('()') ? 'evt)' : ', evt)');
226
+ events[`$${evtName}`] = `function (evt) { return ${addThisPrefix(withEvt)}; }`;
227
+ } else {
228
+ events[evtName] = evtVal;
229
+ }
230
+ hasBind = true;
231
+ } else if (name === 'if') {
232
+ const ifVal = value.replace(/\{\{|\}\}/g, '').trim();
233
+ if (ifVal === 'true') {
234
+ node['shown'] = true;
235
+ } else if (ifVal === 'false') {
236
+ node['shown'] = false;
237
+ } else {
238
+ node['__cond__'] = {
239
+ kind: 'if',
240
+ expr: ifVal
241
+ };
242
+ hasBind = true;
243
+ }
244
+ } else if (name === 'elif') {
245
+ const elifVal = value.replace(/\{\{|\}\}/g, '').trim();
246
+ node['__cond__'] = {
247
+ kind: 'elif',
248
+ expr: elifVal
249
+ };
250
+ hasBind = true;
251
+ } else if (name === 'else') {
252
+ node['__cond__'] = {
253
+ kind: 'else'
254
+ };
255
+ hasBind = true;
256
+ } else if (name === 'show') {
257
+ const showVal = value.replace(/\{\{|\}\}/g, '').trim();
258
+ if (showVal === 'true') attrObj['show'] = true;else if (showVal === 'false') attrObj['show'] = false;else {
259
+ // Try to evaluate as literal
260
+ try {
261
+ const v = JSON.parse(showVal);
262
+ attrObj["show"] = v;
263
+ } catch {
264
+ attrObj['$show'] = showVal;
265
+ hasBind = true;
266
+ }
267
+ }
268
+ } else if (name === 'for') {
269
+ const forVal = value.replace(/\{\{|\}\}/g, '').trim();
270
+ const inMatch = forVal.match(/^\((\w+)\s*,\s*(\w+)\)\s+in\s+(.+)$/);
271
+ const simpleInMatch = !inMatch && forVal.match(/^(\w+)\s+in\s+(.+)$/);
272
+ if (inMatch) {
273
+ // for="(key, value) in expr" syntax
274
+ const expr = inMatch[3].trim();
275
+ if (/^[a-zA-Z_$][\w$.]*$/.test(expr)) {
276
+ node['repeat'] = {
277
+ '$exp': expr,
278
+ key: inMatch[1],
279
+ value: inMatch[2]
280
+ };
281
+ } else {
282
+ node['$repeat'] = `function () { return ${addThisPrefix(expr)}; }`;
283
+ node['repeat'] = [];
284
+ }
285
+ } else if (simpleInMatch) {
286
+ // for="value in expr" syntax
287
+ const expr = simpleInMatch[2].trim();
288
+ if (/^[a-zA-Z_$][\w$.]*$/.test(expr)) {
289
+ node['repeat'] = {
290
+ '$exp': expr,
291
+ value: simpleInMatch[1]
292
+ };
293
+ } else {
294
+ node['$repeat'] = `function () { return ${addThisPrefix(expr)}; }`;
295
+ node['repeat'] = [];
296
+ }
297
+ } else if (pathTestRE.test(forVal)) {
298
+ node['$repeat'] = forVal;
299
+ node['repeat'] = [];
300
+ } else {
301
+ node['$repeat'] = `function () { return ${addThisPrefix(forVal)}; }`;
302
+ node['repeat'] = [];
303
+ }
304
+ hasBind = true;
305
+ // For-loop variables should NOT get this. prefix
306
+ if (inMatch) {
307
+ scopeVars = new Set(scopeVars);
308
+ scopeVars.add(inMatch[1]); // key
309
+ scopeVars.add(inMatch[2]); // value
310
+ } else if (simpleInMatch) {
311
+ scopeVars = new Set(scopeVars);
312
+ scopeVars.add(simpleInMatch[1]); // value
313
+ }
314
+ } else if (name === 'style') {
315
+ if (/^\{\{[^}]+\}\}$/.test(value.trim())) {
316
+ // Pure dynamic style binding: style="{{variable}}"
317
+ const inner = value.replace(/\{\{|\}\}/g, '').trim();
318
+ if (/^[a-zA-Z_$][\w$.]*$/.test(inner)) {
319
+ node['$style'] = inner;
320
+ } else {
321
+ node['$style'] = `function () { return ${addThisPrefix(inner)}; }`;
322
+ }
323
+ hasBind = true;
324
+ } else {
325
+ // Static or per-property dynamic inline style
326
+ // Static values get merged into node.style (alongside tag/class selectors)
327
+ // Dynamic values ($key: function) stay in inlineStyle
328
+ // Only set hasBind if there are dynamic values
329
+ const inlineStyle = {};
330
+ let hasDynamicInlineStyle = false;
331
+ value.split(';').forEach(decl => {
332
+ const [prop, ...vals] = decl.split(':');
333
+ if (prop && vals.length) {
334
+ const key = prop.trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
335
+ const propVal = vals.join(':').trim();
336
+ if (/\{\{[\s\S]*?\}\}/.test(propVal)) {
337
+ // Dynamic value — emit as $key: function() {...}
338
+ const parts = propVal.split(/(\{\{[\s\S]*?\}\})/);
339
+ const exprs = parts.filter(Boolean).map(p => {
340
+ const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
341
+ if (m) return addThisPrefix(m[1].trim());
342
+ return `'${p.replace(/'/g, "\\'")}'`;
343
+ });
344
+ const fullExpr = exprs.length === 1 ? exprs[0] : `'' + ${exprs.join(' + ')}`;
345
+ inlineStyle['$' + key] = `function () { return ${fullExpr}; }`;
346
+ hasDynamicInlineStyle = true;
347
+ } else {
348
+ // Static value — accumulate to staticInlineStyle for merging into node.style later
349
+ staticInlineStyle[key] = propVal;
350
+ }
351
+ }
352
+ });
353
+ if (Object.keys(inlineStyle).length) node['inlineStyle'] = inlineStyle;
354
+ if (hasDynamicInlineStyle) hasBind = true;
355
+ }
356
+ } else if (/\{\{.*\}\}/.test(value)) {
357
+ // Dynamic binding - build expression from mixed static/dynamic parts
358
+ const parts = value.split(/(\{\{[\s\S]*?\}\})/);
359
+ const exprs = parts.filter(Boolean).map(p => {
360
+ const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
361
+ if (m) return addThisPrefix(m[1].trim());
362
+ return `'${p.replace(/'/g, "\\'")}'`;
363
+ });
364
+ const camelName = '$' + name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
365
+ const plainName = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
366
+ // Handle literal values (null, true, false, numbers)
367
+ if (exprs.length === 1) {
368
+ const raw = exprs[0].replace(/^this\./, '');
369
+ if (raw === 'null') {
370
+ attrObj[plainName] = null;
371
+ hasBind = true;
372
+ } else if (raw === 'undefined') {
373
+ attrObj[plainName] = undefined;
374
+ hasBind = true;
375
+ } else if (raw === 'true') {
376
+ attrObj[plainName] = true;
377
+ hasBind = true;
378
+ } else if (raw === 'false') {
379
+ attrObj[plainName] = false;
380
+ hasBind = true;
381
+ } else if (pathTestRE.test(raw)) {
382
+ // Simple variable reference - just the name without this. prefix
383
+ attrObj[camelName] = raw;
384
+ } else {
385
+ attrObj[camelName] = `function () { return ${exprs[0]}; }`;
386
+ }
387
+ } else {
388
+ const wrappedExprs = exprs.map(e => {
389
+ if (e.startsWith("'") || e.startsWith('"')) return e; // string literal
390
+ if (/(\|\||&&|\?\s|\+)/.test(e) && !e.startsWith('(')) return `(${e})`;
391
+ return e;
392
+ });
393
+ const fullExpr = `'' + ${wrappedExprs.join(' + ')}`;
394
+ attrObj[camelName] = `function () { return ${fullExpr}; }`;
395
+ }
396
+ hasBind = true;
397
+ } else {
398
+ let staticVal = value ? value.replace(/ {2,}/g, ' ') : '';
399
+ // Normalize relative path attrs (src, href) to absolute paths from src root
400
+ if (staticVal && (name === 'src' || name === 'href') && !staticVal.startsWith('/') && !/^[a-z]+:/.test(staticVal) && pageDir) {
401
+ // Resolve relative to pageDir, then make absolute from src root
402
+ const segments = pageDir.split('/').filter(Boolean).concat(staticVal.split('/'));
403
+ const stack = [];
404
+ for (const seg of segments) {
405
+ if (seg === '..') stack.pop();else if (seg && seg !== '.') stack.push(seg);
406
+ }
407
+ staticVal = '/' + stack.join('/');
408
+ }
409
+ attrObj[name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = staticVal;
410
+ if (name === 'id') hasBind = true;
411
+ }
412
+ }
413
+ // Handle text content
414
+ for (const child of el.childNodes || []) {
415
+ const textElements = new Set(['text', 'span', 'a', 'input', 'qrcode', 'barcode', 'marquee', 'arc-text', 'option', 'button', 'label', 'textarea', 'richtext']);
416
+ if (child.value?.trim() && !child.tagName && !(el.childNodes || []).some(c => c.tagName) && textElements.has(tagName)) {
417
+ // Collapse whitespace for static text; dynamic text is trimmed separately
418
+ const text = child.value;
419
+ if (/\{\{.*\}\}/.test(text)) {
420
+ // Build function expression from template literal
421
+ // Trim surrounding whitespace before splitting (vivo ignores indentation whitespace)
422
+ const trimmedText = text.trim();
423
+ const parts = trimmedText.split(/(\{\{[\s\S]*?\}\})/);
424
+ const exprs = parts.filter(Boolean).map(p => {
425
+ const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
426
+ if (m) return addThisPrefix(m[1].trim());
427
+ return `'${p.replace(/'/g, "\\'")}'`;
428
+ });
429
+ if (exprs.length === 1 && pathTestRE.test(exprs[0].replace(/^this\./, ''))) {
430
+ attrObj['$value'] = exprs[0].replace(/^this\./, '');
431
+ } else if (exprs.length === 1) {
432
+ attrObj['$value'] = `function () { return ${exprs[0]}; }`;
433
+ } else {
434
+ const wrappedExprs = exprs.map(e => {
435
+ if (e.startsWith("'") || e.startsWith('"')) return e;
436
+ if (/(\|\||&&|\?\s|\+)/.test(e) && !e.startsWith('(')) return `(${e})`;
437
+ return e;
438
+ });
439
+ attrObj['$value'] = `function () { return '' + ${wrappedExprs.join(' + ')}; }`;
440
+ }
441
+ hasBind = true;
442
+ } else {
443
+ attrObj['value'] = text.replace(/\s*\n\s*/g, ' ');
444
+ }
445
+ }
446
+ }
447
+ // Output in vivo order: attr → bind → class → events → import → style → children
448
+ if (Object.keys(attrObj).length) node.attr = attrObj;
449
+ // Handle dynamic class before bind assignment
450
+ let hasDynClass = false;
451
+ if (className && /\{\{.*\}\}/.test(className)) {
452
+ const parts = className.split(/(\{\{[\s\S]*?\}\})/).filter(Boolean);
453
+ const arrayParts = parts.map(p => {
454
+ const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
455
+ if (m) return addThisPrefix(m[1].trim());
456
+ return p.trim().split(/\s+/).map(c => `'${c}'`).join(', ');
457
+ });
458
+ // Single dynamic variable → just the name; otherwise array format
459
+ if (arrayParts.length === 1 && pathTestRE.test(arrayParts[0].replace(/^this\./, ''))) {
460
+ node['$class'] = arrayParts[0].replace(/^this\./, '');
461
+ } else {
462
+ node['$class'] = `function () { return [${arrayParts.join(', ')}]; }`;
463
+ }
464
+ hasBind = true;
465
+ hasDynClass = true;
466
+ }
467
+ if (isComponent) {
468
+ node.bind = 3;
469
+ node['import'] = importMap[el.tagName];
470
+ } else if (node['$shown'] !== undefined || node['$repeat'] || node['repeat']?.['$exp']) {
471
+ node.bind = 2;
472
+ } else if (hasBind) {
473
+ node.bind = 1;
474
+ }
475
+ if (!hasDynClass && className) {
476
+ node.class = className;
477
+ }
478
+ if (Object.keys(events).length) node.events = events;
479
+ // Inline style from styleSheet (class selector + tag selector) + static portion of style="..."
480
+ // Only base (non-@media) selectors are inlined; @media rules are applied at runtime by SDK.
481
+ if (styleSheet) {
482
+ const merged = {};
483
+ if (styleSheet[el.tagName]) Object.assign(merged, styleSheet[el.tagName]);
484
+ if (className && !hasDynClass) {
485
+ for (const cls of className.split(/\s+/)) {
486
+ if (cls && styleSheet[`.${cls}`]) Object.assign(merged, styleSheet[`.${cls}`]);
487
+ }
488
+ }
489
+ Object.assign(merged, staticInlineStyle);
490
+ if (Object.keys(merged).length) node.style = merged;
491
+ } else if (Object.keys(staticInlineStyle).length) {
492
+ node.style = {
493
+ ...staticInlineStyle
494
+ };
495
+ }
496
+ // Process children (skip text nodes already handled above)
497
+ const childNodes = [];
498
+ const hasElementChildren = (el.childNodes || []).some(c => c.tagName);
499
+ const hasTextChildren = (el.childNodes || []).some(c => !c.tagName && c.value?.trim());
500
+ const isMixedContent = hasElementChildren && hasTextChildren && (tagName === 'text' || tagName === 'span' || tagName === 'a' || tagName === 'marquee');
501
+ for (const child of el.childNodes || []) {
502
+ if (child.tagName) {
503
+ childNodes.push(elementToJson(child, styleSheet, importMap, scopeVars, pageDir));
504
+ } else if (isMixedContent && child.value?.trim()) {
505
+ // Wrap text nodes in <span> for mixed content
506
+ const text = child.value.replace(/[ \t]*\n[ \t]*/g, ' ').replace(/ {2,}/g, ' ');
507
+ const spanNode = {
508
+ type: 'span',
509
+ attr: {
510
+ value: text
511
+ }
512
+ };
513
+ childNodes.push(spanNode);
514
+ }
515
+ }
516
+ // Resolve if/elif/else chain — emit $shown with composite negations.
517
+ // if → $shown = self
518
+ // elif → $shown = self && !prev1 && !prev2 ...
519
+ // else → $shown = !prev1 && !prev2 ...
520
+ // Each chain participant gets bind=2 (virtual fragment); else terminates the chain.
521
+ const negate = e => /^this\.[\w$.[\]'"]+$/.test(e) ? `!${e}` : `!(${e})`;
522
+ let prevConds = [];
523
+ for (const child of childNodes) {
524
+ const cond = child['__cond__'];
525
+ if (!cond) {
526
+ prevConds = [];
527
+ continue;
528
+ }
529
+ delete child['__cond__'];
530
+ if (cond.kind === 'if') {
531
+ const expr = cond.expr;
532
+ child['$shown'] = pathTestRE.test(expr) ? expr : `function () { return ${addThisPrefix(expr)}; }`;
533
+ child['shown'] = false;
534
+ child['bind'] = 2;
535
+ prevConds = [addThisPrefix(expr)];
536
+ } else if (cond.kind === 'elif') {
537
+ const selfExpr = addThisPrefix(cond.expr);
538
+ const negs = prevConds.map(negate).join(' && ');
539
+ child['$shown'] = `function () { return ${selfExpr}${negs ? ' && ' + negs : ''}; }`;
540
+ child['shown'] = false;
541
+ child['bind'] = 2;
542
+ prevConds.push(selfExpr);
543
+ } else if (cond.kind === 'else') {
544
+ const negs = prevConds.map(negate).join(' && ');
545
+ child['$shown'] = `function () { return ${negs || 'true'}; }`;
546
+ child['shown'] = false;
547
+ child['bind'] = 2;
548
+ prevConds = [];
549
+ }
550
+ }
551
+ if (childNodes.length) node.children = childNodes;
552
+ return node;
553
+ }
554
+ /** Convert CSS text to css.json format */
555
+
556
+ /** Compute styleObjectId hash from a string (same algorithm as blueos-pack) */
557
+ function getStyleObjectId(name) {
558
+ let h = 0;
559
+ for (let i = 0; i < name.length; i++) {
560
+ h = (h << 5) - h + name.charCodeAt(i);
561
+ h = h & h;
562
+ }
563
+ return Math.abs(h);
564
+ }
@@ -0,0 +1,13 @@
1
+ import IJavascriptCompileOption from './interface/IJavascriptCompileOption';
2
+ import ICompileParam from '../interface/ICompileParam';
3
+ import ICompiler from '../interface/ICompiler';
4
+ import { ILog } from '@aiot-toolkit/shared-utils';
5
+ import { IFileLaneContext } from 'file-lane';
6
+ declare class ViteCompiler implements ICompiler {
7
+ private readonly context;
8
+ private readonly onLog?;
9
+ constructor(context: IFileLaneContext, onLog?: ((log: ILog[]) => void) | undefined);
10
+ compile(param: IJavascriptCompileOption): Promise<void>;
11
+ clean(param: ICompileParam & IJavascriptCompileOption): Promise<void>;
12
+ }
13
+ export default ViteCompiler;