@luna_ui/luna 0.3.3 → 0.3.5
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/dist/cli.mjs +1264 -27
- package/dist/css/index.d.ts +194 -0
- package/dist/css/index.js +721 -0
- package/dist/css/runtime.d.ts +92 -0
- package/dist/css/runtime.js +179 -0
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +5 -0
- package/dist/jsx-runtime.js +1 -1
- package/dist/src-CHiGeWfy.js +1 -0
- package/dist/vite-plugin.d.ts +122 -0
- package/dist/vite-plugin.js +1518 -0
- package/package.json +16 -2
- package/src/css/extract.ts +798 -0
- package/src/css/index.ts +10 -0
- package/src/css/inject.ts +205 -0
- package/src/css/inline.ts +182 -0
- package/src/css/minify.ts +70 -0
- package/src/css/optimizer.ts +6 -0
- package/src/css/runtime.ts +344 -0
- package/src/css-optimizer/README.md +353 -0
- package/src/css-optimizer/cooccurrence.ts +100 -0
- package/src/css-optimizer/core.ts +263 -0
- package/src/css-optimizer/extractors.ts +243 -0
- package/src/css-optimizer/hash.ts +54 -0
- package/src/css-optimizer/index.ts +129 -0
- package/src/css-optimizer/merge.ts +109 -0
- package/src/css-optimizer/moonbit-analyzer.ts +210 -0
- package/src/css-optimizer/parser.ts +120 -0
- package/src/css-optimizer/pattern.ts +171 -0
- package/src/css-optimizer/transformers.ts +301 -0
- package/src/css-optimizer/types.ts +128 -0
- package/src/event-utils.ts +227 -0
- package/src/index.ts +890 -0
- package/src/jsx-dev-runtime.ts +2 -0
- package/src/jsx-runtime.ts +398 -0
- package/src/vite-plugin.ts +718 -0
- package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-empty-to-non-empty-transition-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-renders-correctly-without-show--initial-items--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-with-context-renders-correctly-without-show-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-nested-components-with-context--forEach--and-show-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-inherit-context-from-Owner--fixed--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-work-together--context-uses-default--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-works-with-For-loop-items--component-body-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
- package/tests/context.test.ts +118 -0
- package/tests/css-optimizer-extractors.test.ts +264 -0
- package/tests/css-optimizer-integration.test.ts +566 -0
- package/tests/css-optimizer-transformers.test.ts +301 -0
- package/tests/css-optimizer.test.ts +646 -0
- package/tests/css-runtime.bench.ts +442 -0
- package/tests/css-runtime.test.ts +342 -0
- package/tests/dom.test.ts +872 -0
- package/tests/integration.test.ts +405 -0
- package/tests/issue-5-for-infinite-loop.test.ts +516 -0
- package/tests/jsx-runtime.test.tsx +393 -0
- package/tests/lifecycle.test.ts +833 -0
- package/tests/move-before.bench.ts +304 -0
- package/tests/preact-signals-comparison.test.ts +1608 -0
- package/tests/resource.test.ts +160 -0
- package/tests/router.test.ts +117 -0
- package/tests/show-initial-mount-leak.test.tsx +182 -0
- package/tests/solidjs-api.test.ts +659 -0
- package/tests/static-perf.bench.ts +64 -0
- package/tests/store.test.ts +263 -0
- package/tests/tsx-syntax.test.tsx +404 -0
- package/dist/src-DGWY0NYx.js +0 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development Mode CSS Runtime
|
|
3
|
+
*
|
|
4
|
+
* Provides runtime CSS injection for development mode.
|
|
5
|
+
* In production, this module should NOT be included.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Dynamic CSS injection when rules are missing
|
|
9
|
+
* - Warning mechanism for missing CSS rules
|
|
10
|
+
* - Can be used as a fallback for hot-reload scenarios
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
export interface CssRuntimeOptions {
|
|
18
|
+
/** Warn in console when generating CSS at runtime (default: true) */
|
|
19
|
+
warnOnGenerate?: boolean;
|
|
20
|
+
/** Style element ID (default: "luna-dev-css") */
|
|
21
|
+
styleId?: string;
|
|
22
|
+
/** Whether to log each rule generation (default: false) */
|
|
23
|
+
verbose?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CssRuntimeState {
|
|
27
|
+
/** Generated CSS rules */
|
|
28
|
+
rules: Map<string, string>;
|
|
29
|
+
/** Style element for injection */
|
|
30
|
+
styleEl: HTMLStyleElement | null;
|
|
31
|
+
/** Whether initialized */
|
|
32
|
+
initialized: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// DJB2 Hash (must match registry.mbt and extract.ts)
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
function djb2Hash(s: string): number {
|
|
40
|
+
let hash = 5381;
|
|
41
|
+
for (let i = 0; i < s.length; i++) {
|
|
42
|
+
const c = s.charCodeAt(i);
|
|
43
|
+
hash = ((hash << 5) + hash + c) >>> 0;
|
|
44
|
+
}
|
|
45
|
+
return hash;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toBase36(n: number): string {
|
|
49
|
+
const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
50
|
+
n = n & 0xffffff;
|
|
51
|
+
if (n === 0) return "0";
|
|
52
|
+
let result = "";
|
|
53
|
+
while (n > 0) {
|
|
54
|
+
result = chars[n % 36] + result;
|
|
55
|
+
n = Math.floor(n / 36);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hashClassName(decl: string): string {
|
|
61
|
+
const hash = djb2Hash(decl);
|
|
62
|
+
return "_" + toBase36(hash);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Runtime State
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
const state: CssRuntimeState = {
|
|
70
|
+
rules: new Map(),
|
|
71
|
+
styleEl: null,
|
|
72
|
+
initialized: false,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let options: CssRuntimeOptions = {
|
|
76
|
+
warnOnGenerate: true,
|
|
77
|
+
styleId: "luna-dev-css",
|
|
78
|
+
verbose: false,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Core Functions
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Initialize the CSS runtime
|
|
87
|
+
*/
|
|
88
|
+
export function initCssRuntime(opts: CssRuntimeOptions = {}): void {
|
|
89
|
+
options = { ...options, ...opts };
|
|
90
|
+
|
|
91
|
+
if (typeof document === "undefined") {
|
|
92
|
+
// SSR mode - no-op
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (state.initialized) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if style element already exists
|
|
101
|
+
state.styleEl = document.getElementById(options.styleId!) as HTMLStyleElement;
|
|
102
|
+
if (!state.styleEl) {
|
|
103
|
+
state.styleEl = document.createElement("style");
|
|
104
|
+
state.styleEl.id = options.styleId!;
|
|
105
|
+
state.styleEl.setAttribute("data-luna-dev", "true");
|
|
106
|
+
document.head.appendChild(state.styleEl);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
state.initialized = true;
|
|
110
|
+
|
|
111
|
+
if (options.verbose) {
|
|
112
|
+
console.log("[luna-css] Runtime initialized");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if a CSS class exists in stylesheets
|
|
118
|
+
*/
|
|
119
|
+
export function hasClass(className: string): boolean {
|
|
120
|
+
if (typeof document === "undefined") {
|
|
121
|
+
return true; // Assume exists in SSR
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check our generated rules first
|
|
125
|
+
if (state.rules.has(className)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check all stylesheets
|
|
130
|
+
for (const sheet of document.styleSheets) {
|
|
131
|
+
try {
|
|
132
|
+
const rules = (sheet as CSSStyleSheet).cssRules;
|
|
133
|
+
for (const rule of rules) {
|
|
134
|
+
if (rule instanceof CSSStyleRule) {
|
|
135
|
+
if (rule.selectorText === `.${className}`) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// Cross-origin stylesheet, skip
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generate CSS for a property:value declaration
|
|
150
|
+
* Returns the class name
|
|
151
|
+
*/
|
|
152
|
+
export function css(property: string, value: string): string {
|
|
153
|
+
const decl = `${property}:${value}`;
|
|
154
|
+
const className = hashClassName(decl);
|
|
155
|
+
|
|
156
|
+
if (!hasClass(className)) {
|
|
157
|
+
injectRule(className, decl);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return className;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generate CSS for multiple property:value pairs
|
|
165
|
+
* Returns space-separated class names
|
|
166
|
+
*/
|
|
167
|
+
export function styles(pairs: [string, string][]): string {
|
|
168
|
+
return pairs.map(([p, v]) => css(p, v)).join(" ");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generate CSS for pseudo-class
|
|
173
|
+
*/
|
|
174
|
+
export function on(pseudo: string, property: string, value: string): string {
|
|
175
|
+
const key = `${pseudo}:${property}:${value}`;
|
|
176
|
+
const className = hashClassName(key);
|
|
177
|
+
|
|
178
|
+
if (!hasClass(className)) {
|
|
179
|
+
injectPseudoRule(className, pseudo, property, value);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return className;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Hover shorthand
|
|
187
|
+
*/
|
|
188
|
+
export function hover(property: string, value: string): string {
|
|
189
|
+
return on(":hover", property, value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Focus shorthand
|
|
194
|
+
*/
|
|
195
|
+
export function focus(property: string, value: string): string {
|
|
196
|
+
return on(":focus", property, value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Active shorthand
|
|
201
|
+
*/
|
|
202
|
+
export function active(property: string, value: string): string {
|
|
203
|
+
return on(":active", property, value);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Media query CSS generation
|
|
208
|
+
*/
|
|
209
|
+
export function media(condition: string, property: string, value: string): string {
|
|
210
|
+
const key = `@media(${condition}):${property}:${value}`;
|
|
211
|
+
const className = hashClassName(key);
|
|
212
|
+
|
|
213
|
+
if (!hasClass(className)) {
|
|
214
|
+
injectMediaRule(className, condition, property, value);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return className;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Responsive breakpoint shortcuts
|
|
222
|
+
*/
|
|
223
|
+
export const at_sm = (p: string, v: string) => media("min-width:640px", p, v);
|
|
224
|
+
export const at_md = (p: string, v: string) => media("min-width:768px", p, v);
|
|
225
|
+
export const at_lg = (p: string, v: string) => media("min-width:1024px", p, v);
|
|
226
|
+
export const at_xl = (p: string, v: string) => media("min-width:1280px", p, v);
|
|
227
|
+
export const dark = (p: string, v: string) =>
|
|
228
|
+
media("prefers-color-scheme:dark", p, v);
|
|
229
|
+
|
|
230
|
+
// =============================================================================
|
|
231
|
+
// Internal Injection
|
|
232
|
+
// =============================================================================
|
|
233
|
+
|
|
234
|
+
function injectRule(className: string, decl: string): void {
|
|
235
|
+
if (!state.initialized) {
|
|
236
|
+
initCssRuntime();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const rule = `.${className}{${decl}}`;
|
|
240
|
+
state.rules.set(className, rule);
|
|
241
|
+
|
|
242
|
+
if (state.styleEl) {
|
|
243
|
+
state.styleEl.textContent += rule;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (options.warnOnGenerate) {
|
|
247
|
+
console.warn(
|
|
248
|
+
`[luna-css] Generated at runtime: .${className} { ${decl} }`,
|
|
249
|
+
"\n → Consider running 'luna css extract' to pre-generate CSS"
|
|
250
|
+
);
|
|
251
|
+
} else if (options.verbose) {
|
|
252
|
+
console.log(`[luna-css] ${rule}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function injectPseudoRule(
|
|
257
|
+
className: string,
|
|
258
|
+
pseudo: string,
|
|
259
|
+
property: string,
|
|
260
|
+
value: string
|
|
261
|
+
): void {
|
|
262
|
+
if (!state.initialized) {
|
|
263
|
+
initCssRuntime();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const rule = `.${className}${pseudo}{${property}:${value}}`;
|
|
267
|
+
state.rules.set(className, rule);
|
|
268
|
+
|
|
269
|
+
if (state.styleEl) {
|
|
270
|
+
state.styleEl.textContent += rule;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (options.warnOnGenerate) {
|
|
274
|
+
console.warn(
|
|
275
|
+
`[luna-css] Generated at runtime: .${className}${pseudo} { ${property}: ${value} }`,
|
|
276
|
+
"\n → Consider running 'luna css extract' to pre-generate CSS"
|
|
277
|
+
);
|
|
278
|
+
} else if (options.verbose) {
|
|
279
|
+
console.log(`[luna-css] ${rule}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function injectMediaRule(
|
|
284
|
+
className: string,
|
|
285
|
+
condition: string,
|
|
286
|
+
property: string,
|
|
287
|
+
value: string
|
|
288
|
+
): void {
|
|
289
|
+
if (!state.initialized) {
|
|
290
|
+
initCssRuntime();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const rule = `@media(${condition}){.${className}{${property}:${value}}}`;
|
|
294
|
+
state.rules.set(className, rule);
|
|
295
|
+
|
|
296
|
+
if (state.styleEl) {
|
|
297
|
+
state.styleEl.textContent += rule;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (options.warnOnGenerate) {
|
|
301
|
+
console.warn(
|
|
302
|
+
`[luna-css] Generated at runtime: @media(${condition}) { .${className} { ${property}: ${value} } }`,
|
|
303
|
+
"\n → Consider running 'luna css extract' to pre-generate CSS"
|
|
304
|
+
);
|
|
305
|
+
} else if (options.verbose) {
|
|
306
|
+
console.log(`[luna-css] ${rule}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// =============================================================================
|
|
311
|
+
// Utility Functions
|
|
312
|
+
// =============================================================================
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get all generated CSS as a string
|
|
316
|
+
*/
|
|
317
|
+
export function getGeneratedCss(): string {
|
|
318
|
+
return Array.from(state.rules.values()).join("");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get count of generated rules
|
|
323
|
+
*/
|
|
324
|
+
export function getGeneratedCount(): number {
|
|
325
|
+
return state.rules.size;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Reset the runtime state (for testing)
|
|
330
|
+
*/
|
|
331
|
+
export function resetRuntime(): void {
|
|
332
|
+
state.rules.clear();
|
|
333
|
+
if (state.styleEl) {
|
|
334
|
+
state.styleEl.textContent = "";
|
|
335
|
+
}
|
|
336
|
+
state.initialized = false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Combine multiple class names
|
|
341
|
+
*/
|
|
342
|
+
export function combine(classes: string[]): string {
|
|
343
|
+
return classes.filter(Boolean).join(" ");
|
|
344
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# CSS Co-occurrence Optimizer
|
|
2
|
+
|
|
3
|
+
A standalone, framework-agnostic library for optimizing CSS by merging frequently co-occurring classes. Works with HTML, React, Svelte, Vue, and any framework that uses class-based styling.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ CSS Optimizer │
|
|
10
|
+
├─────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ Extractors Core Transformers │
|
|
12
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
|
13
|
+
│ │ HTML │ │ Pattern │ │ HTML │ │
|
|
14
|
+
│ │ JSX/TSX │───▶│ Analysis │─────▶│ JSX/TSX │ │
|
|
15
|
+
│ │ Svelte │ │ + Merge │ │ Svelte │ │
|
|
16
|
+
│ │ Custom │ └──────────┘ │ Custom │ │
|
|
17
|
+
│ └──────────┘ └──────────────┘ │
|
|
18
|
+
└─────────────────────────────────────────────────────────────┘
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **Extractors**: Pluggable class extraction from different source formats
|
|
22
|
+
- **Core**: Framework-agnostic pattern analysis and CSS generation
|
|
23
|
+
- **Transformers**: Pluggable output transformation for different formats
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @luna_ui/luna
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Framework-Agnostic Usage (Recommended)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import {
|
|
37
|
+
optimizeCore,
|
|
38
|
+
htmlExtractor,
|
|
39
|
+
htmlTransformer,
|
|
40
|
+
} from "@luna_ui/luna/css-optimizer";
|
|
41
|
+
|
|
42
|
+
// 1. Extract class usages from your source files
|
|
43
|
+
const usages = htmlExtractor.extract(html, { classPrefix: "_" });
|
|
44
|
+
|
|
45
|
+
// 2. Build class-to-declaration mapping
|
|
46
|
+
const classToDecl = new Map([
|
|
47
|
+
["_flex", "display:flex"],
|
|
48
|
+
["_gap", "gap:1rem"],
|
|
49
|
+
["_p4", "padding:1rem"],
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
// 3. Optimize
|
|
53
|
+
const result = optimizeCore(usages, css, classToDecl, {
|
|
54
|
+
minFrequency: 2,
|
|
55
|
+
maxPatternSize: 5,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 4. Transform output
|
|
59
|
+
const optimizedHtml = htmlTransformer.transform(html, result.mergeMap);
|
|
60
|
+
|
|
61
|
+
console.log(result.css); // Optimized CSS
|
|
62
|
+
console.log(optimizedHtml); // Transformed HTML
|
|
63
|
+
console.log(result.stats); // { mergedPatterns, estimatedBytesSaved, ... }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### React/JSX Usage
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import {
|
|
70
|
+
optimizeCore,
|
|
71
|
+
jsxExtractor,
|
|
72
|
+
jsxTransformer,
|
|
73
|
+
} from "@luna_ui/luna/css-optimizer";
|
|
74
|
+
|
|
75
|
+
// Extract from JSX
|
|
76
|
+
const usages = jsxExtractor.extract(jsxCode);
|
|
77
|
+
|
|
78
|
+
// ... optimize ...
|
|
79
|
+
|
|
80
|
+
// Transform JSX
|
|
81
|
+
const optimizedJsx = jsxTransformer.transform(jsxCode, result.mergeMap);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Svelte Usage
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import {
|
|
88
|
+
optimizeCore,
|
|
89
|
+
svelteExtractor,
|
|
90
|
+
svelteTransformer,
|
|
91
|
+
} from "@luna_ui/luna/css-optimizer";
|
|
92
|
+
|
|
93
|
+
// Extract from Svelte (preserves {expressions})
|
|
94
|
+
const usages = svelteExtractor.extract(svelteCode);
|
|
95
|
+
|
|
96
|
+
// ... optimize ...
|
|
97
|
+
|
|
98
|
+
// Transform Svelte (preserves {expressions})
|
|
99
|
+
const optimizedSvelte = svelteTransformer.transform(svelteCode, result.mergeMap);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Multi-Framework Projects
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import {
|
|
106
|
+
optimizeCore,
|
|
107
|
+
multiExtractor,
|
|
108
|
+
multiTransformer,
|
|
109
|
+
} from "@luna_ui/luna/css-optimizer";
|
|
110
|
+
|
|
111
|
+
// Extract from multiple file types
|
|
112
|
+
const files = [
|
|
113
|
+
{ content: htmlCode, path: "index.html" },
|
|
114
|
+
{ content: reactCode, path: "app.tsx" },
|
|
115
|
+
{ content: svelteCode, path: "widget.svelte" },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const usages = multiExtractor.extractFromFiles(files);
|
|
119
|
+
|
|
120
|
+
// ... optimize ...
|
|
121
|
+
|
|
122
|
+
// Transform all files
|
|
123
|
+
const transformedFiles = multiTransformer.transformFiles(files, result.mergeMap);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Luna-Specific Convenience API
|
|
127
|
+
|
|
128
|
+
For Luna projects, use the simplified API:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { optimizeCss, optimizeHtml } from "@luna_ui/luna/css-optimizer";
|
|
132
|
+
|
|
133
|
+
// Luna uses declaration -> className mapping
|
|
134
|
+
const mapping = {
|
|
135
|
+
"display:flex": "_flex",
|
|
136
|
+
"gap:1rem": "_gap",
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = optimizeCss(css, html, mapping, {
|
|
140
|
+
minFrequency: 2,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const optimizedHtml = optimizeHtml(html, result.mergeMap);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## API Reference
|
|
147
|
+
|
|
148
|
+
### Core Functions
|
|
149
|
+
|
|
150
|
+
#### `optimizeCore(usages, css, classToDeclaration, options?)`
|
|
151
|
+
|
|
152
|
+
Framework-agnostic core optimizer.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
function optimizeCore(
|
|
156
|
+
usages: ClassUsage[],
|
|
157
|
+
css: string,
|
|
158
|
+
classToDeclaration: Map<string, string>,
|
|
159
|
+
options?: CoreOptimizeOptions
|
|
160
|
+
): CoreOptimizeResult;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `applyMergeToClasses(classes, mergeMap, classPrefix?)`
|
|
164
|
+
|
|
165
|
+
Apply merge map to a class array (useful for runtime optimization).
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
function applyMergeToClasses(
|
|
169
|
+
classes: string[],
|
|
170
|
+
mergeMap: Map<string, string>,
|
|
171
|
+
classPrefix?: string
|
|
172
|
+
): string[];
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Extractors
|
|
176
|
+
|
|
177
|
+
All extractors implement the `ClassExtractor` interface:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
interface ClassExtractor {
|
|
181
|
+
name: string;
|
|
182
|
+
extract(content: string, options?: ExtractorOptions): ClassUsage[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface ExtractorOptions {
|
|
186
|
+
classPrefix?: string; // Filter classes by prefix (default: "_")
|
|
187
|
+
minClasses?: number; // Minimum classes per element (default: 2)
|
|
188
|
+
source?: string; // Source identifier for debugging
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| Extractor | Description | Import |
|
|
193
|
+
|-----------|-------------|--------|
|
|
194
|
+
| `HtmlExtractor` | Extracts from `class="..."` | `htmlExtractor` |
|
|
195
|
+
| `JsxExtractor` | Extracts from `className="..."` | `jsxExtractor` |
|
|
196
|
+
| `SvelteExtractor` | Extracts from `class="..."`, preserves `{expr}` | `svelteExtractor` |
|
|
197
|
+
| `MultiExtractor` | Auto-selects by file extension | `multiExtractor` |
|
|
198
|
+
|
|
199
|
+
### Transformers
|
|
200
|
+
|
|
201
|
+
All transformers implement the `ClassTransformer` interface:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
interface ClassTransformer {
|
|
205
|
+
name: string;
|
|
206
|
+
transform(
|
|
207
|
+
content: string,
|
|
208
|
+
mergeMap: Map<string, string>,
|
|
209
|
+
options?: TransformerOptions
|
|
210
|
+
): string;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
interface TransformerOptions {
|
|
214
|
+
classPrefix?: string; // Class prefix for identifying mergeable classes
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
| Transformer | Description | Import |
|
|
219
|
+
|-------------|-------------|--------|
|
|
220
|
+
| `HtmlTransformer` | Transforms `class="..."` | `htmlTransformer` |
|
|
221
|
+
| `JsxTransformer` | Transforms `className="..."` | `jsxTransformer` |
|
|
222
|
+
| `SvelteTransformer` | Transforms `class="..."`, preserves `{expr}` | `svelteTransformer` |
|
|
223
|
+
| `MultiTransformer` | Auto-selects by file extension | `multiTransformer` |
|
|
224
|
+
|
|
225
|
+
### Options
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
interface CoreOptimizeOptions {
|
|
229
|
+
minFrequency?: number; // Min occurrences to merge (default: 2)
|
|
230
|
+
maxPatternSize?: number; // Max classes per pattern (default: 5)
|
|
231
|
+
pretty?: boolean; // Pretty-print CSS (default: false)
|
|
232
|
+
verbose?: boolean; // Enable logging (default: false)
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Custom Extractors
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { ClassExtractor, ClassUsage } from "@luna_ui/luna/css-optimizer";
|
|
240
|
+
|
|
241
|
+
class VueExtractor implements ClassExtractor {
|
|
242
|
+
name = "vue";
|
|
243
|
+
|
|
244
|
+
extract(content: string, options = {}): ClassUsage[] {
|
|
245
|
+
// Parse Vue SFC template section
|
|
246
|
+
// Return array of ClassUsage objects
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Register with MultiExtractor
|
|
251
|
+
multiExtractor.register("vue", new VueExtractor());
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Build Tool Integration
|
|
255
|
+
|
|
256
|
+
### Vite Plugin (Luna)
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// vite.config.ts
|
|
260
|
+
import { lunaCss } from "@luna_ui/luna/vite-plugin";
|
|
261
|
+
|
|
262
|
+
export default {
|
|
263
|
+
plugins: [
|
|
264
|
+
lunaCss({
|
|
265
|
+
src: ["src"],
|
|
266
|
+
experimental: {
|
|
267
|
+
optimize: true,
|
|
268
|
+
optimizeMinFrequency: 2,
|
|
269
|
+
optimizeMaxPatternSize: 5,
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Generic Build Pipeline
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// build.ts
|
|
280
|
+
import {
|
|
281
|
+
optimizeCore,
|
|
282
|
+
multiExtractor,
|
|
283
|
+
multiTransformer,
|
|
284
|
+
} from "@luna_ui/luna/css-optimizer";
|
|
285
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
286
|
+
import { glob } from "glob";
|
|
287
|
+
|
|
288
|
+
// 1. Collect source files
|
|
289
|
+
const files = glob.sync("src/**/*.{html,jsx,tsx,svelte}").map((path) => ({
|
|
290
|
+
path,
|
|
291
|
+
content: readFileSync(path, "utf-8"),
|
|
292
|
+
}));
|
|
293
|
+
|
|
294
|
+
// 2. Extract class usages
|
|
295
|
+
const usages = multiExtractor.extractFromFiles(files);
|
|
296
|
+
|
|
297
|
+
// 3. Build class-to-declaration map (from your CSS tool)
|
|
298
|
+
const classToDecl = buildClassMap(css);
|
|
299
|
+
|
|
300
|
+
// 4. Optimize
|
|
301
|
+
const result = optimizeCore(usages, css, classToDecl);
|
|
302
|
+
|
|
303
|
+
// 5. Write optimized CSS
|
|
304
|
+
writeFileSync("dist/styles.css", result.css);
|
|
305
|
+
|
|
306
|
+
// 6. Transform source files
|
|
307
|
+
const transformed = multiTransformer.transformFiles(files, result.mergeMap);
|
|
308
|
+
for (const file of transformed) {
|
|
309
|
+
writeFileSync(`dist/${file.path}`, file.content);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## How It Works
|
|
314
|
+
|
|
315
|
+
### Pattern Mining
|
|
316
|
+
|
|
317
|
+
1. **Extract**: Parse source files to find class attribute values
|
|
318
|
+
2. **Analyze**: Count co-occurrence frequency of class combinations
|
|
319
|
+
3. **Mine**: Find patterns (pairs, triples, etc.) that meet frequency threshold
|
|
320
|
+
4. **Filter**: Remove subsumed patterns (subsets with similar frequency)
|
|
321
|
+
5. **Merge**: Generate combined CSS rules with deterministic class names
|
|
322
|
+
|
|
323
|
+
### Savings Estimation
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
HTML savings = (classCount - 1) * avgClassNameLength * frequency
|
|
327
|
+
CSS savings = (classCount - 1) * avgRuleSize
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Deterministic Naming
|
|
331
|
+
|
|
332
|
+
Merged classes use DJB2 hash of sorted declarations:
|
|
333
|
+
```
|
|
334
|
+
_flex _gap _p4 → _m2rld5
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
This ensures consistent naming across builds.
|
|
338
|
+
|
|
339
|
+
## Limitations
|
|
340
|
+
|
|
341
|
+
- Regex-based extraction (not full AST)
|
|
342
|
+
- Only static class values (not computed/dynamic)
|
|
343
|
+
- Pseudo-class/media rules preserved but not merged
|
|
344
|
+
- Single-quoted class attributes not supported (use double quotes)
|
|
345
|
+
|
|
346
|
+
## Future Improvements
|
|
347
|
+
|
|
348
|
+
- [ ] AST-based extraction for React/Svelte
|
|
349
|
+
- [ ] Dynamic class detection
|
|
350
|
+
- [ ] Pseudo-class pattern merging
|
|
351
|
+
- [ ] Source map generation
|
|
352
|
+
- [ ] Webpack plugin
|
|
353
|
+
- [ ] CLI tool
|