chaincss 2.1.37 → 2.1.39
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/ROADMAP.md +31 -0
- package/dist/cli/index.js +458 -3
- package/dist/compiler/analyzer.d.ts +12 -0
- package/dist/compiler/intent-engine.d.ts +31 -0
- package/dist/compiler/math-engine.d.ts +89 -0
- package/dist/compiler/style-graph.d.ts +30 -0
- package/dist/core/compiler.d.ts +12 -0
- package/dist/core/types.d.ts +145 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1116 -9
- package/dist/plugins/vite.js +459 -4
- package/package.json +1 -1
- package/src/compiler/analyzer.ts +62 -0
- package/src/compiler/intent-engine.ts +112 -0
- package/src/compiler/math-engine.ts +511 -0
- package/src/compiler/style-graph.ts +660 -0
- package/src/core/compiler.ts +40 -0
- package/src/core/types.ts +206 -0
- package/src/index.ts +69 -1
- package/src/plugins/vite.ts +9 -1
- package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +0 -1
- package/demo/index.html +0 -16
- package/demo/package.json +0 -20
- package/demo/src/App.tsx +0 -117
- package/demo/src/chaincss-barrel.ts +0 -9
- package/demo/src/main.tsx +0 -8
- package/demo/src/styles.chain.ts +0 -300
- package/demo/vite.config.ts +0 -46
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
## v3.0 Ideas
|
|
3
|
+
|
|
4
|
+
### 1. Predictive & Self-Healing CSS
|
|
5
|
+
- Context-aware typo correction (not just fuzzy matching)
|
|
6
|
+
- Intent detection: `display: "flexbox"` → maps to `display: flex` + applies defaults
|
|
7
|
+
- Modes: strict (error), dev (auto-fix), smart (fix + log)
|
|
8
|
+
- Builds on: suggestions.ts, explain()
|
|
9
|
+
|
|
10
|
+
### 2. Style Graph Compiler
|
|
11
|
+
- Instead of linear compilation, build a dependency graph of styles
|
|
12
|
+
- Dead style elimination
|
|
13
|
+
- Automatic merging of identical rules
|
|
14
|
+
- Predictive pre-compilation
|
|
15
|
+
- Order-safe CSS output
|
|
16
|
+
- Builds on: compile(), atomic optimizer
|
|
17
|
+
|
|
18
|
+
### 3. Unit-Aware Math Engine
|
|
19
|
+
- Full CSS expression evaluator
|
|
20
|
+
- `add("10px", "2rem")` → resolves units → optimized calc()
|
|
21
|
+
- `fluidType(14, 20)` → responsive clamp()
|
|
22
|
+
- Context-aware scaling
|
|
23
|
+
- Builds on: helpers.ts
|
|
24
|
+
|
|
25
|
+
### 4. IDE Intelligence Layer (VS Code Extension)
|
|
26
|
+
- Real-time invalid property detection
|
|
27
|
+
- Shorthand translation hints
|
|
28
|
+
- Animation suggestion
|
|
29
|
+
- Breakpoint inference
|
|
30
|
+
- Style conflict detection
|
|
31
|
+
- Builds on: suggestions.ts
|
package/dist/cli/index.js
CHANGED
|
@@ -2612,6 +2612,431 @@ var init_content_addressable_cache = __esm({
|
|
|
2612
2612
|
}
|
|
2613
2613
|
});
|
|
2614
2614
|
|
|
2615
|
+
// src/compiler/style-graph.ts
|
|
2616
|
+
import crypto4 from "crypto";
|
|
2617
|
+
function calculateSpecificity(selector) {
|
|
2618
|
+
let a = 0;
|
|
2619
|
+
let b = 0;
|
|
2620
|
+
let c = 0;
|
|
2621
|
+
const idMatches = selector.match(/#[a-zA-Z0-9_-]+/g);
|
|
2622
|
+
if (idMatches) a += idMatches.length;
|
|
2623
|
+
const classMatches = selector.match(/\.[a-zA-Z0-9_-]+/g);
|
|
2624
|
+
if (classMatches) b += classMatches.length;
|
|
2625
|
+
const attrMatches = selector.match(/\[[^\]]+\]/g);
|
|
2626
|
+
if (attrMatches) b += attrMatches.length;
|
|
2627
|
+
const pseudoClassMatches = selector.match(/:[a-zA-Z-]+(?:\([^)]*\))?/g);
|
|
2628
|
+
if (pseudoClassMatches) {
|
|
2629
|
+
const notMatches = selector.match(/:not\(([^)]+)\)/g);
|
|
2630
|
+
const regularPseudoClasses = pseudoClassMatches.length - (notMatches?.length || 0);
|
|
2631
|
+
b += Math.max(0, regularPseudoClasses);
|
|
2632
|
+
}
|
|
2633
|
+
const elementMatches = selector.match(/^[a-zA-Z]+|[a-zA-Z]+(?=[.#[:])/g);
|
|
2634
|
+
if (elementMatches) c += elementMatches.length;
|
|
2635
|
+
return a * 1e4 + b * 100 + c;
|
|
2636
|
+
}
|
|
2637
|
+
function hashProperties(properties) {
|
|
2638
|
+
const sorted = Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${v}`).join(";");
|
|
2639
|
+
return crypto4.createHash("md5").update(sorted).digest("hex").slice(0, 8);
|
|
2640
|
+
}
|
|
2641
|
+
function kebab2(prop) {
|
|
2642
|
+
return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
2643
|
+
}
|
|
2644
|
+
function eliminateDeadStyles(graph, knownSelectors) {
|
|
2645
|
+
if (knownSelectors.length === 0) {
|
|
2646
|
+
return { eliminated: 0, graph };
|
|
2647
|
+
}
|
|
2648
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
2649
|
+
const queue = [];
|
|
2650
|
+
for (const [id, node] of graph.nodes) {
|
|
2651
|
+
if (knownSelectors.some((ks) => node.selector.includes(ks) || ks.includes(node.selector))) {
|
|
2652
|
+
reachable.add(id);
|
|
2653
|
+
queue.push(id);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
while (queue.length > 0) {
|
|
2657
|
+
const current = queue.shift();
|
|
2658
|
+
const node = graph.nodes.get(current);
|
|
2659
|
+
if (!node) continue;
|
|
2660
|
+
for (const depId of node.dependents) {
|
|
2661
|
+
if (!reachable.has(depId)) {
|
|
2662
|
+
reachable.add(depId);
|
|
2663
|
+
queue.push(depId);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
let eliminated = 0;
|
|
2668
|
+
for (const [id, node] of graph.nodes) {
|
|
2669
|
+
if (!reachable.has(id)) {
|
|
2670
|
+
node.isDead = true;
|
|
2671
|
+
eliminated++;
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
return { eliminated, graph };
|
|
2675
|
+
}
|
|
2676
|
+
function mergeIdenticalRules(graph, threshold) {
|
|
2677
|
+
const hashGroups = /* @__PURE__ */ new Map();
|
|
2678
|
+
for (const [, node] of graph.nodes) {
|
|
2679
|
+
if (node.isDead) continue;
|
|
2680
|
+
if (Object.keys(node.properties).length < threshold) continue;
|
|
2681
|
+
const existing = hashGroups.get(node.hash) || [];
|
|
2682
|
+
existing.push(node);
|
|
2683
|
+
hashGroups.set(node.hash, existing);
|
|
2684
|
+
}
|
|
2685
|
+
let merged = 0;
|
|
2686
|
+
for (const [, group] of hashGroups) {
|
|
2687
|
+
if (group.length < 2) continue;
|
|
2688
|
+
const mergedSelector = group.map((n) => n.selector).join(", ");
|
|
2689
|
+
const primary = group[0];
|
|
2690
|
+
primary.selector = mergedSelector;
|
|
2691
|
+
for (let i = 1; i < group.length; i++) {
|
|
2692
|
+
group[i].isDead = true;
|
|
2693
|
+
merged++;
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
return { merged, graph };
|
|
2697
|
+
}
|
|
2698
|
+
function topologicalSort(graph) {
|
|
2699
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2700
|
+
const sorted = [];
|
|
2701
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2702
|
+
function visit(id) {
|
|
2703
|
+
if (visited.has(id)) return true;
|
|
2704
|
+
if (visiting.has(id)) return false;
|
|
2705
|
+
visiting.add(id);
|
|
2706
|
+
const node = graph.nodes.get(id);
|
|
2707
|
+
if (node) {
|
|
2708
|
+
for (const depId of node.dependencies) {
|
|
2709
|
+
if (!visit(depId)) return false;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
visiting.delete(id);
|
|
2713
|
+
visited.add(id);
|
|
2714
|
+
if (node && !node.isDead) {
|
|
2715
|
+
sorted.push(node);
|
|
2716
|
+
}
|
|
2717
|
+
return true;
|
|
2718
|
+
}
|
|
2719
|
+
for (const id of graph.rootNodes) {
|
|
2720
|
+
if (!visit(id)) {
|
|
2721
|
+
return Array.from(graph.nodes.values()).filter((n) => !n.isDead).sort((a, b) => a.sourceComponent?.localeCompare(b.sourceComponent || "") || 0);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
for (const [id] of graph.nodes) {
|
|
2725
|
+
if (!visited.has(id)) {
|
|
2726
|
+
visit(id);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
return sorted;
|
|
2730
|
+
}
|
|
2731
|
+
function generateCSSFromGraph(graph, sortOutput = "specificity") {
|
|
2732
|
+
let nodes;
|
|
2733
|
+
switch (sortOutput) {
|
|
2734
|
+
case "specificity":
|
|
2735
|
+
nodes = Array.from(graph.nodes.values()).filter((n) => !n.isDead).sort((a, b) => a.specificity - b.specificity);
|
|
2736
|
+
break;
|
|
2737
|
+
case "topological":
|
|
2738
|
+
nodes = topologicalSort(graph);
|
|
2739
|
+
break;
|
|
2740
|
+
case "source-order":
|
|
2741
|
+
default:
|
|
2742
|
+
nodes = Array.from(graph.nodes.values()).filter((n) => !n.isDead);
|
|
2743
|
+
break;
|
|
2744
|
+
}
|
|
2745
|
+
let css = "";
|
|
2746
|
+
let currentMediaQuery;
|
|
2747
|
+
for (const node of nodes) {
|
|
2748
|
+
if (node.isDead) continue;
|
|
2749
|
+
if (node.mediaQuery !== currentMediaQuery) {
|
|
2750
|
+
if (currentMediaQuery) {
|
|
2751
|
+
css += "}\n\n";
|
|
2752
|
+
}
|
|
2753
|
+
if (node.mediaQuery) {
|
|
2754
|
+
css += `@media ${node.mediaQuery} {
|
|
2755
|
+
`;
|
|
2756
|
+
}
|
|
2757
|
+
currentMediaQuery = node.mediaQuery;
|
|
2758
|
+
}
|
|
2759
|
+
const rules = Object.entries(node.properties).map(([prop, value]) => ` ${kebab2(prop)}: ${value};`).join("\n");
|
|
2760
|
+
if (rules) {
|
|
2761
|
+
css += `${node.selector} {
|
|
2762
|
+
${rules}
|
|
2763
|
+
}
|
|
2764
|
+
`;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
if (currentMediaQuery) {
|
|
2768
|
+
css += "}\n";
|
|
2769
|
+
}
|
|
2770
|
+
return css;
|
|
2771
|
+
}
|
|
2772
|
+
var StyleGraphBuilder, StyleGraphCompiler;
|
|
2773
|
+
var init_style_graph = __esm({
|
|
2774
|
+
"src/compiler/style-graph.ts"() {
|
|
2775
|
+
"use strict";
|
|
2776
|
+
StyleGraphBuilder = class {
|
|
2777
|
+
entries = [];
|
|
2778
|
+
nodes = /* @__PURE__ */ new Map();
|
|
2779
|
+
edges = [];
|
|
2780
|
+
orderCounter = 0;
|
|
2781
|
+
addEntry(entry) {
|
|
2782
|
+
entry.sourceOrder = this.orderCounter++;
|
|
2783
|
+
this.entries.push(entry);
|
|
2784
|
+
}
|
|
2785
|
+
build() {
|
|
2786
|
+
this.nodes.clear();
|
|
2787
|
+
this.edges = [];
|
|
2788
|
+
for (const entry of this.entries) {
|
|
2789
|
+
const id = `node-${this.nodes.size}`;
|
|
2790
|
+
const node = {
|
|
2791
|
+
id,
|
|
2792
|
+
selector: entry.selector,
|
|
2793
|
+
properties: entry.properties,
|
|
2794
|
+
specificity: calculateSpecificity(entry.selector),
|
|
2795
|
+
dependencies: [],
|
|
2796
|
+
dependents: [],
|
|
2797
|
+
mediaQuery: entry.mediaQuery,
|
|
2798
|
+
isDead: false,
|
|
2799
|
+
hash: hashProperties(entry.properties),
|
|
2800
|
+
sourceComponent: entry.sourceComponent
|
|
2801
|
+
};
|
|
2802
|
+
this.nodes.set(id, node);
|
|
2803
|
+
}
|
|
2804
|
+
const nodeArray = Array.from(this.nodes.values());
|
|
2805
|
+
for (let i = 0; i < nodeArray.length; i++) {
|
|
2806
|
+
for (let j = i + 1; j < nodeArray.length; j++) {
|
|
2807
|
+
const a = nodeArray[i];
|
|
2808
|
+
const b = nodeArray[j];
|
|
2809
|
+
if (this.selectorsOverlap(a.selector, b.selector)) {
|
|
2810
|
+
if (a.specificity <= b.specificity) {
|
|
2811
|
+
this.edges.push({ from: a.id, to: b.id, type: "overrides" });
|
|
2812
|
+
a.dependents.push(b.id);
|
|
2813
|
+
b.dependencies.push(a.id);
|
|
2814
|
+
}
|
|
2815
|
+
if (b.specificity <= a.specificity) {
|
|
2816
|
+
this.edges.push({ from: b.id, to: a.id, type: "overrides" });
|
|
2817
|
+
b.dependents.push(a.id);
|
|
2818
|
+
a.dependencies.push(b.id);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
const rootNodes = nodeArray.filter((n) => n.dependencies.length === 0).map((n) => n.id);
|
|
2824
|
+
const leafNodes = nodeArray.filter((n) => n.dependents.length === 0).map((n) => n.id);
|
|
2825
|
+
return {
|
|
2826
|
+
nodes: this.nodes,
|
|
2827
|
+
edges: this.edges,
|
|
2828
|
+
rootNodes,
|
|
2829
|
+
leafNodes
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
selectorsOverlap(a, b) {
|
|
2833
|
+
const partsA = a.split(/[\s>+~]+/).filter(Boolean);
|
|
2834
|
+
const partsB = b.split(/[\s>+~]+/).filter(Boolean);
|
|
2835
|
+
for (const pa of partsA) {
|
|
2836
|
+
for (const pb of partsB) {
|
|
2837
|
+
if (pa === pb) return true;
|
|
2838
|
+
if (pa.startsWith(".") && pb.startsWith(".") && pa === pb) return true;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
return false;
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
StyleGraphCompiler = class {
|
|
2845
|
+
options;
|
|
2846
|
+
constructor(options = {}) {
|
|
2847
|
+
this.options = {
|
|
2848
|
+
eliminateDead: options.eliminateDead ?? false,
|
|
2849
|
+
knownSelectors: options.knownSelectors ?? [],
|
|
2850
|
+
mergeIdentical: options.mergeIdentical ?? false,
|
|
2851
|
+
mergeThreshold: options.mergeThreshold ?? 3,
|
|
2852
|
+
sortOutput: options.sortOutput ?? "specificity",
|
|
2853
|
+
verbose: options.verbose ?? false
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
/**
|
|
2857
|
+
* Compile a set of style definitions through the graph compiler.
|
|
2858
|
+
*/
|
|
2859
|
+
compile(styles) {
|
|
2860
|
+
const startTime = Date.now();
|
|
2861
|
+
const builder = new StyleGraphBuilder();
|
|
2862
|
+
let preOptimizationSize = 0;
|
|
2863
|
+
for (const [componentName, styleDef] of Object.entries(styles)) {
|
|
2864
|
+
if (!styleDef || !styleDef.selectors) continue;
|
|
2865
|
+
for (const selector of styleDef.selectors) {
|
|
2866
|
+
const properties = {};
|
|
2867
|
+
for (const [prop, value] of Object.entries(styleDef)) {
|
|
2868
|
+
if (prop === "selectors" || prop === "atRules" || prop === "nestedRules" || prop === "hover" || prop === "themes" || prop.startsWith("_")) {
|
|
2869
|
+
continue;
|
|
2870
|
+
}
|
|
2871
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
2872
|
+
properties[prop] = String(value);
|
|
2873
|
+
preOptimizationSize += String(value).length + prop.length;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
if (Object.keys(properties).length > 0) {
|
|
2877
|
+
builder.addEntry({
|
|
2878
|
+
selector,
|
|
2879
|
+
properties,
|
|
2880
|
+
sourceComponent: componentName,
|
|
2881
|
+
sourceOrder: 0
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
if (styleDef.hover && typeof styleDef.hover === "object") {
|
|
2885
|
+
const hoverProperties = {};
|
|
2886
|
+
for (const [prop, value] of Object.entries(styleDef.hover)) {
|
|
2887
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
2888
|
+
hoverProperties[prop] = String(value);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
if (Object.keys(hoverProperties).length > 0) {
|
|
2892
|
+
builder.addEntry({
|
|
2893
|
+
selector: `${selector}:hover`,
|
|
2894
|
+
properties: hoverProperties,
|
|
2895
|
+
sourceComponent: componentName,
|
|
2896
|
+
sourceOrder: 0
|
|
2897
|
+
});
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
if (styleDef.atRules) {
|
|
2901
|
+
for (const rule of styleDef.atRules) {
|
|
2902
|
+
if (rule.type === "media" && rule.styles && rule.query) {
|
|
2903
|
+
const mediaProperties = {};
|
|
2904
|
+
for (const [prop, value] of Object.entries(rule.styles)) {
|
|
2905
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
2906
|
+
mediaProperties[prop] = String(value);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
if (Object.keys(mediaProperties).length > 0) {
|
|
2910
|
+
builder.addEntry({
|
|
2911
|
+
selector,
|
|
2912
|
+
properties: mediaProperties,
|
|
2913
|
+
sourceComponent: componentName,
|
|
2914
|
+
sourceOrder: 0,
|
|
2915
|
+
mediaQuery: rule.query
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
let graph = builder.build();
|
|
2924
|
+
let eliminatedDead = 0;
|
|
2925
|
+
if (this.options.eliminateDead && this.options.knownSelectors.length > 0) {
|
|
2926
|
+
const result = eliminateDeadStyles(graph, this.options.knownSelectors);
|
|
2927
|
+
eliminatedDead = result.eliminated;
|
|
2928
|
+
graph = result.graph;
|
|
2929
|
+
}
|
|
2930
|
+
let mergedRules = 0;
|
|
2931
|
+
if (this.options.mergeIdentical) {
|
|
2932
|
+
const result = mergeIdenticalRules(graph, this.options.mergeThreshold);
|
|
2933
|
+
mergedRules = result.merged;
|
|
2934
|
+
graph = result.graph;
|
|
2935
|
+
}
|
|
2936
|
+
const css = generateCSSFromGraph(graph, this.options.sortOutput);
|
|
2937
|
+
let postOptimizationSize = css.length;
|
|
2938
|
+
if (postOptimizationSize === 0) {
|
|
2939
|
+
postOptimizationSize = preOptimizationSize;
|
|
2940
|
+
}
|
|
2941
|
+
const classMap = {};
|
|
2942
|
+
for (const [, node] of graph.nodes) {
|
|
2943
|
+
if (!node.isDead && node.sourceComponent) {
|
|
2944
|
+
if (classMap[node.sourceComponent]) {
|
|
2945
|
+
classMap[node.sourceComponent] += ` ${node.selector.replace(/^\./, "")}`;
|
|
2946
|
+
} else {
|
|
2947
|
+
classMap[node.sourceComponent] = node.selector.replace(/^\./, "");
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
const totalNodes = graph.nodes.size;
|
|
2952
|
+
const aliveNodes = totalNodes - eliminatedDead;
|
|
2953
|
+
const savingsPercent = preOptimizationSize > 0 ? `${((preOptimizationSize - postOptimizationSize) / preOptimizationSize * 100).toFixed(1)}%` : "0%";
|
|
2954
|
+
const stats = {
|
|
2955
|
+
totalStyles: totalNodes,
|
|
2956
|
+
atomicStyles: 0,
|
|
2957
|
+
uniqueProperties: new Set(
|
|
2958
|
+
Array.from(graph.nodes.values()).filter((n) => !n.isDead).flatMap((n) => Object.keys(n.properties))
|
|
2959
|
+
).size,
|
|
2960
|
+
savings: savingsPercent,
|
|
2961
|
+
compileTime: Date.now() - startTime
|
|
2962
|
+
};
|
|
2963
|
+
return {
|
|
2964
|
+
css,
|
|
2965
|
+
classMap,
|
|
2966
|
+
atomicClasses: [],
|
|
2967
|
+
stats,
|
|
2968
|
+
graph,
|
|
2969
|
+
eliminatedDead,
|
|
2970
|
+
mergedRules,
|
|
2971
|
+
optimizationTime: Date.now() - startTime,
|
|
2972
|
+
preOptimizationSize,
|
|
2973
|
+
postOptimizationSize
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2977
|
+
* Analyze a style graph without generating CSS.
|
|
2978
|
+
*/
|
|
2979
|
+
analyze(styles) {
|
|
2980
|
+
const builder = new StyleGraphBuilder();
|
|
2981
|
+
for (const [componentName, styleDef] of Object.entries(styles)) {
|
|
2982
|
+
if (!styleDef || !styleDef.selectors) continue;
|
|
2983
|
+
for (const selector of styleDef.selectors) {
|
|
2984
|
+
const properties = {};
|
|
2985
|
+
for (const [prop, value] of Object.entries(styleDef)) {
|
|
2986
|
+
if (prop === "selectors" || prop.startsWith("_")) continue;
|
|
2987
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
2988
|
+
properties[prop] = String(value);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
if (Object.keys(properties).length > 0) {
|
|
2992
|
+
builder.addEntry({ selector, properties, sourceComponent: componentName, sourceOrder: 0 });
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
return builder.build();
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Get optimization statistics for a graph.
|
|
3000
|
+
*/
|
|
3001
|
+
getStats(graph) {
|
|
3002
|
+
const nodes = Array.from(graph.nodes.values());
|
|
3003
|
+
const deadNodes = nodes.filter((n) => n.isDead).length;
|
|
3004
|
+
const averageSpecificity = nodes.length > 0 ? nodes.reduce((sum, n) => sum + n.specificity, 0) / nodes.length : 0;
|
|
3005
|
+
let maxDepth = 0;
|
|
3006
|
+
const depths = /* @__PURE__ */ new Map();
|
|
3007
|
+
function getDepth(id) {
|
|
3008
|
+
if (depths.has(id)) return depths.get(id);
|
|
3009
|
+
const node = graph.nodes.get(id);
|
|
3010
|
+
if (!node || node.dependencies.length === 0) {
|
|
3011
|
+
depths.set(id, 0);
|
|
3012
|
+
return 0;
|
|
3013
|
+
}
|
|
3014
|
+
const max = Math.max(...node.dependencies.map((d) => getDepth(d)));
|
|
3015
|
+
const depth = max + 1;
|
|
3016
|
+
depths.set(id, depth);
|
|
3017
|
+
return depth;
|
|
3018
|
+
}
|
|
3019
|
+
for (const [id] of graph.nodes) {
|
|
3020
|
+
maxDepth = Math.max(maxDepth, getDepth(id));
|
|
3021
|
+
}
|
|
3022
|
+
return {
|
|
3023
|
+
totalNodes: nodes.length,
|
|
3024
|
+
deadNodes,
|
|
3025
|
+
mergedGroups: 0,
|
|
3026
|
+
averageSpecificity: Math.round(averageSpecificity * 100) / 100,
|
|
3027
|
+
deepestDependencyChain: maxDepth
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Update options.
|
|
3032
|
+
*/
|
|
3033
|
+
configure(options) {
|
|
3034
|
+
this.options = { ...this.options, ...options };
|
|
3035
|
+
}
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
});
|
|
3039
|
+
|
|
2615
3040
|
// src/core/compiler.ts
|
|
2616
3041
|
var compiler_exports = {};
|
|
2617
3042
|
__export(compiler_exports, {
|
|
@@ -2620,7 +3045,7 @@ __export(compiler_exports, {
|
|
|
2620
3045
|
});
|
|
2621
3046
|
import fs7 from "fs";
|
|
2622
3047
|
import path6 from "path";
|
|
2623
|
-
import
|
|
3048
|
+
import crypto5 from "crypto";
|
|
2624
3049
|
import chalk2 from "chalk";
|
|
2625
3050
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
2626
3051
|
async function compileChainCSS(inputFile, outputDir, config) {
|
|
@@ -2639,6 +3064,7 @@ var init_compiler = __esm({
|
|
|
2639
3064
|
init_cache_manager();
|
|
2640
3065
|
init_content_addressable_cache();
|
|
2641
3066
|
init_shorthands();
|
|
3067
|
+
init_style_graph();
|
|
2642
3068
|
__filename = typeof import.meta !== "undefined" ? (() => {
|
|
2643
3069
|
try {
|
|
2644
3070
|
return fileURLToPath(import.meta.url);
|
|
@@ -2689,6 +3115,35 @@ var init_compiler = __esm({
|
|
|
2689
3115
|
this.initOptimizer();
|
|
2690
3116
|
this.initPrefixer();
|
|
2691
3117
|
}
|
|
3118
|
+
/**
|
|
3119
|
+
* Compile using the style graph compiler for advanced optimizations.
|
|
3120
|
+
*
|
|
3121
|
+
* @example
|
|
3122
|
+
* const result = compiler.compileWithGraph(styles, {
|
|
3123
|
+
* eliminateDead: true,
|
|
3124
|
+
* knownSelectors: ['.header', '.footer'],
|
|
3125
|
+
* mergeIdentical: true
|
|
3126
|
+
* });
|
|
3127
|
+
*/
|
|
3128
|
+
compileWithGraph(styles, options) {
|
|
3129
|
+
const graphCompiler = new StyleGraphCompiler({
|
|
3130
|
+
...options,
|
|
3131
|
+
verbose: this.config.verbose
|
|
3132
|
+
});
|
|
3133
|
+
const result = graphCompiler.compile(styles);
|
|
3134
|
+
if (this.config.verbose) {
|
|
3135
|
+
if (result.eliminatedDead > 0) {
|
|
3136
|
+
console.log(` \u{1F9F9} Eliminated ${result.eliminatedDead} dead styles`);
|
|
3137
|
+
}
|
|
3138
|
+
if (result.mergedRules > 0) {
|
|
3139
|
+
console.log(` \u{1F517} Merged ${result.mergedRules} identical rules`);
|
|
3140
|
+
}
|
|
3141
|
+
if (result.optimizationTime > 0) {
|
|
3142
|
+
console.log(` \u26A1 Graph compilation: ${result.optimizationTime}ms`);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
return result;
|
|
3146
|
+
}
|
|
2692
3147
|
hasStyles() {
|
|
2693
3148
|
const combined = this.getCombinedCSS();
|
|
2694
3149
|
return !!(combined && combined.trim().length > 0);
|
|
@@ -2726,7 +3181,7 @@ var init_compiler = __esm({
|
|
|
2726
3181
|
if (result.css && result.css.trim()) {
|
|
2727
3182
|
this.accumulatedCSS += result.css + "\n";
|
|
2728
3183
|
}
|
|
2729
|
-
const cacheKey =
|
|
3184
|
+
const cacheKey = crypto5.createHash("sha256").update(`${componentName}-${JSON.stringify(styleObj)}`).digest("hex").slice(0, 16);
|
|
2730
3185
|
this.addToCache(cacheKey, {
|
|
2731
3186
|
result: {
|
|
2732
3187
|
css: result.css || "",
|
|
@@ -2854,7 +3309,7 @@ var init_compiler = __esm({
|
|
|
2854
3309
|
// ============================================================================
|
|
2855
3310
|
hashStyleDef(styleDef) {
|
|
2856
3311
|
const { _componentName, _generateComponent, _framework, _propsDefinition, ...relevant } = styleDef;
|
|
2857
|
-
return
|
|
3312
|
+
return crypto5.createHash("sha256").update(JSON.stringify(relevant)).digest("hex").slice(0, 16);
|
|
2858
3313
|
}
|
|
2859
3314
|
async importModule(filePath) {
|
|
2860
3315
|
const absolutePath = path6.resolve(filePath);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StyleDefinition, StyleDiagnostic, StyleAnalysis, BreakpointInference, DiagnosticSeverity } from '../core/types.js';
|
|
2
|
+
export type { StyleDiagnostic, StyleAnalysis, BreakpointInference, DiagnosticSeverity };
|
|
3
|
+
export declare class StyleAnalyzer {
|
|
4
|
+
private _diagnostics;
|
|
5
|
+
analyzeStyle(selector: string, styles: Record<string, any>, _opts?: any): StyleDiagnostic[];
|
|
6
|
+
analyze(sd: StyleDefinition): StyleAnalysis;
|
|
7
|
+
reset(): void;
|
|
8
|
+
getDiagnostics(): StyleDiagnostic[];
|
|
9
|
+
}
|
|
10
|
+
export declare function analyze(sd: StyleDefinition): StyleAnalysis;
|
|
11
|
+
export declare function analyzeStyle(sd: StyleDefinition): StyleAnalysis;
|
|
12
|
+
export default StyleAnalyzer;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CorrectionResult, HealMode, HealResult, IntentContext } from '../core/types.js';
|
|
2
|
+
export type { CorrectionResult, HealMode, HealResult, IntentContext };
|
|
3
|
+
interface ValueCorrection {
|
|
4
|
+
wrong: string;
|
|
5
|
+
correct: string;
|
|
6
|
+
confidence: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const intent: {
|
|
9
|
+
correct(property: string, value: string, context?: IntentContext): CorrectionResult | null;
|
|
10
|
+
heal(styles: Record<string, any>, mode?: HealMode, context?: IntentContext): HealResult;
|
|
11
|
+
getIntent(value: string, ctx?: IntentContext): string | null;
|
|
12
|
+
validate(property: string, value: string): {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
suggestion?: string;
|
|
15
|
+
};
|
|
16
|
+
getCorrections(property: string): ValueCorrection[];
|
|
17
|
+
explain(correction: CorrectionResult): string;
|
|
18
|
+
getKnownProperties(): string[];
|
|
19
|
+
getIntents(): {
|
|
20
|
+
pattern: string;
|
|
21
|
+
description: string;
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
24
|
+
export declare const correct: (property: string, value: string, context?: IntentContext) => CorrectionResult | null;
|
|
25
|
+
export declare const heal: (styles: Record<string, any>, mode?: HealMode, context?: IntentContext) => HealResult;
|
|
26
|
+
export declare const validate: (property: string, value: string) => {
|
|
27
|
+
valid: boolean;
|
|
28
|
+
suggestion?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const getIntent: (value: string, ctx?: IntentContext) => string | null;
|
|
31
|
+
export default intent;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { CSSUnit, CSSMathValue, MathContext, MathResult, FluidTypeConfig } from '../core/types.js';
|
|
2
|
+
export type { CSSUnit, CSSMathValue, MathContext, MathResult, FluidTypeConfig };
|
|
3
|
+
export type MathOp = 'add' | 'subtract' | 'multiply' | 'divide';
|
|
4
|
+
export declare const math: {
|
|
5
|
+
/**
|
|
6
|
+
* Add two CSS values with unit resolution.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* math.add('10px', '2rem') // → '42px' (with default context)
|
|
10
|
+
* math.add('10px', '2rem', { rootFontSize: 16 }) // → '42px'
|
|
11
|
+
* math.add('10px', '2vw') // → 'calc(10px + 2vw)'
|
|
12
|
+
*/
|
|
13
|
+
add(a: string | number, b: string | number, context?: MathContext): MathResult;
|
|
14
|
+
/**
|
|
15
|
+
* Subtract two CSS values with unit resolution.
|
|
16
|
+
*/
|
|
17
|
+
subtract(a: string | number, b: string | number, context?: MathContext): MathResult;
|
|
18
|
+
/**
|
|
19
|
+
* Multiply two CSS values with unit resolution.
|
|
20
|
+
*/
|
|
21
|
+
multiply(a: string | number, b: string | number, context?: MathContext): MathResult;
|
|
22
|
+
/**
|
|
23
|
+
* Divide two CSS values with unit resolution.
|
|
24
|
+
*/
|
|
25
|
+
divide(a: string | number, b: string | number, context?: MathContext): MathResult;
|
|
26
|
+
/**
|
|
27
|
+
* Sum multiple CSS values.
|
|
28
|
+
*/
|
|
29
|
+
sum(...values: (string | number)[]): MathResult;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a CSS value to pixels.
|
|
32
|
+
*/
|
|
33
|
+
toPx(value: string | number, context?: MathContext): number;
|
|
34
|
+
/**
|
|
35
|
+
* Convert between CSS units.
|
|
36
|
+
*/
|
|
37
|
+
convert(value: string | number, toUnit: CSSUnit, context?: MathContext): MathResult;
|
|
38
|
+
/**
|
|
39
|
+
* Create a fluid typography clamp() expression.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* math.fluidType({ minSize: 14, maxSize: 20 })
|
|
43
|
+
* // → 'clamp(14px, 0.625vw + 12px, 20px)'
|
|
44
|
+
* math.fluidType({ minSize: 14, maxSize: 20, unit: 'rem', rootFontSize: 16 })
|
|
45
|
+
* // → 'clamp(0.875rem, 0.625vw + 0.75rem, 1.25rem)'
|
|
46
|
+
*/
|
|
47
|
+
fluidType(config: FluidTypeConfig): MathResult;
|
|
48
|
+
/**
|
|
49
|
+
* Scale a value by a factor with unit preservation.
|
|
50
|
+
*/
|
|
51
|
+
scale(value: string | number, factor: number): MathResult;
|
|
52
|
+
/**
|
|
53
|
+
* Clamp a CSS value between min and max.
|
|
54
|
+
*/
|
|
55
|
+
clampValue(value: string | number, min: string | number, max: string | number, context?: MathContext): MathResult;
|
|
56
|
+
/**
|
|
57
|
+
* Parse a CSS value into its numeric and unit parts.
|
|
58
|
+
*/
|
|
59
|
+
parse(value: string | number): CSSMathValue;
|
|
60
|
+
/**
|
|
61
|
+
* Check if two values have compatible units for direct operations.
|
|
62
|
+
*/
|
|
63
|
+
compatible(a: string | number, b: string | number): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Get the category of a CSS unit.
|
|
66
|
+
*/
|
|
67
|
+
unitCategory(unit: CSSUnit): string;
|
|
68
|
+
/**
|
|
69
|
+
* Create a CSS min() expression.
|
|
70
|
+
*/
|
|
71
|
+
cssMin(...values: (string | number)[]): string;
|
|
72
|
+
/**
|
|
73
|
+
* Create a CSS max() expression.
|
|
74
|
+
*/
|
|
75
|
+
cssMax(...values: (string | number)[]): string;
|
|
76
|
+
/**
|
|
77
|
+
* Format a number with specified precision.
|
|
78
|
+
*/
|
|
79
|
+
precision(value: number, decimals?: number): string;
|
|
80
|
+
};
|
|
81
|
+
export declare const add: (a: string | number, b: string | number, context?: MathContext) => MathResult;
|
|
82
|
+
export declare const subtract: (a: string | number, b: string | number, context?: MathContext) => MathResult;
|
|
83
|
+
export declare const multiply: (a: string | number, b: string | number, context?: MathContext) => MathResult;
|
|
84
|
+
export declare const divide: (a: string | number, b: string | number, context?: MathContext) => MathResult;
|
|
85
|
+
export declare const fluidType: (config: FluidTypeConfig) => MathResult;
|
|
86
|
+
export declare const convert: (value: string | number, toUnit: CSSUnit, context?: MathContext) => MathResult;
|
|
87
|
+
export declare const toPx: (value: string | number, context?: MathContext) => number;
|
|
88
|
+
export declare const scale: (value: string | number, factor: number) => MathResult;
|
|
89
|
+
export default math;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { StyleDefinition, StyleGraph, StyleGraphNode, StyleGraphEdge, GraphCompileOptions, GraphCompileResult } from '../core/types.js';
|
|
2
|
+
export type { StyleGraph, StyleGraphNode, StyleGraphEdge, GraphCompileOptions, GraphCompileResult };
|
|
3
|
+
export declare class StyleGraphCompiler {
|
|
4
|
+
private options;
|
|
5
|
+
constructor(options?: GraphCompileOptions);
|
|
6
|
+
/**
|
|
7
|
+
* Compile a set of style definitions through the graph compiler.
|
|
8
|
+
*/
|
|
9
|
+
compile(styles: Record<string, StyleDefinition>): GraphCompileResult;
|
|
10
|
+
/**
|
|
11
|
+
* Analyze a style graph without generating CSS.
|
|
12
|
+
*/
|
|
13
|
+
analyze(styles: Record<string, StyleDefinition>): StyleGraph;
|
|
14
|
+
/**
|
|
15
|
+
* Get optimization statistics for a graph.
|
|
16
|
+
*/
|
|
17
|
+
getStats(graph: StyleGraph): {
|
|
18
|
+
totalNodes: number;
|
|
19
|
+
deadNodes: number;
|
|
20
|
+
mergedGroups: number;
|
|
21
|
+
averageSpecificity: number;
|
|
22
|
+
deepestDependencyChain: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Update options.
|
|
26
|
+
*/
|
|
27
|
+
configure(options: Partial<GraphCompileOptions>): void;
|
|
28
|
+
}
|
|
29
|
+
export declare function compileGraph(styles: Record<string, StyleDefinition>, options?: GraphCompileOptions): GraphCompileResult;
|
|
30
|
+
export default StyleGraphCompiler;
|