@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.
Files changed (174) hide show
  1. package/dist/cli.mjs +1264 -27
  2. package/dist/css/index.d.ts +194 -0
  3. package/dist/css/index.js +721 -0
  4. package/dist/css/runtime.d.ts +92 -0
  5. package/dist/css/runtime.js +179 -0
  6. package/dist/index.js +1 -1
  7. package/dist/jsx-dev-runtime.js +1 -1
  8. package/dist/jsx-runtime.d.ts +5 -0
  9. package/dist/jsx-runtime.js +1 -1
  10. package/dist/src-CHiGeWfy.js +1 -0
  11. package/dist/vite-plugin.d.ts +122 -0
  12. package/dist/vite-plugin.js +1518 -0
  13. package/package.json +16 -2
  14. package/src/css/extract.ts +798 -0
  15. package/src/css/index.ts +10 -0
  16. package/src/css/inject.ts +205 -0
  17. package/src/css/inline.ts +182 -0
  18. package/src/css/minify.ts +70 -0
  19. package/src/css/optimizer.ts +6 -0
  20. package/src/css/runtime.ts +344 -0
  21. package/src/css-optimizer/README.md +353 -0
  22. package/src/css-optimizer/cooccurrence.ts +100 -0
  23. package/src/css-optimizer/core.ts +263 -0
  24. package/src/css-optimizer/extractors.ts +243 -0
  25. package/src/css-optimizer/hash.ts +54 -0
  26. package/src/css-optimizer/index.ts +129 -0
  27. package/src/css-optimizer/merge.ts +109 -0
  28. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  29. package/src/css-optimizer/parser.ts +120 -0
  30. package/src/css-optimizer/pattern.ts +171 -0
  31. package/src/css-optimizer/transformers.ts +301 -0
  32. package/src/css-optimizer/types.ts +128 -0
  33. package/src/event-utils.ts +227 -0
  34. package/src/index.ts +890 -0
  35. package/src/jsx-dev-runtime.ts +2 -0
  36. package/src/jsx-runtime.ts +398 -0
  37. package/src/vite-plugin.ts +718 -0
  38. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  39. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  40. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  41. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  42. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  43. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  44. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  45. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  46. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  50. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  51. 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
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  58. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  65. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  66. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  67. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  68. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  69. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  70. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  71. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  72. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  73. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  74. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  75. 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
  76. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  77. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  78. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  79. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  80. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  81. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  82. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  83. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  84. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  85. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  117. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  118. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  119. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  120. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  121. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  122. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  123. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  124. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  125. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  137. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  138. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  139. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  140. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  141. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  142. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  143. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  144. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  145. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  147. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  148. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  149. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  150. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  151. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  152. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  153. package/tests/context.test.ts +118 -0
  154. package/tests/css-optimizer-extractors.test.ts +264 -0
  155. package/tests/css-optimizer-integration.test.ts +566 -0
  156. package/tests/css-optimizer-transformers.test.ts +301 -0
  157. package/tests/css-optimizer.test.ts +646 -0
  158. package/tests/css-runtime.bench.ts +442 -0
  159. package/tests/css-runtime.test.ts +342 -0
  160. package/tests/dom.test.ts +872 -0
  161. package/tests/integration.test.ts +405 -0
  162. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  163. package/tests/jsx-runtime.test.tsx +393 -0
  164. package/tests/lifecycle.test.ts +833 -0
  165. package/tests/move-before.bench.ts +304 -0
  166. package/tests/preact-signals-comparison.test.ts +1608 -0
  167. package/tests/resource.test.ts +160 -0
  168. package/tests/router.test.ts +117 -0
  169. package/tests/show-initial-mount-leak.test.tsx +182 -0
  170. package/tests/solidjs-api.test.ts +659 -0
  171. package/tests/static-perf.bench.ts +64 -0
  172. package/tests/store.test.ts +263 -0
  173. package/tests/tsx-syntax.test.tsx +404 -0
  174. 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