@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,129 @@
1
+ /**
2
+ * CSS Co-occurrence Optimizer
3
+ *
4
+ * A standalone, framework-agnostic library for optimizing CSS by merging
5
+ * frequently co-occurring classes. Works with HTML, React, Svelte, and more.
6
+ *
7
+ * ## Architecture
8
+ *
9
+ * - **Core**: Pure functions for pattern analysis (no framework dependency)
10
+ * - **Extractors**: Pluggable class extraction (HTML, JSX, Svelte, etc.)
11
+ * - **Transformers**: Pluggable output transformation
12
+ *
13
+ * ## Quick Start (Framework-agnostic)
14
+ *
15
+ * ```typescript
16
+ * import {
17
+ * optimizeCore,
18
+ * htmlExtractor,
19
+ * htmlTransformer
20
+ * } from "@luna_ui/luna/css-optimizer";
21
+ *
22
+ * // 1. Extract class usages
23
+ * const usages = htmlExtractor.extract(html);
24
+ *
25
+ * // 2. Optimize
26
+ * const classToDecl = new Map([["_flex", "display:flex"], ...]);
27
+ * const result = optimizeCore(usages, css, classToDecl);
28
+ *
29
+ * // 3. Transform output
30
+ * const optimizedHtml = htmlTransformer.transform(html, result.mergeMap);
31
+ * ```
32
+ *
33
+ * ## Legacy API (Luna-specific convenience)
34
+ *
35
+ * ```typescript
36
+ * import { optimizeCss, optimizeHtml } from "@luna_ui/luna/css-optimizer";
37
+ *
38
+ * const result = optimizeCss(css, html, mapping, { minFrequency: 2 });
39
+ * const optimizedHtml = optimizeHtml(html, result.mergeMap);
40
+ * ```
41
+ */
42
+
43
+ // Types
44
+ export type {
45
+ ClassUsage,
46
+ CoOccurrence,
47
+ MergePattern,
48
+ OptimizeResult,
49
+ OptimizeOptions,
50
+ ClassNameGenerator,
51
+ CssRule,
52
+ // MoonBit analyzer types
53
+ ClassCooccurrence,
54
+ AnalyzerWarning,
55
+ } from "./types.js";
56
+
57
+ // Core (framework-agnostic)
58
+ export {
59
+ optimizeCore,
60
+ applyMergeToClasses,
61
+ type CoreOptimizeOptions,
62
+ type CoreOptimizeResult,
63
+ } from "./core.js";
64
+
65
+ // Extractors
66
+ export {
67
+ HtmlExtractor,
68
+ JsxExtractor,
69
+ SvelteExtractor,
70
+ MultiExtractor,
71
+ htmlExtractor,
72
+ jsxExtractor,
73
+ svelteExtractor,
74
+ multiExtractor,
75
+ type ClassExtractor,
76
+ type ExtractorOptions,
77
+ } from "./extractors.js";
78
+
79
+ // Transformers
80
+ export {
81
+ HtmlTransformer,
82
+ JsxTransformer,
83
+ SvelteTransformer,
84
+ MultiTransformer,
85
+ htmlTransformer,
86
+ jsxTransformer,
87
+ svelteTransformer,
88
+ multiTransformer,
89
+ type ClassTransformer,
90
+ type TransformerOptions,
91
+ } from "./transformers.js";
92
+
93
+ // Hash utilities
94
+ export { djb2Hash, toBase36, hashClassName, hashMergedClassName } from "./hash.js";
95
+
96
+ // Parsing utilities (for advanced use)
97
+ export {
98
+ extractClassUsages,
99
+ parseCssRules,
100
+ buildClassToDeclarationMap,
101
+ extractUniqueClasses,
102
+ } from "./parser.js";
103
+
104
+ // Co-occurrence analysis (for advanced use)
105
+ export {
106
+ buildCooccurrenceMatrix,
107
+ matrixToCooccurrences,
108
+ getTopCooccurrences,
109
+ buildAdjacencyList,
110
+ } from "./cooccurrence.js";
111
+
112
+ // Pattern mining (for advanced use)
113
+ export {
114
+ findFrequentPatterns,
115
+ removeSubsumedPatterns,
116
+ groupByClassSet,
117
+ } from "./pattern.js";
118
+
119
+ // Legacy API (convenience wrappers)
120
+ export { optimizeCss, optimizeHtml, optimize } from "./merge.js";
121
+
122
+ // MoonBit static analyzer (for Luna projects)
123
+ export {
124
+ analyzeFile,
125
+ analyzeDirectory,
126
+ convertToOptimizerInput,
127
+ type MoonBitAnalysisResult,
128
+ type ConvertedResult,
129
+ } from "./moonbit-analyzer.js";
@@ -0,0 +1,109 @@
1
+ /**
2
+ * CSS class merging utilities
3
+ *
4
+ * This module provides Luna-specific convenience wrappers around the
5
+ * framework-agnostic core optimizer.
6
+ */
7
+
8
+ import type { ClassUsage, OptimizeResult, OptimizeOptions } from "./types.js";
9
+ import { optimizeCore } from "./core.js";
10
+ import { htmlExtractor } from "./extractors.js";
11
+ import { htmlTransformer } from "./transformers.js";
12
+
13
+ /**
14
+ * Framework-agnostic optimize function
15
+ *
16
+ * This is the recommended API for non-Luna projects.
17
+ * It takes pre-extracted class usages and a class-to-declaration map.
18
+ *
19
+ * @param usages - Class usages extracted from your source files
20
+ * @param css - Original CSS content
21
+ * @param classToDeclaration - Map from class name to CSS declaration
22
+ * @param options - Optimization options
23
+ */
24
+ export function optimize(
25
+ usages: ClassUsage[],
26
+ css: string,
27
+ classToDeclaration: Map<string, string>,
28
+ options: OptimizeOptions = {}
29
+ ): OptimizeResult {
30
+ return optimizeCore(usages, css, classToDeclaration, options);
31
+ }
32
+
33
+ /**
34
+ * Optimize CSS by merging co-occurring classes (Luna convenience wrapper)
35
+ *
36
+ * This is a convenience function that:
37
+ * 1. Extracts class usages from HTML using HtmlExtractor
38
+ * 2. Converts Luna's declaration mapping format
39
+ * 3. Calls the core optimizer
40
+ *
41
+ * For non-Luna projects, use the `optimize` function instead.
42
+ *
43
+ * @param css - Original CSS content
44
+ * @param html - HTML content to analyze
45
+ * @param declarationMapping - Luna's declaration -> class name mapping
46
+ * @param options - Optimization options
47
+ */
48
+ export function optimizeCss(
49
+ css: string,
50
+ html: string,
51
+ declarationMapping: Record<string, string>,
52
+ options: OptimizeOptions = {}
53
+ ): OptimizeResult {
54
+ const { classPrefix = "_", verbose = false } = options;
55
+
56
+ const log = (msg: string) => {
57
+ if (verbose) {
58
+ console.log(`[optimizer] ${msg}`);
59
+ }
60
+ };
61
+
62
+ // Step 1: Extract class usages using the HTML extractor
63
+ const usages = htmlExtractor.extract(html, {
64
+ classPrefix,
65
+ source: "html",
66
+ });
67
+
68
+ log(`Found ${usages.length} class usage sites`);
69
+
70
+ if (usages.length === 0) {
71
+ return {
72
+ css,
73
+ mergeMap: new Map(),
74
+ patterns: [],
75
+ stats: {
76
+ originalClasses: Object.keys(declarationMapping).length,
77
+ mergedPatterns: 0,
78
+ estimatedBytesSaved: 0,
79
+ },
80
+ };
81
+ }
82
+
83
+ // Step 2: Build class -> declaration map (reverse of Luna's mapping)
84
+ const classToDecl = new Map<string, string>();
85
+ for (const [decl, cls] of Object.entries(declarationMapping)) {
86
+ classToDecl.set(cls, decl);
87
+ }
88
+
89
+ // Step 3: Call core optimizer
90
+ return optimizeCore(usages, css, classToDecl, options);
91
+ }
92
+
93
+ /**
94
+ * Apply optimization to HTML by replacing class combinations (Luna convenience wrapper)
95
+ *
96
+ * This is a convenience function that uses HtmlTransformer.
97
+ * For non-Luna projects, use the transformer directly.
98
+ *
99
+ * @param html - HTML content to transform
100
+ * @param mergeMap - Merge map from optimizeCss result
101
+ * @param classPrefix - Class prefix (default: "_")
102
+ */
103
+ export function optimizeHtml(
104
+ html: string,
105
+ mergeMap: Map<string, string>,
106
+ classPrefix = "_"
107
+ ): string {
108
+ return htmlTransformer.transform(html, mergeMap, { classPrefix });
109
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * MoonBit Static Analyzer Integration
3
+ *
4
+ * Calls the MoonBit-based CSS static analyzer to extract class co-occurrences
5
+ * from MoonBit source files.
6
+ */
7
+
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import type { ClassCooccurrence, AnalyzerWarning, ClassUsage } from "./types.js";
12
+
13
+ // Lazy-loaded MoonBit analyzer module
14
+ let analyzerModule: { analyze_file_json: (source: string, file: string) => string } | null = null;
15
+
16
+ /**
17
+ * Get the path to the MoonBit analyzer JS module
18
+ */
19
+ function getAnalyzerPath(): string {
20
+ // When running from dist, the analyzer is relative to the package
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+
23
+ // Try multiple possible locations
24
+ const candidates = [
25
+ // Development: from dist/ to target/ (bundled cli.mjs)
26
+ path.resolve(__dirname, "../../../target/js/release/build/luna/css/analyzer/analyzer.js"),
27
+ // Development: from src/css-optimizer/ to target/
28
+ path.resolve(__dirname, "../../../../target/js/release/build/luna/css/analyzer/analyzer.js"),
29
+ // Installed package
30
+ path.resolve(__dirname, "../moonbit/analyzer.js"),
31
+ ];
32
+
33
+ for (const candidate of candidates) {
34
+ if (fs.existsSync(candidate)) {
35
+ return candidate;
36
+ }
37
+ }
38
+
39
+ throw new Error(
40
+ `MoonBit analyzer not found. Tried:\n${candidates.join("\n")}\nRun 'moon build --target js src/luna/css/analyzer' first.`
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Load the MoonBit analyzer module
46
+ */
47
+ async function loadAnalyzer(): Promise<typeof analyzerModule> {
48
+ if (analyzerModule) {
49
+ return analyzerModule;
50
+ }
51
+
52
+ const analyzerPath = getAnalyzerPath();
53
+ analyzerModule = await import(analyzerPath);
54
+ return analyzerModule;
55
+ }
56
+
57
+ /**
58
+ * Result from analyzing a single file
59
+ */
60
+ export interface MoonBitAnalysisResult {
61
+ cooccurrences: ClassCooccurrence[];
62
+ warnings: AnalyzerWarning[];
63
+ }
64
+
65
+ /**
66
+ * Analyze a single MoonBit source file
67
+ */
68
+ export async function analyzeFile(
69
+ source: string,
70
+ filePath: string
71
+ ): Promise<MoonBitAnalysisResult> {
72
+ const analyzer = await loadAnalyzer();
73
+ if (!analyzer) {
74
+ throw new Error("Failed to load MoonBit analyzer");
75
+ }
76
+
77
+ const jsonResult = analyzer.analyze_file_json(source, filePath);
78
+ return JSON.parse(jsonResult);
79
+ }
80
+
81
+ /**
82
+ * Analyze all .mbt files in a directory
83
+ */
84
+ export async function analyzeDirectory(
85
+ dir: string,
86
+ options: { recursive?: boolean } = {}
87
+ ): Promise<MoonBitAnalysisResult> {
88
+ const { recursive = true } = options;
89
+ const allCooccurrences: ClassCooccurrence[] = [];
90
+ const allWarnings: AnalyzerWarning[] = [];
91
+
92
+ const files = findMbtFiles(dir, recursive);
93
+
94
+ for (const file of files) {
95
+ const source = fs.readFileSync(file, "utf-8");
96
+ const result = await analyzeFile(source, file);
97
+ allCooccurrences.push(...result.cooccurrences);
98
+ allWarnings.push(...result.warnings);
99
+ }
100
+
101
+ return {
102
+ cooccurrences: allCooccurrences,
103
+ warnings: allWarnings,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Find all .mbt files in a directory
109
+ */
110
+ function findMbtFiles(dir: string, recursive: boolean): string[] {
111
+ const files: string[] = [];
112
+
113
+ function walk(currentDir: string) {
114
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
115
+
116
+ for (const entry of entries) {
117
+ const fullPath = path.join(currentDir, entry.name);
118
+
119
+ if (entry.isDirectory()) {
120
+ // Skip common non-source directories
121
+ if (
122
+ entry.name === "node_modules" ||
123
+ entry.name === "target" ||
124
+ entry.name === ".git" ||
125
+ entry.name === ".mooncakes"
126
+ ) {
127
+ continue;
128
+ }
129
+
130
+ if (recursive) {
131
+ walk(fullPath);
132
+ }
133
+ } else if (entry.isFile() && entry.name.endsWith(".mbt")) {
134
+ // Skip test files
135
+ if (!entry.name.endsWith("_test.mbt")) {
136
+ files.push(fullPath);
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ walk(dir);
143
+ return files;
144
+ }
145
+
146
+ /**
147
+ * Convert MoonBit analysis result to css-optimizer input format
148
+ */
149
+ export interface ConvertedResult {
150
+ /** Class usages in css-optimizer format */
151
+ usages: ClassUsage[];
152
+ /** Map from class name to CSS declaration */
153
+ classToDeclaration: Map<string, string>;
154
+ }
155
+
156
+ /**
157
+ * Convert MoonBit analyzer output to css-optimizer format
158
+ *
159
+ * @param result - MoonBit analysis result
160
+ * @param hashFn - Function to generate class name from declaration
161
+ */
162
+ export function convertToOptimizerInput(
163
+ result: MoonBitAnalysisResult,
164
+ hashFn: (decl: string) => string
165
+ ): ConvertedResult {
166
+ const classToDeclaration = new Map<string, string>();
167
+ const usages: ClassUsage[] = [];
168
+
169
+ for (const co of result.cooccurrences) {
170
+ // Only process static patterns (can be safely optimized)
171
+ if (!co.isStatic) continue;
172
+
173
+ const classes: string[] = [];
174
+
175
+ for (const decl of co.classes) {
176
+ const className = hashFn(decl);
177
+ classToDeclaration.set(className, decl);
178
+ classes.push(className);
179
+ }
180
+
181
+ usages.push({
182
+ classes,
183
+ source: `${co.file}:${co.line}`,
184
+ });
185
+ }
186
+
187
+ return { usages, classToDeclaration };
188
+ }
189
+
190
+ /**
191
+ * CLI entry point for testing
192
+ */
193
+ export async function main(args: string[]): Promise<void> {
194
+ const dir = args[0] || ".";
195
+
196
+ console.error(`Analyzing MoonBit files in: ${dir}`);
197
+
198
+ const result = await analyzeDirectory(dir);
199
+
200
+ console.log(JSON.stringify(result, null, 2));
201
+
202
+ if (result.warnings.length > 0) {
203
+ console.error(`\nWarnings: ${result.warnings.length}`);
204
+ for (const w of result.warnings) {
205
+ console.error(` ${w.file}:${w.line} - ${w.kind}: ${w.message}`);
206
+ }
207
+ }
208
+
209
+ console.error(`\nFound ${result.cooccurrences.length} class co-occurrences`);
210
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * HTML and CSS parsing utilities
3
+ */
4
+
5
+ import type { ClassUsage, CssRule } from "./types.js";
6
+
7
+ /**
8
+ * Pattern to match class attributes in HTML
9
+ */
10
+ const CLASS_ATTR_PATTERN = /class\s*=\s*"([^"]+)"/g;
11
+
12
+ /**
13
+ * Pattern to match CSS rules
14
+ */
15
+ const CSS_RULE_PATTERN = /\.([a-zA-Z_][a-zA-Z0-9_-]*)\s*\{([^}]+)\}/g;
16
+
17
+ /**
18
+ * Extract class usages from HTML content
19
+ * @param html - HTML content
20
+ * @param source - Source identifier for debugging
21
+ * @param classPrefix - Only extract classes starting with this prefix
22
+ */
23
+ export function extractClassUsages(
24
+ html: string,
25
+ source = "html",
26
+ classPrefix = "_"
27
+ ): ClassUsage[] {
28
+ const usages: ClassUsage[] = [];
29
+ let match: RegExpExecArray | null;
30
+
31
+ CLASS_ATTR_PATTERN.lastIndex = 0;
32
+ while ((match = CLASS_ATTR_PATTERN.exec(html)) !== null) {
33
+ const classValue = match[1].trim();
34
+ if (!classValue) continue;
35
+
36
+ // Split by whitespace and filter by prefix
37
+ const classes = classValue
38
+ .split(/\s+/)
39
+ .filter((c) => c.startsWith(classPrefix) && c.length > classPrefix.length);
40
+
41
+ // Only consider elements with 2+ matching classes
42
+ if (classes.length >= 2) {
43
+ // Sort for consistent ordering
44
+ classes.sort();
45
+ usages.push({ classes, source: `${source}:${match.index}` });
46
+ }
47
+ }
48
+
49
+ return usages;
50
+ }
51
+
52
+ /**
53
+ * Parse CSS rules from CSS content
54
+ * @param css - CSS content
55
+ */
56
+ export function parseCssRules(css: string): CssRule[] {
57
+ const rules: CssRule[] = [];
58
+ let match: RegExpExecArray | null;
59
+
60
+ CSS_RULE_PATTERN.lastIndex = 0;
61
+ while ((match = CSS_RULE_PATTERN.exec(css)) !== null) {
62
+ rules.push({
63
+ selector: match[1],
64
+ declarations: match[2].trim(),
65
+ });
66
+ }
67
+
68
+ return rules;
69
+ }
70
+
71
+ /**
72
+ * Build a mapping from class name to CSS declaration
73
+ * @param css - CSS content
74
+ * @param classPrefix - Class prefix filter
75
+ */
76
+ export function buildClassToDeclarationMap(
77
+ css: string,
78
+ classPrefix = "_"
79
+ ): Map<string, string> {
80
+ const map = new Map<string, string>();
81
+ const rules = parseCssRules(css);
82
+
83
+ for (const rule of rules) {
84
+ if (rule.selector.startsWith(classPrefix)) {
85
+ // Normalize declarations (remove extra whitespace)
86
+ const normalized = rule.declarations
87
+ .split(";")
88
+ .map((d) => d.trim())
89
+ .filter((d) => d)
90
+ .join(";");
91
+ map.set(rule.selector, normalized);
92
+ }
93
+ }
94
+
95
+ return map;
96
+ }
97
+
98
+ /**
99
+ * Extract all unique classes from HTML
100
+ * @param html - HTML content
101
+ * @param classPrefix - Class prefix filter
102
+ */
103
+ export function extractUniqueClasses(html: string, classPrefix = "_"): Set<string> {
104
+ const classes = new Set<string>();
105
+ let match: RegExpExecArray | null;
106
+
107
+ CLASS_ATTR_PATTERN.lastIndex = 0;
108
+ while ((match = CLASS_ATTR_PATTERN.exec(html)) !== null) {
109
+ const classValue = match[1].trim();
110
+ if (!classValue) continue;
111
+
112
+ for (const cls of classValue.split(/\s+/)) {
113
+ if (cls.startsWith(classPrefix) && cls.length > classPrefix.length) {
114
+ classes.add(cls);
115
+ }
116
+ }
117
+ }
118
+
119
+ return classes;
120
+ }