@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,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;
|