@aiot-toolkit/aiotpack 2.1.0-prender.1 → 2.1.0-prender.10
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 +0 -4
- package/lib/afterCompile/ux/UxAfterCompile.js +10 -70
- package/lib/compiler/javascript/TemplateCompiler.d.ts +0 -10
- package/lib/compiler/javascript/TemplateCompiler.js +383 -57
- package/lib/compiler/javascript/ViteCompiler.d.ts +4 -0
- package/lib/compiler/javascript/ViteCompiler.js +398 -68
- package/lib/compiler/javascript/interface/IJavascriptCompileOption.d.ts +0 -17
- package/lib/compiler/javascript/vela/utils/Jsc.d.ts +3 -3
- package/lib/compiler/javascript/vela/utils/Jsc.js +87 -4
- package/lib/compiler/javascript/vela/utils/ZipUtil.d.ts +4 -1
- package/lib/compiler/javascript/vela/utils/ZipUtil.js +23 -5
- package/lib/config/UxConfig.js +0 -3
- package/package.json +6 -6
- package/lib/prerender/PrerenderVM.d.ts +0 -86
- package/lib/prerender/PrerenderVM.js +0 -677
- package/lib/prerender/StyleSerializer.d.ts +0 -18
- package/lib/prerender/StyleSerializer.js +0 -92
- package/lib/prerender/TemplateSerializer.d.ts +0 -26
- package/lib/prerender/TemplateSerializer.js +0 -122
- package/lib/prerender/index.d.ts +0 -20
- package/lib/prerender/index.js +0 -519
- package/lib/prerender/interface/IPrerenderOption.d.ts +0 -15
- package/lib/prerender/interface/IPrerenderOption.js +0 -1
|
@@ -6,10 +6,6 @@ import IJavascriptCompileOption from '../../compiler/javascript/interface/IJavas
|
|
|
6
6
|
declare class UxAfterCompile {
|
|
7
7
|
static compileJavascript: FollowWork<IJavascriptCompileOption>;
|
|
8
8
|
static protobuf: FollowWork<IJavascriptCompileOption>;
|
|
9
|
-
/**
|
|
10
|
-
* 预渲染:在 VM 沙箱中执行编译后的模板函数,生成 .template.json + .css.json
|
|
11
|
-
*/
|
|
12
|
-
static prerender: FollowWork<IJavascriptCompileOption>;
|
|
13
9
|
static jsc: FollowWork<IJavascriptCompileOption>;
|
|
14
10
|
static toRpk: FollowWork<IJavascriptCompileOption>;
|
|
15
11
|
/**
|
|
@@ -19,7 +19,6 @@ var _ZipFileUtil = _interopRequireDefault(require("@aiot-toolkit/shared-utils/li
|
|
|
19
19
|
var _ImageIcu = _interopRequireDefault(require("../../compiler/tools/icu/ImageIcu"));
|
|
20
20
|
var _LiteCard = _interopRequireDefault(require("../../compiler/javascript/vela/utils/LiteCard"));
|
|
21
21
|
var _UxUtil = _interopRequireDefault(require("@aiot-toolkit/parser/lib/ux/utils/UxUtil"));
|
|
22
|
-
var _prerender = require("../../prerender");
|
|
23
22
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
24
23
|
const BinaryPlugin = require('@aiot-toolkit/parser/lib/ux/translate/vela/protobuf/BinaryPlugin');
|
|
25
24
|
|
|
@@ -45,11 +44,16 @@ class UxAfterCompile {
|
|
|
45
44
|
level: _sharedUtils.Loglevel.SUCCESS,
|
|
46
45
|
message: ['javascript compile success']
|
|
47
46
|
}]);
|
|
48
|
-
}).catch(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
}).catch(err => {
|
|
48
|
+
const errors = err?.errors;
|
|
49
|
+
const warnings = err?.warnings;
|
|
50
|
+
if (!errors?.length && !warnings?.length) {
|
|
51
|
+
onLog?.([{
|
|
52
|
+
level: _sharedUtils.Loglevel.THROW,
|
|
53
|
+
message: [`javascript compile failed: ${err?.message ?? err}`]
|
|
54
|
+
}]);
|
|
55
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
56
|
+
}
|
|
53
57
|
const errorLength = errors?.length || 0;
|
|
54
58
|
const messages = [`webpack error:\r\n`];
|
|
55
59
|
if (errors?.length) {
|
|
@@ -90,70 +94,6 @@ class UxAfterCompile {
|
|
|
90
94
|
BinaryPlugin.createBinFiles();
|
|
91
95
|
}
|
|
92
96
|
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 预渲染:在 VM 沙箱中执行编译后的模板函数,生成 .template.json + .css.json
|
|
96
|
-
*/
|
|
97
|
-
static prerender = async params => {
|
|
98
|
-
const {
|
|
99
|
-
compilerOption,
|
|
100
|
-
onLog,
|
|
101
|
-
compilation
|
|
102
|
-
} = params;
|
|
103
|
-
if (!compilerOption || compilerOption.enablePrerender === false) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// In watch mode, clear prerender caches for changed pages
|
|
108
|
-
if (compilation && compilation.trigger !== _FileLaneTriggerType.default.START) {
|
|
109
|
-
const {
|
|
110
|
-
sourceRoot
|
|
111
|
-
} = compilerOption;
|
|
112
|
-
const srcPath = _path.default.join(compilerOption.projectPath, sourceRoot);
|
|
113
|
-
const changedPages = (compilation.changedFiles || []).filter(f => compilation.entries?.includes(f)).map(f => {
|
|
114
|
-
const rel = _path.default.relative(srcPath, f).replace(/\\/g, '/');
|
|
115
|
-
return rel.replace(/\.[^.]+$/, '');
|
|
116
|
-
});
|
|
117
|
-
(0, _prerender.watchChange)(changedPages.length > 0 ? changedPages : undefined);
|
|
118
|
-
}
|
|
119
|
-
const {
|
|
120
|
-
projectPath,
|
|
121
|
-
outputPath,
|
|
122
|
-
sourceRoot
|
|
123
|
-
} = compilerOption;
|
|
124
|
-
const buildPath = _path.default.join(projectPath, outputPath);
|
|
125
|
-
const manifestPath = _path.default.join(projectPath, sourceRoot, 'manifest.json');
|
|
126
|
-
if (!_fsExtra.default.existsSync(manifestPath)) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const excludeList = compilerOption.prerenderExclude || [];
|
|
130
|
-
const manifest = _fsExtra.default.readJSONSync(manifestPath);
|
|
131
|
-
const pages = Object.keys(manifest.router?.pages || {}).filter(pageName => {
|
|
132
|
-
if (excludeList.some(pattern => pageName.startsWith(pattern))) {
|
|
133
|
-
onLog?.([{
|
|
134
|
-
level: _sharedUtils.Loglevel.INFO,
|
|
135
|
-
message: [`prerender: skip excluded ${pageName}`]
|
|
136
|
-
}]);
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
return true;
|
|
140
|
-
}).map(pageName => {
|
|
141
|
-
const pageConf = manifest.router.pages[pageName];
|
|
142
|
-
const component = pageConf.component || 'index';
|
|
143
|
-
return `${pageName}/${component}`;
|
|
144
|
-
});
|
|
145
|
-
if (!pages.length) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const deviceTypes = manifest.deviceTypeList?.length ? manifest.deviceTypeList : ['default'];
|
|
149
|
-
await (0, _prerender.prerender)({
|
|
150
|
-
buildPath,
|
|
151
|
-
sourcePath: _path.default.join(projectPath, sourceRoot),
|
|
152
|
-
pages,
|
|
153
|
-
prerenderSubComp: compilerOption.prerenderSubComp !== false,
|
|
154
|
-
deviceTypes
|
|
155
|
-
}, onLog);
|
|
156
|
-
};
|
|
157
97
|
static jsc = async params => {
|
|
158
98
|
const {
|
|
159
99
|
context,
|
|
@@ -12,16 +12,6 @@ declare function uxToTemplateJson(templateNode: any, styleObjectId?: number, sty
|
|
|
12
12
|
* `screen and ` is preserved; `not` / `only` are preserved as-is.
|
|
13
13
|
*/
|
|
14
14
|
declare function normalizeMediaQuery(condition: string): string;
|
|
15
|
-
/**
|
|
16
|
-
* Parse compiled $app_style$ array. Each rule is either:
|
|
17
|
-
* [selectors, props] — base rule
|
|
18
|
-
* [{condition: "<media-query>"}, selectors, props] — conditional rule (@media or @import with query)
|
|
19
|
-
*
|
|
20
|
-
* Returns a flat map: selectors → props for base rules, plus
|
|
21
|
-
* `"@media <normalizedConditionText>"` keys whose values are the per-condition
|
|
22
|
-
* `{selector: props}` map. The condition text is normalized to Level 3 so
|
|
23
|
-
* runtime only needs a Level-3 matcher.
|
|
24
|
-
*/
|
|
25
15
|
declare function parseStyleArray(code: string): Record<string, any>;
|
|
26
16
|
/** Convert CSS text to css.json format */
|
|
27
17
|
export { uxToTemplateJson, parseStyleArray, normalizeMediaQuery };
|
|
@@ -13,6 +13,149 @@ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return
|
|
|
13
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
14
|
// Template.json compilation from parse5 AST
|
|
15
15
|
|
|
16
|
+
// A stylesheet has an applicable @MEDIA rule for a node when any @MEDIA entry
|
|
17
|
+
// carries a selector that matches the node's tag or one of its static class
|
|
18
|
+
// tokens (either directly or as a component of a descendant/combinator sel).
|
|
19
|
+
function hasApplicableMediaRule(styleSheet, tagName, classTokens) {
|
|
20
|
+
const media = styleSheet?.['@MEDIA'];
|
|
21
|
+
if (!Array.isArray(media)) return false;
|
|
22
|
+
const classSet = new Set(classTokens);
|
|
23
|
+
for (const entry of media) {
|
|
24
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
25
|
+
for (const sel of Object.keys(entry)) {
|
|
26
|
+
if (sel === 'condition') continue;
|
|
27
|
+
for (const part of sel.split(/[\s>+~]/).map(s => s.trim()).filter(Boolean)) {
|
|
28
|
+
if (part.startsWith('.')) {
|
|
29
|
+
if (classSet.has(part.slice(1))) return true;
|
|
30
|
+
} else if (!part.startsWith('#')) {
|
|
31
|
+
if (part === tagName) return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// vivo blueos-pack's aggressive camelCase applied to transition-property values.
|
|
40
|
+
// Every letter that follows a non-letter character (separator or digit) is upper-cased,
|
|
41
|
+
// then all non-alphanumeric characters are dropped. This intentionally covers both the
|
|
42
|
+
// simple case (`background-position` → `backgroundPosition`) and the odd shorthand-inside-
|
|
43
|
+
// transition-property form we see in inline styles (`transform-origin 0.2s ease-in-out .5s`
|
|
44
|
+
// → `transformOrigin02SEaseInOut5S`).
|
|
45
|
+
function normalizeTransitionPropertyValue(s) {
|
|
46
|
+
let out = '';
|
|
47
|
+
let capNext = false;
|
|
48
|
+
for (const ch of s) {
|
|
49
|
+
if (/[A-Za-z]/.test(ch)) {
|
|
50
|
+
out += capNext ? ch.toUpperCase() : ch;
|
|
51
|
+
capNext = false;
|
|
52
|
+
} else if (/[0-9]/.test(ch)) {
|
|
53
|
+
out += ch;
|
|
54
|
+
capNext = true;
|
|
55
|
+
} else {
|
|
56
|
+
capNext = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// vivo blueos-pack's inline `animation-timing-function` normalization.
|
|
63
|
+
// Returns:
|
|
64
|
+
// - string → the normalized value to emit
|
|
65
|
+
// - null (drop) → invalid; caller should skip this inline decl so the class-level
|
|
66
|
+
// value (or default) applies
|
|
67
|
+
// Rules (traced empirically from build-vivo/watch-round baseline):
|
|
68
|
+
// - Predefined keywords (`linear`, `ease`, `ease-in`, `ease-out`, `ease-in-out`,
|
|
69
|
+
// `step-start`, `step-end`) → keep as-is
|
|
70
|
+
// - `steps(N)` → normalize to `steps(N, end)` (fill default dir)
|
|
71
|
+
// - `steps(N, start|end)` → keep as-is
|
|
72
|
+
// - `cubic-bezier(a, b, c, d)`
|
|
73
|
+
// * all 4 numeric and x1,x2 ∈ [0,1] → keep (spec-compliant)
|
|
74
|
+
// * otherwise → drop (invalid per vivo validator)
|
|
75
|
+
// - Any other unrecognized function → drop
|
|
76
|
+
function normalizeAnimationTimingFunction(s) {
|
|
77
|
+
const trimmed = s.trim();
|
|
78
|
+
if (['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'step-start', 'step-end'].includes(trimmed)) return trimmed;
|
|
79
|
+
const stepsMatch = /^steps\s*\(\s*([^,)]+?)\s*(?:,\s*(start|end)\s*)?\)$/.exec(trimmed);
|
|
80
|
+
if (stepsMatch) {
|
|
81
|
+
const n = stepsMatch[1];
|
|
82
|
+
const dir = stepsMatch[2] || 'end';
|
|
83
|
+
return `steps(${n}, ${dir})`;
|
|
84
|
+
}
|
|
85
|
+
const cubicMatch = /^cubic-bezier\s*\(\s*([^)]+)\)$/.exec(trimmed);
|
|
86
|
+
if (cubicMatch) {
|
|
87
|
+
const args = cubicMatch[1].split(',').map(a => a.trim());
|
|
88
|
+
if (args.length !== 4) return null;
|
|
89
|
+
const nums = args.map(a => Number(a));
|
|
90
|
+
if (nums.some(n => !isFinite(n))) return null;
|
|
91
|
+
// spec: x1 and x2 must be in [0, 1]; y1 and y2 unbounded. Reproduce vivo behavior:
|
|
92
|
+
// any component outside [0, 1] is treated as invalid (vivo is stricter than spec).
|
|
93
|
+
if (nums.some(n => n < 0 || n > 1)) return null;
|
|
94
|
+
return `cubic-bezier(${args.join(', ')})`;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// `transform: translate(40px) scale(0.5) ...` (CSS function-list from inline style="")
|
|
100
|
+
// → `{"translateX":"40px","translateY":"0px","scaleX":0.5,"scaleY":0.5}`.
|
|
101
|
+
// Returns null when input isn't a plain CSS function list (already JSON, dynamic
|
|
102
|
+
// binding, or unknown function), so callers can fall back to the raw string.
|
|
103
|
+
function normalizeCssTransform(input) {
|
|
104
|
+
if (typeof input !== 'string') return null;
|
|
105
|
+
const s = input.trim();
|
|
106
|
+
if (!s || s.startsWith('{') || /\{\{/.test(s)) return null;
|
|
107
|
+
const out = {};
|
|
108
|
+
const funcRe = /(\w+)\s*\(([^)]*)\)/g;
|
|
109
|
+
let matched = false;
|
|
110
|
+
let m;
|
|
111
|
+
const toNum = x => /^-?\d+(?:\.\d+)?$/.test(x) ? Number(x) : x;
|
|
112
|
+
// Add a CSS unit to bare numeric arguments; keep existing unit / percent forms.
|
|
113
|
+
const withUnit = (x, defaultUnit) => {
|
|
114
|
+
if (x === undefined) return '0' + defaultUnit;
|
|
115
|
+
return /^-?\d+(?:\.\d+)?$/.test(x) ? x + defaultUnit : x;
|
|
116
|
+
};
|
|
117
|
+
while ((m = funcRe.exec(s)) !== null) {
|
|
118
|
+
matched = true;
|
|
119
|
+
const fn = m[1];
|
|
120
|
+
const args = m[2].split(',').map(a => a.trim()).filter(Boolean);
|
|
121
|
+
switch (fn) {
|
|
122
|
+
case 'translate':
|
|
123
|
+
out.translateX = withUnit(args[0], 'px');
|
|
124
|
+
out.translateY = withUnit(args[1], 'px');
|
|
125
|
+
break;
|
|
126
|
+
case 'scale':
|
|
127
|
+
{
|
|
128
|
+
const x = args[0] ?? '1';
|
|
129
|
+
out.scaleX = toNum(x);
|
|
130
|
+
out.scaleY = args[1] !== undefined ? toNum(args[1]) : toNum(x);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'translateX':
|
|
134
|
+
case 'translateY':
|
|
135
|
+
case 'translateZ':
|
|
136
|
+
out[fn] = withUnit(args[0], 'px');
|
|
137
|
+
break;
|
|
138
|
+
case 'scaleX':
|
|
139
|
+
case 'scaleY':
|
|
140
|
+
out[fn] = toNum(args[0] ?? '1');
|
|
141
|
+
break;
|
|
142
|
+
case 'rotate':
|
|
143
|
+
case 'rotateX':
|
|
144
|
+
case 'rotateY':
|
|
145
|
+
case 'rotateZ':
|
|
146
|
+
case 'skew':
|
|
147
|
+
case 'skewX':
|
|
148
|
+
case 'skewY':
|
|
149
|
+
// Only the first arg is meaningful for single-axis rotate/skew; vivo silently
|
|
150
|
+
// discards extras (e.g. `rotate(90deg,120deg)` → `rotate: "90deg"`). We follow suit.
|
|
151
|
+
out[fn] = withUnit(args[0], 'deg');
|
|
152
|
+
break;
|
|
153
|
+
default:
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return matched ? JSON.stringify(out) : null;
|
|
158
|
+
}
|
|
16
159
|
function uxToTemplateJson(templateNode, styleObjectId, styleSheet, importMap, pageDir) {
|
|
17
160
|
const children = templateNode.content?.childNodes || templateNode.childNodes || [];
|
|
18
161
|
const rootEl = children.find(n => n.tagName && n.nodeName !== '#text');
|
|
@@ -100,10 +243,74 @@ function normalizeMediaQuery(condition) {
|
|
|
100
243
|
* `{selector: props}` map. The condition text is normalized to Level 3 so
|
|
101
244
|
* runtime only needs a Level-3 matcher.
|
|
102
245
|
*/
|
|
246
|
+
const NAMED_COLORS = new Set(['transparent', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']);
|
|
247
|
+
const COLOR_PROP_RE = /(?:^|[a-z])[Cc]olor$/;
|
|
248
|
+
const NON_NAMED_COLOR_VALUE_RE = /^(?:#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|currentColor)/;
|
|
103
249
|
function parseStyleArray(code) {
|
|
104
250
|
const styleSheet = {};
|
|
251
|
+
const expandShortHex = value => {
|
|
252
|
+
const short = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])?$/.exec(value);
|
|
253
|
+
if (!short) return value;
|
|
254
|
+
const r = short[1] + short[1];
|
|
255
|
+
const g = short[2] + short[2];
|
|
256
|
+
const b = short[3] + short[3];
|
|
257
|
+
const a = short[4] ? short[4] + short[4] : '';
|
|
258
|
+
return ('#' + r + g + b + a).toLowerCase();
|
|
259
|
+
};
|
|
260
|
+
const normalizeTransform = obj => {
|
|
261
|
+
if (!obj || typeof obj !== 'object') return typeof obj === 'string' ? obj : JSON.stringify(obj);
|
|
262
|
+
const out = {};
|
|
263
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
264
|
+
out[k] = typeof v === 'number' && /^translate[XYZ]?$/.test(k) ? `${v}px` : v;
|
|
265
|
+
}
|
|
266
|
+
return JSON.stringify(out);
|
|
267
|
+
};
|
|
268
|
+
const expandKeyframeSteps = value => {
|
|
269
|
+
let arr = [];
|
|
270
|
+
if (typeof value === 'string') {
|
|
271
|
+
try {
|
|
272
|
+
arr = JSON.parse(value);
|
|
273
|
+
} catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
} else if (Array.isArray(value)) {
|
|
277
|
+
arr = value;
|
|
278
|
+
} else {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
return arr.map(step => {
|
|
282
|
+
const out = {};
|
|
283
|
+
const entries = Object.entries(step ?? {});
|
|
284
|
+
for (const [k, v] of entries) {
|
|
285
|
+
if (k === 'time') continue;
|
|
286
|
+
out[k] = k === 'transform' && v && typeof v === 'object' ? normalizeTransform(v) : v;
|
|
287
|
+
}
|
|
288
|
+
if ('time' in (step ?? {})) out.time = step.time;
|
|
289
|
+
return out;
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
const normalizeStyleProps = props => {
|
|
293
|
+
if (!props || typeof props !== 'object') return props;
|
|
294
|
+
const out = {};
|
|
295
|
+
for (const [k, v] of Object.entries(props)) {
|
|
296
|
+
if ((k === 'animationIterationCount' || k === 'iterationCount') && v === 'infinite') {
|
|
297
|
+
out[k] = -1;
|
|
298
|
+
} else if (typeof v === 'string' && COLOR_PROP_RE.test(k)) {
|
|
299
|
+
if (NAMED_COLORS.has(v.toLowerCase())) {
|
|
300
|
+
out[k] = v;
|
|
301
|
+
} else if (NON_NAMED_COLOR_VALUE_RE.test(v)) {
|
|
302
|
+
out[k] = expandShortHex(v);
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
out[k] = v;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return out;
|
|
309
|
+
};
|
|
105
310
|
const ingest = parsed => {
|
|
106
311
|
if (!Array.isArray(parsed)) return;
|
|
312
|
+
let currentMediaCondition = null;
|
|
313
|
+
let currentMediaEntry = null;
|
|
107
314
|
for (const rule of parsed) {
|
|
108
315
|
if (!Array.isArray(rule) || rule.length < 2) continue;
|
|
109
316
|
let selectors, props, conditionText;
|
|
@@ -115,16 +322,47 @@ function parseStyleArray(code) {
|
|
|
115
322
|
selectors = rule[0];
|
|
116
323
|
props = rule[1];
|
|
117
324
|
}
|
|
118
|
-
if (!Array.isArray(selectors)) continue;
|
|
119
|
-
let target
|
|
325
|
+
if (!Array.isArray(selectors) || selectors.length === 0) continue;
|
|
326
|
+
let target;
|
|
120
327
|
if (conditionText) {
|
|
121
|
-
const
|
|
122
|
-
|
|
328
|
+
const normalized = normalizeMediaQuery(conditionText);
|
|
329
|
+
if (currentMediaCondition !== normalized) {
|
|
330
|
+
const list = styleSheet['@MEDIA'] = styleSheet['@MEDIA'] || [];
|
|
331
|
+
currentMediaEntry = {
|
|
332
|
+
condition: normalized
|
|
333
|
+
};
|
|
334
|
+
list.push(currentMediaEntry);
|
|
335
|
+
currentMediaCondition = normalized;
|
|
336
|
+
}
|
|
337
|
+
target = currentMediaEntry;
|
|
338
|
+
} else {
|
|
339
|
+
target = styleSheet;
|
|
340
|
+
currentMediaCondition = null;
|
|
341
|
+
currentMediaEntry = null;
|
|
123
342
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
343
|
+
const normalizedProps = normalizeStyleProps(props);
|
|
344
|
+
const firstSel = selectors[0];
|
|
345
|
+
if (Array.isArray(firstSel) && firstSel.length >= 2 && firstSel[0] === 3) {
|
|
346
|
+
const bucket = target['@KEYFRAMES'] = target['@KEYFRAMES'] || {};
|
|
347
|
+
bucket[firstSel[1]] = expandKeyframeSteps(props?.keyframes ?? '[]');
|
|
348
|
+
} else if (Array.isArray(firstSel) && firstSel.length >= 2 && firstSel[0] === 4) {
|
|
349
|
+
const bucket = target['@FONT-FACE'] = target['@FONT-FACE'] || {};
|
|
350
|
+
bucket[firstSel[1]] = props?.fontface ?? normalizedProps;
|
|
351
|
+
} else {
|
|
352
|
+
const pieces = [];
|
|
353
|
+
for (let i = 0; i < selectors.length; i++) {
|
|
354
|
+
const sel = selectors[i];
|
|
355
|
+
if (!Array.isArray(sel) || sel.length < 2) continue;
|
|
356
|
+
if (sel[0] === 5) {
|
|
357
|
+
pieces.push(String(sel[1]));
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (pieces.length && pieces[pieces.length - 1] !== '>' && pieces[pieces.length - 1] !== '~' && pieces[pieces.length - 1] !== '+') {
|
|
361
|
+
pieces.push(' ');
|
|
362
|
+
}
|
|
363
|
+
if (sel[0] === 0) pieces.push(`.${sel[1]}`);else if (sel[0] === 1) pieces.push(`#${sel[1]}`);else pieces.push(String(sel[1]));
|
|
127
364
|
}
|
|
365
|
+
if (pieces.length) target[pieces.join('')] = normalizedProps;
|
|
128
366
|
}
|
|
129
367
|
}
|
|
130
368
|
};
|
|
@@ -186,6 +424,50 @@ function addThisPrefix(expr, extraReserved) {
|
|
|
186
424
|
}).join('');
|
|
187
425
|
}
|
|
188
426
|
}
|
|
427
|
+
function emitTextValueAttr(rawText) {
|
|
428
|
+
const text = rawText.trim();
|
|
429
|
+
if (!/\{\{[\s\S]*?\}\}/.test(text)) {
|
|
430
|
+
return {
|
|
431
|
+
attr: {
|
|
432
|
+
value: text.replace(/\s*\n\s*/g, ' ')
|
|
433
|
+
},
|
|
434
|
+
needsBind: false
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const parts = text.split(/(\{\{[\s\S]*?\}\})/);
|
|
438
|
+
const exprs = parts.filter(Boolean).map(p => {
|
|
439
|
+
const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
|
|
440
|
+
if (m) return addThisPrefix(m[1].trim());
|
|
441
|
+
return `'${p.replace(/'/g, "\\'")}'`;
|
|
442
|
+
});
|
|
443
|
+
if (exprs.length === 1 && pathTestRE.test(exprs[0].replace(/^this\./, ''))) {
|
|
444
|
+
return {
|
|
445
|
+
attr: {
|
|
446
|
+
$value: exprs[0].replace(/^this\./, '')
|
|
447
|
+
},
|
|
448
|
+
needsBind: true
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (exprs.length === 1) {
|
|
452
|
+
return {
|
|
453
|
+
attr: {
|
|
454
|
+
$value: `function () { return ${exprs[0]}; }`
|
|
455
|
+
},
|
|
456
|
+
needsBind: true
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const wrapped = exprs.map(e => {
|
|
460
|
+
if (e.startsWith("'") || e.startsWith('"')) return e;
|
|
461
|
+
if (/(\|\||&&|\?\s|\+)/.test(e) && !e.startsWith('(')) return `(${e})`;
|
|
462
|
+
return e;
|
|
463
|
+
});
|
|
464
|
+
return {
|
|
465
|
+
attr: {
|
|
466
|
+
$value: `function () { return '' + ${wrapped.join(' + ')}; }`
|
|
467
|
+
},
|
|
468
|
+
needsBind: true
|
|
469
|
+
};
|
|
470
|
+
}
|
|
189
471
|
function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
190
472
|
const tagName = el.tagName === 'img' ? 'image' : el.tagName;
|
|
191
473
|
const node = {
|
|
@@ -214,7 +496,7 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
214
496
|
if (name === 'is' && tagName === 'component') {
|
|
215
497
|
continue; // is attribute handled at runtime
|
|
216
498
|
} else if (name === 'class') {
|
|
217
|
-
className = value.trim();
|
|
499
|
+
className = value.replace(/\s+/g, ' ').trim();
|
|
218
500
|
} else if (name.startsWith('@') || name.startsWith('on') && name.length > 2) {
|
|
219
501
|
const rawEvtName = name.startsWith('@') ? name.slice(1) : name.replace(/^on/, '');
|
|
220
502
|
const evtName = rawEvtName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -346,7 +628,17 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
346
628
|
hasDynamicInlineStyle = true;
|
|
347
629
|
} else {
|
|
348
630
|
// Static value — accumulate to staticInlineStyle for merging into node.style later
|
|
349
|
-
|
|
631
|
+
if (key === 'transform') {
|
|
632
|
+
staticInlineStyle[key] = normalizeCssTransform(propVal) ?? propVal;
|
|
633
|
+
} else if (key === 'transitionProperty') {
|
|
634
|
+
staticInlineStyle[key] = normalizeTransitionPropertyValue(propVal);
|
|
635
|
+
} else if (key === 'animationTimingFunction' || key === 'transitionTimingFunction') {
|
|
636
|
+
const normalized = normalizeAnimationTimingFunction(propVal);
|
|
637
|
+
if (normalized !== null) staticInlineStyle[key] = normalized;
|
|
638
|
+
// else drop — invalid inline value; class-level rule (or default) applies
|
|
639
|
+
} else {
|
|
640
|
+
staticInlineStyle[key] = propVal;
|
|
641
|
+
}
|
|
350
642
|
}
|
|
351
643
|
}
|
|
352
644
|
});
|
|
@@ -396,8 +688,11 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
396
688
|
hasBind = true;
|
|
397
689
|
} else {
|
|
398
690
|
let staticVal = value ? value.replace(/ {2,}/g, ' ') : '';
|
|
399
|
-
// Normalize relative path attrs (src, href) to absolute paths from src root
|
|
400
|
-
|
|
691
|
+
// Normalize relative path attrs (src, href, alt) to absolute paths from src root.
|
|
692
|
+
// alt can also carry prose (e.g. "blank") — only rewrite when the value starts with
|
|
693
|
+
// ./ or ../ so image alt-text prose passes through untouched.
|
|
694
|
+
const isRelPath = staticVal.startsWith('./') || staticVal.startsWith('../');
|
|
695
|
+
if (staticVal && isRelPath && (name === 'src' || name === 'href' || name === 'alt') && pageDir) {
|
|
401
696
|
// Resolve relative to pageDir, then make absolute from src root
|
|
402
697
|
const segments = pageDir.split('/').filter(Boolean).concat(staticVal.split('/'));
|
|
403
698
|
const stack = [];
|
|
@@ -414,34 +709,9 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
414
709
|
for (const child of el.childNodes || []) {
|
|
415
710
|
const textElements = new Set(['text', 'span', 'a', 'input', 'qrcode', 'barcode', 'marquee', 'arc-text', 'option', 'button', 'label', 'textarea', 'richtext']);
|
|
416
711
|
if (child.value?.trim() && !child.tagName && !(el.childNodes || []).some(c => c.tagName) && textElements.has(tagName)) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (
|
|
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
|
-
}
|
|
712
|
+
const emitted = emitTextValueAttr(child.value);
|
|
713
|
+
Object.assign(attrObj, emitted.attr);
|
|
714
|
+
if (emitted.needsBind) hasBind = true;
|
|
445
715
|
}
|
|
446
716
|
}
|
|
447
717
|
// Output in vivo order: attr → bind → class → events → import → style → children
|
|
@@ -464,30 +734,88 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
464
734
|
hasBind = true;
|
|
465
735
|
hasDynClass = true;
|
|
466
736
|
}
|
|
467
|
-
if (
|
|
468
|
-
node.bind = 3;
|
|
469
|
-
node['import'] = importMap[el.tagName];
|
|
470
|
-
} else if (node['$shown'] !== undefined || node['$repeat'] || node['repeat']?.['$exp']) {
|
|
737
|
+
if (node['$shown'] !== undefined || node['$repeat'] !== undefined || node['repeat']?.['$exp']) {
|
|
471
738
|
node.bind = 2;
|
|
739
|
+
} else if (isComponent) {
|
|
740
|
+
node.bind = 3;
|
|
472
741
|
} else if (hasBind) {
|
|
473
742
|
node.bind = 1;
|
|
474
743
|
}
|
|
744
|
+
if (isComponent) {
|
|
745
|
+
node['import'] = importMap[el.tagName];
|
|
746
|
+
}
|
|
475
747
|
if (!hasDynClass && className) {
|
|
476
748
|
node.class = className;
|
|
477
749
|
}
|
|
478
750
|
if (Object.keys(events).length) node.events = events;
|
|
479
|
-
// Inline style from styleSheet (class selector + tag selector) + static portion of style="..."
|
|
480
|
-
//
|
|
751
|
+
// Inline style from styleSheet (class selector + tag selector) + static portion of style="...".
|
|
752
|
+
// - Static class only: merge tag + class rules + staticInlineStyle → node.style
|
|
753
|
+
// - Dynamic class (partly {{}}): vivo emits merged tag + STATIC class rules into
|
|
754
|
+
// node.inlineStyle (alongside per-key $foo bindings),
|
|
755
|
+
// so the runtime still applies them even though the
|
|
756
|
+
// class list is only known at runtime.
|
|
757
|
+
// - Fully dynamic (only {{...}}): no static class tokens → tag rules only, still into
|
|
758
|
+
// inlineStyle.
|
|
759
|
+
// @media rules are applied at runtime by SDK, not inlined here.
|
|
760
|
+
// Class rules apply in stylesheet declaration order (later declarations override earlier),
|
|
761
|
+
// NOT in class-attribute order.
|
|
481
762
|
if (styleSheet) {
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
for (const
|
|
486
|
-
if (
|
|
763
|
+
const staticClassTokens = [];
|
|
764
|
+
if (className) {
|
|
765
|
+
const parts = className.split(/(\{\{[\s\S]*?\}\})/).filter(Boolean);
|
|
766
|
+
for (const p of parts) {
|
|
767
|
+
if (/^\{\{[\s\S]*?\}\}$/.test(p)) continue;
|
|
768
|
+
for (const tok of p.trim().split(/\s+/)) if (tok) staticClassTokens.push(tok);
|
|
487
769
|
}
|
|
488
770
|
}
|
|
489
|
-
|
|
490
|
-
|
|
771
|
+
const hasMedia = hasApplicableMediaRule(styleSheet, el.tagName, staticClassTokens);
|
|
772
|
+
// Skip class-rule merge when the node has any dynamic binding OR any @media
|
|
773
|
+
// rule that applies to it — runtime must resolve class chain against the
|
|
774
|
+
// stylesheet for correct priority + media evaluation. Only the user-authored
|
|
775
|
+
// `style="..."` static portion stays inlined on the node.
|
|
776
|
+
const skipClassMerge = hasBind || hasMedia;
|
|
777
|
+
// @media-only presence with no other dynamic binding still needs runtime
|
|
778
|
+
// re-evaluation, mark bind=1 (vivo convention).
|
|
779
|
+
if (!node.bind && hasMedia) node.bind = 1;
|
|
780
|
+
if (skipClassMerge) {
|
|
781
|
+
const inlined = {
|
|
782
|
+
...staticInlineStyle
|
|
783
|
+
};
|
|
784
|
+
if (inlined.animationName && styleSheet['@KEYFRAMES']?.[inlined.animationName]) {
|
|
785
|
+
inlined.animationKeyframes = {
|
|
786
|
+
[inlined.animationName]: styleSheet['@KEYFRAMES'][inlined.animationName]
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
if (Object.keys(inlined).length) {
|
|
790
|
+
if (hasDynClass) {
|
|
791
|
+
const existing = node['inlineStyle'] || {};
|
|
792
|
+
node['inlineStyle'] = {
|
|
793
|
+
...inlined,
|
|
794
|
+
...existing
|
|
795
|
+
};
|
|
796
|
+
} else {
|
|
797
|
+
node.style = inlined;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
} else {
|
|
801
|
+
const merged = {};
|
|
802
|
+
if (styleSheet[el.tagName]) Object.assign(merged, styleSheet[el.tagName]);
|
|
803
|
+
if (staticClassTokens.length) {
|
|
804
|
+
const classSet = new Set(staticClassTokens);
|
|
805
|
+
for (const key of Object.keys(styleSheet)) {
|
|
806
|
+
if (key.length > 1 && key.startsWith('.') && classSet.has(key.slice(1))) {
|
|
807
|
+
Object.assign(merged, styleSheet[key]);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
Object.assign(merged, staticInlineStyle);
|
|
812
|
+
if (merged.animationName && styleSheet['@KEYFRAMES']?.[merged.animationName]) {
|
|
813
|
+
merged.animationKeyframes = {
|
|
814
|
+
[merged.animationName]: styleSheet['@KEYFRAMES'][merged.animationName]
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
if (Object.keys(merged).length) node.style = merged;
|
|
818
|
+
}
|
|
491
819
|
} else if (Object.keys(staticInlineStyle).length) {
|
|
492
820
|
node.style = {
|
|
493
821
|
...staticInlineStyle
|
|
@@ -502,14 +830,12 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
|
|
|
502
830
|
if (child.tagName) {
|
|
503
831
|
childNodes.push(elementToJson(child, styleSheet, importMap, scopeVars, pageDir));
|
|
504
832
|
} else if (isMixedContent && child.value?.trim()) {
|
|
505
|
-
|
|
506
|
-
const text = child.value.replace(/[ \t]*\n[ \t]*/g, ' ').replace(/ {2,}/g, ' ');
|
|
833
|
+
const emitted = emitTextValueAttr(child.value);
|
|
507
834
|
const spanNode = {
|
|
508
835
|
type: 'span',
|
|
509
|
-
attr:
|
|
510
|
-
value: text
|
|
511
|
-
}
|
|
836
|
+
attr: emitted.attr
|
|
512
837
|
};
|
|
838
|
+
if (emitted.needsBind) spanNode.bind = 1;
|
|
513
839
|
childNodes.push(spanNode);
|
|
514
840
|
}
|
|
515
841
|
}
|
|
@@ -10,4 +10,8 @@ declare class ViteCompiler implements ICompiler {
|
|
|
10
10
|
compile(param: IJavascriptCompileOption): Promise<void>;
|
|
11
11
|
clean(param: ICompileParam & IJavascriptCompileOption): Promise<void>;
|
|
12
12
|
}
|
|
13
|
+
declare function stripDeadDefProp(code: string): string;
|
|
14
|
+
export declare const __test: {
|
|
15
|
+
stripDeadDefProp: typeof stripDeadDefProp;
|
|
16
|
+
};
|
|
13
17
|
export default ViteCompiler;
|