@aiot-toolkit/aiotpack 2.1.0-prender.10 → 2.1.0-prender.12

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.
@@ -35,6 +35,81 @@ function hasApplicableMediaRule(styleSheet, tagName, classTokens) {
35
35
  }
36
36
  return false;
37
37
  }
38
+ // Parse a simple/compound selector like ".a", ".a.b", "text", "text.a", ".a #id"
39
+ // (id treated as a class token for build-time approximation).
40
+ function parseCompoundSelector(part) {
41
+ const out = {
42
+ classes: []
43
+ };
44
+ const re = /(\.[\w-]+)|(#[\w-]+)|(\*)|([a-zA-Z][\w-]*)/g;
45
+ let m;
46
+ let matched = false;
47
+ while ((m = re.exec(part)) !== null) {
48
+ matched = true;
49
+ if (m[1]) out.classes.push(m[1].slice(1));else if (m[2]) out.classes.push(m[2].slice(1));else if (m[4]) out.tag = m[4];
50
+ }
51
+ return matched ? out : null;
52
+ }
53
+ function matchesCompound(comp, tag, classes) {
54
+ if (comp.tag && comp.tag !== tag) return false;
55
+ for (const c of comp.classes) if (!classes.has(c)) return false;
56
+ return true;
57
+ }
58
+
59
+ // Match a CSS selector against an element + its ancestor chain (root...parent).
60
+ // Supports descendant, child (>), and compound (.a.b) selectors. Sibling
61
+ // combinators (+, ~) require sibling context and are treated as non-match here.
62
+ function selectorMatchesElement(selector, tag, classes, ancestors) {
63
+ const parts = [];
64
+ const combinators = [];
65
+ let cur = '';
66
+ for (let i = 0; i < selector.length; i++) {
67
+ const ch = selector[i];
68
+ if (ch === '>' || ch === '+' || ch === '~') {
69
+ if (cur.trim()) parts.push(cur.trim());
70
+ combinators.push(ch);
71
+ cur = '';
72
+ } else if (ch === ' ' || ch === '\t') {
73
+ if (cur.trim()) {
74
+ parts.push(cur.trim());
75
+ combinators.push(' ');
76
+ cur = '';
77
+ }
78
+ } else {
79
+ cur += ch;
80
+ }
81
+ }
82
+ if (cur.trim()) parts.push(cur.trim());
83
+ if (parts.length === 0) return false;
84
+ const rightMost = parseCompoundSelector(parts[parts.length - 1]);
85
+ if (!rightMost) return false;
86
+ if (!matchesCompound(rightMost, tag, classes)) return false;
87
+ let idx = ancestors.length - 1;
88
+ for (let i = parts.length - 2; i >= 0; i--) {
89
+ const combinator = combinators[i];
90
+ const simple = parseCompoundSelector(parts[i]);
91
+ if (!simple) return false;
92
+ if (combinator === ' ') {
93
+ let found = false;
94
+ while (idx >= 0) {
95
+ if (matchesCompound(simple, ancestors[idx].tag, ancestors[idx].classes)) {
96
+ idx--;
97
+ found = true;
98
+ break;
99
+ }
100
+ idx--;
101
+ }
102
+ if (!found) return false;
103
+ } else if (combinator === '>') {
104
+ if (idx < 0) return false;
105
+ if (!matchesCompound(simple, ancestors[idx].tag, ancestors[idx].classes)) return false;
106
+ idx--;
107
+ } else {
108
+ return false;
109
+ }
110
+ }
111
+ return true;
112
+ }
38
113
 
39
114
  // vivo blueos-pack's aggressive camelCase applied to transition-property values.
40
115
  // Every letter that follows a non-letter character (separator or digit) is upper-cased,
@@ -469,6 +544,7 @@ function emitTextValueAttr(rawText) {
469
544
  };
470
545
  }
471
546
  function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
547
+ let ancestors = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : [];
472
548
  const tagName = el.tagName === 'img' ? 'image' : el.tagName;
473
549
  const node = {
474
550
  type: tagName
@@ -493,9 +569,7 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
493
569
  name = name.slice(1);
494
570
  if (!value.includes('{{')) value = `{{${value}}}`;
495
571
  }
496
- if (name === 'is' && tagName === 'component') {
497
- continue; // is attribute handled at runtime
498
- } else if (name === 'class') {
572
+ if (name === 'class') {
499
573
  className = value.replace(/\s+/g, ' ').trim();
500
574
  } else if (name.startsWith('@') || name.startsWith('on') && name.length > 2) {
501
575
  const rawEvtName = name.startsWith('@') ? name.slice(1) : name.replace(/^on/, '');
@@ -595,7 +669,7 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
595
669
  }
596
670
  } else if (name === 'style') {
597
671
  if (/^\{\{[^}]+\}\}$/.test(value.trim())) {
598
- // Pure dynamic style binding: style="{{variable}}"
672
+ // style="{{variable}}" — whole style bound to a single variable.
599
673
  const inner = value.replace(/\{\{|\}\}/g, '').trim();
600
674
  if (/^[a-zA-Z_$][\w$.]*$/.test(inner)) {
601
675
  node['$style'] = inner;
@@ -604,46 +678,51 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
604
678
  }
605
679
  hasBind = true;
606
680
  } else {
607
- // Static or per-property dynamic inline style
608
- // Static values get merged into node.style (alongside tag/class selectors)
609
- // Dynamic values ($key: function) stay in inlineStyle
610
- // Only set hasBind if there are dynamic values
611
- const inlineStyle = {};
681
+ // Per-property scan. Any dynamic entry promotes the whole attribute
682
+ // to $style-as-object, merging static and dynamic keys together
683
+ // (dynamic → `$key: function`, static → `key: value`). All-static
684
+ // falls back to staticInlineStyle for the pure-static merge path.
685
+ const dynEntries = {};
686
+ const staticEntries = {};
612
687
  let hasDynamicInlineStyle = false;
613
688
  value.split(';').forEach(decl => {
614
689
  const [prop, ...vals] = decl.split(':');
615
- if (prop && vals.length) {
616
- const key = prop.trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
617
- const propVal = vals.join(':').trim();
618
- if (/\{\{[\s\S]*?\}\}/.test(propVal)) {
619
- // Dynamic value — emit as $key: function() {...}
620
- const parts = propVal.split(/(\{\{[\s\S]*?\}\})/);
621
- const exprs = parts.filter(Boolean).map(p => {
622
- const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
623
- if (m) return addThisPrefix(m[1].trim());
624
- return `'${p.replace(/'/g, "\\'")}'`;
625
- });
626
- const fullExpr = exprs.length === 1 ? exprs[0] : `'' + ${exprs.join(' + ')}`;
627
- inlineStyle['$' + key] = `function () { return ${fullExpr}; }`;
628
- hasDynamicInlineStyle = true;
629
- } else {
630
- // Static value — accumulate to staticInlineStyle for merging into node.style later
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
- }
690
+ if (!prop || !vals.length) return;
691
+ const key = prop.trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
692
+ const propVal = vals.join(':').trim();
693
+ if (/\{\{[\s\S]*?\}\}/.test(propVal)) {
694
+ const parts = propVal.split(/(\{\{[\s\S]*?\}\})/);
695
+ const exprs = parts.filter(Boolean).map(p => {
696
+ const m = p.match(/^\{\{([\s\S]*?)\}\}$/);
697
+ if (m) return addThisPrefix(m[1].trim());
698
+ return `'${p.replace(/'/g, "\\'")}'`;
699
+ });
700
+ const fullExpr = exprs.length === 1 ? exprs[0] : `'' + ${exprs.join(' + ')}`;
701
+ dynEntries['$' + key] = `function () { return ${fullExpr}; }`;
702
+ hasDynamicInlineStyle = true;
703
+ } else {
704
+ let normalized = propVal;
705
+ if (key === 'transform') {
706
+ normalized = normalizeCssTransform(propVal) ?? propVal;
707
+ } else if (key === 'transitionProperty') {
708
+ normalized = normalizeTransitionPropertyValue(propVal);
709
+ } else if (key === 'animationTimingFunction' || key === 'transitionTimingFunction') {
710
+ const n = normalizeAnimationTimingFunction(propVal);
711
+ if (n === null) return;
712
+ normalized = n;
642
713
  }
714
+ staticEntries[key] = normalized;
643
715
  }
644
716
  });
645
- if (Object.keys(inlineStyle).length) node['inlineStyle'] = inlineStyle;
646
- if (hasDynamicInlineStyle) hasBind = true;
717
+ if (hasDynamicInlineStyle) {
718
+ node['$style'] = {
719
+ ...staticEntries,
720
+ ...dynEntries
721
+ };
722
+ hasBind = true;
723
+ } else {
724
+ for (const [k, v] of Object.entries(staticEntries)) staticInlineStyle[k] = v;
725
+ }
647
726
  }
648
727
  } else if (/\{\{.*\}\}/.test(value)) {
649
728
  // Dynamic binding - build expression from mixed static/dynamic parts
@@ -759,15 +838,15 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
759
838
  // @media rules are applied at runtime by SDK, not inlined here.
760
839
  // Class rules apply in stylesheet declaration order (later declarations override earlier),
761
840
  // NOT in class-attribute order.
762
- if (styleSheet) {
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);
769
- }
841
+ const staticClassTokens = [];
842
+ if (className) {
843
+ const parts = className.split(/(\{\{[\s\S]*?\}\})/).filter(Boolean);
844
+ for (const p of parts) {
845
+ if (/^\{\{[\s\S]*?\}\}$/.test(p)) continue;
846
+ for (const tok of p.trim().split(/\s+/)) if (tok) staticClassTokens.push(tok);
770
847
  }
848
+ }
849
+ if (styleSheet) {
771
850
  const hasMedia = hasApplicableMediaRule(styleSheet, el.tagName, staticClassTokens);
772
851
  // Skip class-rule merge when the node has any dynamic binding OR any @media
773
852
  // rule that applies to it — runtime must resolve class chain against the
@@ -786,24 +865,22 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
786
865
  [inlined.animationName]: styleSheet['@KEYFRAMES'][inlined.animationName]
787
866
  };
788
867
  }
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
- }
868
+ if (Object.keys(inlined).length) node.style = inlined;
800
869
  } else {
801
870
  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))) {
871
+ const classSet = new Set(staticClassTokens);
872
+ for (const key of Object.keys(styleSheet)) {
873
+ if (key.startsWith('@')) continue;
874
+ if (key === el.tagName) {
875
+ Object.assign(merged, styleSheet[key]);
876
+ continue;
877
+ }
878
+ if (key.length > 1 && key.startsWith('.') && !/[\s>+~.]/.test(key.slice(1))) {
879
+ if (classSet.has(key.slice(1))) Object.assign(merged, styleSheet[key]);
880
+ continue;
881
+ }
882
+ if (/[\s>+~]/.test(key) || /\.[\w-]+\./.test(key)) {
883
+ if (selectorMatchesElement(key, el.tagName, classSet, ancestors)) {
807
884
  Object.assign(merged, styleSheet[key]);
808
885
  }
809
886
  }
@@ -828,7 +905,10 @@ function elementToJson(el, styleSheet, importMap, scopeVars, pageDir) {
828
905
  const isMixedContent = hasElementChildren && hasTextChildren && (tagName === 'text' || tagName === 'span' || tagName === 'a' || tagName === 'marquee');
829
906
  for (const child of el.childNodes || []) {
830
907
  if (child.tagName) {
831
- childNodes.push(elementToJson(child, styleSheet, importMap, scopeVars, pageDir));
908
+ childNodes.push(elementToJson(child, styleSheet, importMap, scopeVars, pageDir, [...ancestors, {
909
+ tag: el.tagName,
910
+ classes: new Set(staticClassTokens)
911
+ }]));
832
912
  } else if (isMixedContent && child.value?.trim()) {
833
913
  const emitted = emitTextValueAttr(child.value);
834
914
  const spanNode = {
@@ -11,7 +11,9 @@ declare class ViteCompiler implements ICompiler {
11
11
  clean(param: ICompileParam & IJavascriptCompileOption): Promise<void>;
12
12
  }
13
13
  declare function stripDeadDefProp(code: string): string;
14
+ declare function mergeStyleSheets(app: any, page: any): any;
14
15
  export declare const __test: {
15
16
  stripDeadDefProp: typeof stripDeadDefProp;
17
+ mergeStyleSheets: typeof mergeStyleSheets;
16
18
  };
17
19
  export default ViteCompiler;
@@ -246,6 +246,7 @@ class ViteCompiler {
246
246
  }
247
247
  const entryFailures = [];
248
248
  let entryAttempted = 0;
249
+ let appStyleSheet = {};
249
250
  for (const [entryName, entryValue] of Object.entries(entries)) {
250
251
  const entryPath = entryValue.split('?')[0];
251
252
  const resolvedEntry = _path.default.resolve(originalProjectPath, entryPath);
@@ -427,6 +428,9 @@ class ViteCompiler {
427
428
  if (styleLiteral) {
428
429
  styleSheet = (0, _TemplateCompiler.parseStyleArray)(styleLiteral);
429
430
  }
431
+ if (isApp) {
432
+ appStyleSheet = styleSheet;
433
+ }
430
434
  // Write css.json
431
435
  const cssPath = _path.default.join(buildPath, `${isApp ? 'app' : pageName}.css.json`);
432
436
  _fsExtra.default.ensureDirSync(_path.default.dirname(cssPath));
@@ -439,7 +443,8 @@ class ViteCompiler {
439
443
  if (templateNode && !isApp) {
440
444
  const tplPath = _path.default.join(buildPath, `${pageName}.template.json`);
441
445
  _fsExtra.default.ensureDirSync(_path.default.dirname(tplPath));
442
- _fsExtra.default.writeJSONSync(tplPath, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, styleSheet, importMap, _path.default.dirname(pageName)), param.mode === 'production' ? undefined : {
446
+ const mergedSheet = mergeStyleSheets(appStyleSheet, styleSheet);
447
+ _fsExtra.default.writeJSONSync(tplPath, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, mergedSheet, importMap, _path.default.dirname(pageName)), param.mode === 'production' ? undefined : {
443
448
  spaces: 2
444
449
  });
445
450
  // Worklist BFS over nested <import> chain so transitively imported
@@ -504,7 +509,7 @@ class ViteCompiler {
504
509
  compStyleSheet = (0, _TemplateCompiler.parseStyleArray)(csm);
505
510
  }
506
511
  const compSid = (0, _TemplateCompiler.getStyleObjectId)(compPath);
507
- _fsExtra.default.writeJSONSync(compTplPath, (0, _TemplateCompiler.uxToTemplateJson)(compTpl, compSid, compStyleSheet, compImportMap, _path.default.dirname(compPath)), param.mode === 'production' ? undefined : {
512
+ _fsExtra.default.writeJSONSync(compTplPath, (0, _TemplateCompiler.uxToTemplateJson)(compTpl, compSid, mergeStyleSheets(appStyleSheet, compStyleSheet), compImportMap, _path.default.dirname(compPath)), param.mode === 'production' ? undefined : {
508
513
  spaces: 2
509
514
  });
510
515
  const compCssPath = _path.default.join(buildPath, `${compPath}.css.json`);
@@ -630,7 +635,7 @@ class ViteCompiler {
630
635
  // Generate template.json
631
636
  if (templateNode) {
632
637
  _fsExtra.default.ensureDirSync(_path.default.dirname(outputTpl));
633
- _fsExtra.default.writeJSONSync(outputTpl, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, styleSheet, Object.keys(importMap).length ? importMap : undefined, _path.default.dirname(relPath)), param.mode === 'production' ? undefined : {
638
+ _fsExtra.default.writeJSONSync(outputTpl, (0, _TemplateCompiler.uxToTemplateJson)(templateNode, sid, mergeStyleSheets(appStyleSheet, styleSheet), Object.keys(importMap).length ? importMap : undefined, _path.default.dirname(relPath)), param.mode === 'production' ? undefined : {
634
639
  spaces: 2
635
640
  });
636
641
  }
@@ -738,7 +743,33 @@ $app_define$("${defineId}", [], function($app_require$, $app_exports$, $app_modu
738
743
  $app_bootstrap$("${bootstrapId}");
739
744
  `;
740
745
  }
746
+ function mergeStyleSheets(app, page) {
747
+ if (!app || !Object.keys(app).length) return page;
748
+ if (!page || !Object.keys(page).length) return app;
749
+ const merged = {
750
+ ...app
751
+ };
752
+ for (const [k, v] of Object.entries(page)) {
753
+ if (k === '@KEYFRAMES' || k === '@FONT-FACE') {
754
+ merged[k] = {
755
+ ...(merged[k] || {}),
756
+ ...v
757
+ };
758
+ } else if (k === '@MEDIA') {
759
+ merged[k] = [...(merged[k] || []), ...v];
760
+ } else if (typeof v === 'object' && v !== null && typeof merged[k] === 'object' && merged[k] !== null) {
761
+ merged[k] = {
762
+ ...merged[k],
763
+ ...v
764
+ };
765
+ } else {
766
+ merged[k] = v;
767
+ }
768
+ }
769
+ return merged;
770
+ }
741
771
  const __test = exports.__test = {
742
- stripDeadDefProp
772
+ stripDeadDefProp,
773
+ mergeStyleSheets
743
774
  };
744
775
  var _default = exports.default = ViteCompiler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiot-toolkit/aiotpack",
3
- "version": "2.1.0-prender.10",
3
+ "version": "2.1.0-prender.12",
4
4
  "description": "The process tool for packaging aiot projects.",
5
5
  "keywords": [
6
6
  "aiotpack"
@@ -19,16 +19,16 @@
19
19
  "test": "node ./__tests__/aiotpack.test.js"
20
20
  },
21
21
  "dependencies": {
22
- "@aiot-toolkit/generator": "2.1.0-prender.10",
23
- "@aiot-toolkit/parser": "2.1.0-prender.10",
24
- "@aiot-toolkit/shared-utils": "2.1.0-prender.10",
22
+ "@aiot-toolkit/generator": "2.1.0-prender.12",
23
+ "@aiot-toolkit/parser": "2.1.0-prender.12",
24
+ "@aiot-toolkit/shared-utils": "2.1.0-prender.12",
25
25
  "@hap-toolkit/aaptjs": "^2.0.0",
26
26
  "@rspack/core": "^1.3.9",
27
27
  "acorn": "^8.16.0",
28
28
  "aiot-parse5": "^1.0.2",
29
29
  "astring": "^1.9.0",
30
30
  "babel-loader": "^9.1.3",
31
- "file-lane": "2.1.0-prender.10",
31
+ "file-lane": "2.1.0-prender.12",
32
32
  "file-loader": "^6.2.0",
33
33
  "fs-extra": "^11.2.0",
34
34
  "jsrsasign": "^11.1.0",