@luna_ui/luna 0.3.4 → 0.4.0

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 (182) 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-CyEkcO3_.d.ts → index-CDWzWF-h.d.ts} +2 -2
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/jsx-dev-runtime.js +1 -1
  10. package/dist/jsx-runtime.d.ts +1 -1
  11. package/dist/jsx-runtime.js +1 -1
  12. package/dist/src-DEjrAhrg.js +1 -0
  13. package/dist/vite-plugin.d.ts +122 -0
  14. package/dist/vite-plugin.js +1518 -0
  15. package/package.json +16 -2
  16. package/src/css/extract.ts +798 -0
  17. package/src/css/index.ts +10 -0
  18. package/src/css/inject.ts +205 -0
  19. package/src/css/inline.ts +182 -0
  20. package/src/css/minify.ts +70 -0
  21. package/src/css/optimizer.ts +6 -0
  22. package/src/css/runtime.ts +344 -0
  23. package/src/css-optimizer/README.md +353 -0
  24. package/src/css-optimizer/cooccurrence.ts +100 -0
  25. package/src/css-optimizer/core.ts +263 -0
  26. package/src/css-optimizer/extractors.ts +243 -0
  27. package/src/css-optimizer/hash.ts +54 -0
  28. package/src/css-optimizer/index.ts +129 -0
  29. package/src/css-optimizer/merge.ts +109 -0
  30. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  31. package/src/css-optimizer/parser.ts +120 -0
  32. package/src/css-optimizer/pattern.ts +171 -0
  33. package/src/css-optimizer/transformers.ts +301 -0
  34. package/src/css-optimizer/types.ts +128 -0
  35. package/src/event-utils.ts +227 -0
  36. package/src/hydration/createHydrator.ts +62 -0
  37. package/src/hydration/delegate.ts +62 -0
  38. package/src/hydration/drag.ts +214 -0
  39. package/src/hydration/index.ts +12 -0
  40. package/src/hydration/keyboard.ts +64 -0
  41. package/src/hydration/toggle.ts +101 -0
  42. package/src/index.ts +890 -0
  43. package/src/jsx-dev-runtime.ts +2 -0
  44. package/src/jsx-runtime.ts +398 -0
  45. package/src/vite-plugin.ts +718 -0
  46. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  50. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  51. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  58. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  59. 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
  60. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  61. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  62. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  63. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  64. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  65. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  66. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  73. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  74. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  75. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  76. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  77. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  78. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  79. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  80. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  81. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  82. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  83. 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
  84. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  85. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  117. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  118. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  119. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  120. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  121. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  122. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  123. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  124. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  125. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  137. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  138. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  139. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  140. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  141. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  142. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  143. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  144. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  145. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  147. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  148. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  149. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  150. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  151. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  152. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  153. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  154. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  155. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  156. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  157. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  158. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  159. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  160. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  161. package/tests/context.test.ts +118 -0
  162. package/tests/css-optimizer-extractors.test.ts +264 -0
  163. package/tests/css-optimizer-integration.test.ts +566 -0
  164. package/tests/css-optimizer-transformers.test.ts +301 -0
  165. package/tests/css-optimizer.test.ts +646 -0
  166. package/tests/css-runtime.bench.ts +442 -0
  167. package/tests/css-runtime.test.ts +342 -0
  168. package/tests/dom.test.ts +872 -0
  169. package/tests/integration.test.ts +405 -0
  170. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  171. package/tests/jsx-runtime.test.tsx +393 -0
  172. package/tests/lifecycle.test.ts +833 -0
  173. package/tests/move-before.bench.ts +304 -0
  174. package/tests/preact-signals-comparison.test.ts +1608 -0
  175. package/tests/resource.test.ts +160 -0
  176. package/tests/router.test.ts +117 -0
  177. package/tests/show-initial-mount-leak.test.tsx +182 -0
  178. package/tests/solidjs-api.test.ts +659 -0
  179. package/tests/static-perf.bench.ts +64 -0
  180. package/tests/store.test.ts +263 -0
  181. package/tests/tsx-syntax.test.tsx +404 -0
  182. package/dist/src-BDdxGwvq.js +0 -1
@@ -0,0 +1,1518 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ //#region src/css/extract.ts
5
+ /**
6
+ * Static CSS Extractor for Luna CSS Module
7
+ *
8
+ * Extracts all CSS declarations from .mbt files at build time.
9
+ * This ensures all styles are collected regardless of runtime branches.
10
+ */
11
+ const CSS_PATTERN = /\bcss\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
12
+ const STYLES_PATTERN = /\bstyles\s*\(\s*\[([\s\S]*?)\]\s*\)/g;
13
+ const STYLES_PAIR_PATTERN = /\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
14
+ const HOVER_PATTERN = /\bhover\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
15
+ const FOCUS_PATTERN = /\bfocus\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
16
+ const ACTIVE_PATTERN = /\bactive\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
17
+ const ON_PATTERN = /\bon\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
18
+ const MEDIA_PATTERN = /\bmedia\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
19
+ const AT_SM_PATTERN = /\bat_sm\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
20
+ const AT_MD_PATTERN = /\bat_md\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
21
+ const AT_LG_PATTERN = /\bat_lg\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
22
+ const AT_XL_PATTERN = /\bat_xl\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
23
+ const DARK_PATTERN = /\bdark\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
24
+ const UCSS_PATTERN = /\bucss\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
25
+ const USTYLES_PATTERN = /\bustyles\s*\(\s*\[([\s\S]*?)\]\s*\)/g;
26
+ const UHOVER_PATTERN = /\buhover\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
27
+ const UFOCUS_PATTERN = /\bufocus\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
28
+ const UACTIVE_PATTERN = /\buactive\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
29
+ const UON_PATTERN = /\buon\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
30
+ const UAT_MD_PATTERN = /\buat_md\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
31
+ const UAT_LG_PATTERN = /\buat_lg\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
32
+ const UDARK_PATTERN = /\budark\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
33
+ const ARG_PATTERN = `(?:"(?:[^"\\\\]|\\\\.)*"|[^,)]+)`;
34
+ const WARN_PATTERNS = [
35
+ {
36
+ name: "css",
37
+ pattern: new RegExp(`\\b(u?css)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
38
+ args: 2
39
+ },
40
+ {
41
+ name: "hover",
42
+ pattern: new RegExp(`\\b(u?hover)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
43
+ args: 2
44
+ },
45
+ {
46
+ name: "focus",
47
+ pattern: new RegExp(`\\b(u?focus)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
48
+ args: 2
49
+ },
50
+ {
51
+ name: "active",
52
+ pattern: new RegExp(`\\b(u?active)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
53
+ args: 2
54
+ },
55
+ {
56
+ name: "at_sm",
57
+ pattern: new RegExp(`\\bat_sm\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
58
+ args: 2,
59
+ noPrefix: true
60
+ },
61
+ {
62
+ name: "at_md",
63
+ pattern: new RegExp(`\\b(u?at_md)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
64
+ args: 2
65
+ },
66
+ {
67
+ name: "at_lg",
68
+ pattern: new RegExp(`\\b(u?at_lg)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
69
+ args: 2
70
+ },
71
+ {
72
+ name: "at_xl",
73
+ pattern: new RegExp(`\\bat_xl\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
74
+ args: 2,
75
+ noPrefix: true
76
+ },
77
+ {
78
+ name: "dark",
79
+ pattern: new RegExp(`\\b(u?dark)\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
80
+ args: 2
81
+ },
82
+ {
83
+ name: "on",
84
+ pattern: new RegExp(`\\b(u?on)\\s*\\(\\s*"(::?[^"]+)"\\s*,\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
85
+ args: 3,
86
+ pseudoInMatch: true
87
+ },
88
+ {
89
+ name: "media",
90
+ pattern: new RegExp(`\\bmedia\\s*\\(\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*,\\s*(${ARG_PATTERN})\\s*\\)`, "g"),
91
+ args: 3,
92
+ noPrefix: true
93
+ }
94
+ ];
95
+ function isStringLiteral(str) {
96
+ const trimmed = str.trim();
97
+ return trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'");
98
+ }
99
+ function isFunctionDefinition(code) {
100
+ return /:\s*String/.test(code);
101
+ }
102
+ function getLineNumber(content, position) {
103
+ return content.substring(0, position).split("\n").length;
104
+ }
105
+ function detectWarnings(content, filePath) {
106
+ const warnings = [];
107
+ for (const { name, pattern, args, noPrefix, pseudoInMatch } of WARN_PATTERNS) {
108
+ pattern.lastIndex = 0;
109
+ for (const match of content.matchAll(pattern)) {
110
+ const fullMatch = match[0];
111
+ const position = match.index;
112
+ const line = getLineNumber(content, position);
113
+ if (isFunctionDefinition(fullMatch)) continue;
114
+ const extractedArgs = [];
115
+ if (args === 2) {
116
+ const startIdx = noPrefix ? 1 : 2;
117
+ extractedArgs.push(match[startIdx], match[startIdx + 1]);
118
+ } else if (args === 3) if (pseudoInMatch) extractedArgs.push(match[3], match[4]);
119
+ else {
120
+ const startIdx = noPrefix ? 1 : 2;
121
+ extractedArgs.push(match[startIdx], match[startIdx + 1], match[startIdx + 2]);
122
+ }
123
+ for (let i = 0; i < extractedArgs.length; i++) {
124
+ const arg = extractedArgs[i];
125
+ if (arg && !isStringLiteral(arg)) {
126
+ const argNum = pseudoInMatch ? i + 2 : i + 1;
127
+ warnings.push({
128
+ file: filePath,
129
+ line,
130
+ func: name,
131
+ code: fullMatch.trim(),
132
+ reason: `Argument ${argNum} is not a string literal: ${arg.trim()}`
133
+ });
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return warnings;
140
+ }
141
+ function extractFromContent(content) {
142
+ const base = /* @__PURE__ */ new Set();
143
+ const pseudo = [];
144
+ const media = [];
145
+ function extractBase(pattern) {
146
+ for (const match of content.matchAll(pattern)) base.add(`${match[1]}:${match[2]}`);
147
+ }
148
+ function extractStyles(pattern) {
149
+ for (const match of content.matchAll(pattern)) {
150
+ const pairs = match[1];
151
+ for (const pair of pairs.matchAll(STYLES_PAIR_PATTERN)) base.add(`${pair[1]}:${pair[2]}`);
152
+ }
153
+ }
154
+ function extractPseudo(pattern, pseudoSelector) {
155
+ for (const match of content.matchAll(pattern)) pseudo.push({
156
+ pseudo: pseudoSelector,
157
+ property: match[1],
158
+ value: match[2]
159
+ });
160
+ }
161
+ function extractOn(pattern) {
162
+ for (const match of content.matchAll(pattern)) pseudo.push({
163
+ pseudo: match[1],
164
+ property: match[2],
165
+ value: match[3]
166
+ });
167
+ }
168
+ function extractMedia(pattern, condition = null) {
169
+ for (const match of content.matchAll(pattern)) if (condition) media.push({
170
+ condition,
171
+ property: match[1],
172
+ value: match[2]
173
+ });
174
+ else media.push({
175
+ condition: match[1],
176
+ property: match[2],
177
+ value: match[3]
178
+ });
179
+ }
180
+ extractBase(CSS_PATTERN);
181
+ extractBase(UCSS_PATTERN);
182
+ extractStyles(STYLES_PATTERN);
183
+ extractStyles(USTYLES_PATTERN);
184
+ extractPseudo(HOVER_PATTERN, ":hover");
185
+ extractPseudo(UHOVER_PATTERN, ":hover");
186
+ extractPseudo(FOCUS_PATTERN, ":focus");
187
+ extractPseudo(UFOCUS_PATTERN, ":focus");
188
+ extractPseudo(ACTIVE_PATTERN, ":active");
189
+ extractPseudo(UACTIVE_PATTERN, ":active");
190
+ extractOn(ON_PATTERN);
191
+ extractOn(UON_PATTERN);
192
+ extractMedia(MEDIA_PATTERN);
193
+ extractMedia(AT_SM_PATTERN, "min-width:640px");
194
+ extractMedia(AT_MD_PATTERN, "min-width:768px");
195
+ extractMedia(UAT_MD_PATTERN, "min-width:768px");
196
+ extractMedia(AT_LG_PATTERN, "min-width:1024px");
197
+ extractMedia(UAT_LG_PATTERN, "min-width:1024px");
198
+ extractMedia(AT_XL_PATTERN, "min-width:1280px");
199
+ extractMedia(DARK_PATTERN, "prefers-color-scheme:dark");
200
+ extractMedia(UDARK_PATTERN, "prefers-color-scheme:dark");
201
+ return {
202
+ base,
203
+ pseudo,
204
+ media
205
+ };
206
+ }
207
+ function findMbtFiles(dir) {
208
+ const files = [];
209
+ function walk(currentDir) {
210
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
211
+ for (const entry of entries) {
212
+ const fullPath = path.join(currentDir, entry.name);
213
+ if (entry.isDirectory()) {
214
+ if (![
215
+ "node_modules",
216
+ ".git",
217
+ "target",
218
+ ".mooncakes"
219
+ ].includes(entry.name)) walk(fullPath);
220
+ } else if (entry.isFile() && entry.name.endsWith(".mbt")) files.push(fullPath);
221
+ }
222
+ }
223
+ walk(dir);
224
+ return files;
225
+ }
226
+ /**
227
+ * DJB2 hash function - must match MoonBit registry.mbt implementation
228
+ */
229
+ function djb2Hash$1(s) {
230
+ let hash = 5381;
231
+ for (let i = 0; i < s.length; i++) {
232
+ const c = s.charCodeAt(i);
233
+ hash = (hash << 5) + hash + c >>> 0;
234
+ }
235
+ return hash;
236
+ }
237
+ /**
238
+ * Convert hash to base36 string (0-9a-z)
239
+ * Takes lower 24 bits to keep class names short (4-5 chars)
240
+ */
241
+ function toBase36$1(n) {
242
+ const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
243
+ n = n & 16777215;
244
+ if (n === 0) return "0";
245
+ let result = "";
246
+ while (n > 0) {
247
+ result = chars[n % 36] + result;
248
+ n = Math.floor(n / 36);
249
+ }
250
+ return result;
251
+ }
252
+ /**
253
+ * Generate class name from declaration hash
254
+ */
255
+ function hashClassName(decl) {
256
+ return "_" + toBase36$1(djb2Hash$1(decl));
257
+ }
258
+ function generateCSS(styles, options = {}) {
259
+ const { pretty = false } = options;
260
+ const mapping = {};
261
+ const parts = [];
262
+ for (const decl of styles.base) {
263
+ const cls = hashClassName(decl);
264
+ mapping[decl] = cls;
265
+ if (pretty) parts.push(`.${cls} { ${decl} }`);
266
+ else parts.push(`.${cls}{${decl}}`);
267
+ }
268
+ for (const { pseudo, property, value } of styles.pseudo) {
269
+ const hashKey = `${pseudo}:${property}:${value}`;
270
+ const cls = hashClassName(hashKey);
271
+ mapping[hashKey] = cls;
272
+ if (pretty) parts.push(`.${cls}${pseudo} { ${property}: ${value} }`);
273
+ else parts.push(`.${cls}${pseudo}{${property}:${value}}`);
274
+ }
275
+ const mediaGroups = /* @__PURE__ */ new Map();
276
+ for (const { condition, property, value } of styles.media) {
277
+ if (!mediaGroups.has(condition)) mediaGroups.set(condition, []);
278
+ mediaGroups.get(condition).push({
279
+ property,
280
+ value
281
+ });
282
+ }
283
+ for (const [condition, declarations] of mediaGroups) {
284
+ const rules = [];
285
+ for (const { property, value } of declarations) {
286
+ const hashKey = `@media(${condition}):${property}:${value}`;
287
+ const cls = hashClassName(hashKey);
288
+ mapping[hashKey] = cls;
289
+ if (pretty) rules.push(` .${cls} { ${property}: ${value} }`);
290
+ else rules.push(`.${cls}{${property}:${value}}`);
291
+ }
292
+ if (pretty) parts.push(`@media (${condition}) {\n${rules.join("\n")}\n}`);
293
+ else parts.push(`@media(${condition}){${rules.join("")}}`);
294
+ }
295
+ const separator = pretty ? "\n" : "";
296
+ return {
297
+ css: parts.join(separator),
298
+ mapping
299
+ };
300
+ }
301
+ function extract(dir, options = {}) {
302
+ const { pretty = false, warn = true, strict = false, verbose = false } = options;
303
+ const files = findMbtFiles(dir);
304
+ if (verbose) console.error(`Found ${files.length} .mbt files`);
305
+ const combined = {
306
+ base: /* @__PURE__ */ new Set(),
307
+ pseudo: [],
308
+ media: []
309
+ };
310
+ const allWarnings = [];
311
+ for (const file of files) {
312
+ const content = fs.readFileSync(file, "utf-8");
313
+ const extracted = extractFromContent(content);
314
+ for (const decl of extracted.base) combined.base.add(decl);
315
+ combined.pseudo.push(...extracted.pseudo);
316
+ combined.media.push(...extracted.media);
317
+ if (warn) {
318
+ const warnings = detectWarnings(content, file);
319
+ allWarnings.push(...warnings);
320
+ }
321
+ }
322
+ if (warn && allWarnings.length > 0 && verbose) {
323
+ console.error(`\n⚠️ ${allWarnings.length} warning(s): non-literal CSS arguments detected\n`);
324
+ for (const w of allWarnings) {
325
+ console.error(` ${w.file}:${w.line}`);
326
+ console.error(` ${w.func}(...) - ${w.reason}`);
327
+ console.error(` Code: ${w.code}`);
328
+ console.error("");
329
+ }
330
+ if (strict) throw new Error("Strict mode: non-literal CSS arguments detected");
331
+ }
332
+ if (verbose) {
333
+ console.error(`Extracted:`);
334
+ console.error(` - ${combined.base.size} base declarations`);
335
+ console.error(` - ${combined.pseudo.length} pseudo-class declarations`);
336
+ console.error(` - ${combined.media.length} media query declarations`);
337
+ }
338
+ const { css, mapping } = generateCSS(combined, { pretty });
339
+ return {
340
+ css,
341
+ mapping,
342
+ stats: {
343
+ base: combined.base.size,
344
+ pseudo: combined.pseudo.length,
345
+ media: combined.media.length
346
+ },
347
+ warnings: warn ? allWarnings : void 0
348
+ };
349
+ }
350
+ /**
351
+ * Extract CSS with split output by file or directory.
352
+ * Shared CSS (used in 3+ files/dirs) is separated into a shared chunk.
353
+ */
354
+ function extractSplit(dir, mode, options = {}) {
355
+ const { pretty = false, warn = true, strict = false, verbose = false, sharedThreshold = 3 } = options;
356
+ const files = findMbtFiles(dir);
357
+ if (verbose) console.error(`Found ${files.length} .mbt files`);
358
+ const declUsage = /* @__PURE__ */ new Map();
359
+ const pseudoUsage = /* @__PURE__ */ new Map();
360
+ const mediaUsage = /* @__PURE__ */ new Map();
361
+ const perChunk = /* @__PURE__ */ new Map();
362
+ const allWarnings = [];
363
+ for (const file of files) {
364
+ const content = fs.readFileSync(file, "utf-8");
365
+ const extracted = extractFromContent(content);
366
+ const relPath = path.relative(dir, file);
367
+ const chunkKey = mode === "file" ? relPath : path.dirname(relPath) || ".";
368
+ if (!perChunk.has(chunkKey)) perChunk.set(chunkKey, {
369
+ base: /* @__PURE__ */ new Set(),
370
+ pseudo: [],
371
+ media: []
372
+ });
373
+ const chunk = perChunk.get(chunkKey);
374
+ for (const decl of extracted.base) {
375
+ chunk.base.add(decl);
376
+ if (!declUsage.has(decl)) declUsage.set(decl, /* @__PURE__ */ new Set());
377
+ declUsage.get(decl).add(chunkKey);
378
+ }
379
+ for (const p of extracted.pseudo) {
380
+ const key = `${p.pseudo}:${p.property}:${p.value}`;
381
+ chunk.pseudo.push(p);
382
+ if (!pseudoUsage.has(key)) pseudoUsage.set(key, /* @__PURE__ */ new Set());
383
+ pseudoUsage.get(key).add(chunkKey);
384
+ }
385
+ for (const m of extracted.media) {
386
+ const key = `@media(${m.condition}):${m.property}:${m.value}`;
387
+ chunk.media.push(m);
388
+ if (!mediaUsage.has(key)) mediaUsage.set(key, /* @__PURE__ */ new Set());
389
+ mediaUsage.get(key).add(chunkKey);
390
+ }
391
+ if (warn) {
392
+ const warnings = detectWarnings(content, file);
393
+ allWarnings.push(...warnings);
394
+ }
395
+ }
396
+ const sharedBase = /* @__PURE__ */ new Set();
397
+ const sharedPseudo = [];
398
+ const sharedMedia = [];
399
+ for (const [decl, chunks] of declUsage) if (chunks.size >= sharedThreshold) sharedBase.add(decl);
400
+ const seenPseudo = /* @__PURE__ */ new Set();
401
+ for (const [key, chunks] of pseudoUsage) if (chunks.size >= sharedThreshold && !seenPseudo.has(key)) {
402
+ seenPseudo.add(key);
403
+ const [pseudo, property, value] = key.split(":");
404
+ sharedPseudo.push({
405
+ pseudo,
406
+ property,
407
+ value
408
+ });
409
+ }
410
+ const seenMedia = /* @__PURE__ */ new Set();
411
+ for (const [key, chunks] of mediaUsage) if (chunks.size >= sharedThreshold && !seenMedia.has(key)) {
412
+ seenMedia.add(key);
413
+ const match = key.match(/^@media\(([^)]+)\):([^:]+):(.+)$/);
414
+ if (match) sharedMedia.push({
415
+ condition: match[1],
416
+ property: match[2],
417
+ value: match[3]
418
+ });
419
+ }
420
+ const finalChunks = /* @__PURE__ */ new Map();
421
+ const chunkStats = /* @__PURE__ */ new Map();
422
+ for (const [chunkKey, chunk] of perChunk) {
423
+ const chunkOnlyBase = /* @__PURE__ */ new Set();
424
+ for (const decl of chunk.base) if (!sharedBase.has(decl)) chunkOnlyBase.add(decl);
425
+ const chunkOnlyPseudo = chunk.pseudo.filter((p) => {
426
+ const key = `${p.pseudo}:${p.property}:${p.value}`;
427
+ return !seenPseudo.has(key);
428
+ });
429
+ const chunkOnlyMedia = chunk.media.filter((m) => {
430
+ const key = `@media(${m.condition}):${m.property}:${m.value}`;
431
+ return !seenMedia.has(key);
432
+ });
433
+ const chunkStyles = {
434
+ base: chunkOnlyBase,
435
+ pseudo: chunkOnlyPseudo,
436
+ media: chunkOnlyMedia
437
+ };
438
+ const { css } = generateCSS(chunkStyles, { pretty });
439
+ finalChunks.set(chunkKey, {
440
+ css,
441
+ styles: chunkStyles
442
+ });
443
+ chunkStats.set(chunkKey, {
444
+ base: chunkOnlyBase.size,
445
+ pseudo: chunkOnlyPseudo.length,
446
+ media: chunkOnlyMedia.length
447
+ });
448
+ }
449
+ const sharedStyles = {
450
+ base: sharedBase,
451
+ pseudo: sharedPseudo,
452
+ media: sharedMedia
453
+ };
454
+ const { css: sharedCss, mapping } = generateCSS(sharedStyles, { pretty });
455
+ const combinedStyles = {
456
+ base: /* @__PURE__ */ new Set(),
457
+ pseudo: [],
458
+ media: []
459
+ };
460
+ for (const decl of sharedBase) combinedStyles.base.add(decl);
461
+ for (const [, chunk] of finalChunks) {
462
+ for (const decl of chunk.styles.base) combinedStyles.base.add(decl);
463
+ combinedStyles.pseudo.push(...chunk.styles.pseudo);
464
+ combinedStyles.media.push(...chunk.styles.media);
465
+ }
466
+ combinedStyles.pseudo.push(...sharedPseudo);
467
+ combinedStyles.media.push(...sharedMedia);
468
+ const { css: combinedCss, mapping: fullMapping } = generateCSS(combinedStyles, { pretty });
469
+ if (verbose) {
470
+ console.error(`Split mode: ${mode}`);
471
+ console.error(`Chunks: ${finalChunks.size}`);
472
+ console.error(`Shared declarations (≥${sharedThreshold} usages):`);
473
+ console.error(` - ${sharedBase.size} base`);
474
+ console.error(` - ${sharedPseudo.length} pseudo`);
475
+ console.error(` - ${sharedMedia.length} media`);
476
+ }
477
+ if (warn && allWarnings.length > 0 && verbose) console.error(`\n⚠️ ${allWarnings.length} warning(s)\n`);
478
+ if (strict && allWarnings.length > 0) throw new Error("Strict mode: non-literal CSS arguments detected");
479
+ return {
480
+ chunks: finalChunks,
481
+ shared: {
482
+ css: sharedCss,
483
+ styles: sharedStyles
484
+ },
485
+ combined: combinedCss,
486
+ mapping: fullMapping,
487
+ stats: chunkStats,
488
+ warnings: warn ? allWarnings : void 0
489
+ };
490
+ }
491
+
492
+ //#endregion
493
+ //#region src/css-optimizer/hash.ts
494
+ /**
495
+ * Hash functions for CSS class name generation
496
+ */
497
+ /**
498
+ * DJB2 hash function
499
+ * A simple but effective string hashing algorithm
500
+ */
501
+ function djb2Hash(s) {
502
+ let hash = 5381;
503
+ for (let i = 0; i < s.length; i++) {
504
+ const c = s.charCodeAt(i);
505
+ hash = (hash << 5) + hash + c >>> 0;
506
+ }
507
+ return hash;
508
+ }
509
+ /**
510
+ * Convert a number to base36 string (0-9a-z)
511
+ * Takes lower 24 bits to keep class names short (4-5 chars)
512
+ */
513
+ function toBase36(n) {
514
+ const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
515
+ n = n & 16777215;
516
+ if (n === 0) return "0";
517
+ let result = "";
518
+ while (n > 0) {
519
+ result = chars[n % 36] + result;
520
+ n = Math.floor(n / 36);
521
+ }
522
+ return result;
523
+ }
524
+ /**
525
+ * Generate a merged class name from combined declarations
526
+ * @param declarations - Array of CSS declarations
527
+ * @param prefix - Class name prefix (default: "_m" for merged)
528
+ */
529
+ function hashMergedClassName(declarations, prefix = "_m") {
530
+ return prefix + toBase36(djb2Hash(declarations.sort().join(";")));
531
+ }
532
+
533
+ //#endregion
534
+ //#region src/css-optimizer/pattern.ts
535
+ /**
536
+ * Find frequent patterns (sets of classes that often appear together)
537
+ * Uses a greedy enumeration approach for small pattern sizes
538
+ */
539
+ function findFrequentPatterns(usages, minFrequency, maxSize) {
540
+ const patternCounts = /* @__PURE__ */ new Map();
541
+ for (const usage of usages) {
542
+ const classes = [...usage.classes].sort();
543
+ const n = classes.length;
544
+ for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) {
545
+ const key = `${classes[i]}|${classes[j]}`;
546
+ patternCounts.set(key, (patternCounts.get(key) || 0) + 1);
547
+ }
548
+ if (maxSize >= 3) for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) for (let k = j + 1; k < n; k++) {
549
+ const key = `${classes[i]}|${classes[j]}|${classes[k]}`;
550
+ patternCounts.set(key, (patternCounts.get(key) || 0) + 1);
551
+ }
552
+ if (maxSize >= 4) for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) for (let k = j + 1; k < n; k++) for (let l = k + 1; l < n; l++) {
553
+ const key = `${classes[i]}|${classes[j]}|${classes[k]}|${classes[l]}`;
554
+ patternCounts.set(key, (patternCounts.get(key) || 0) + 1);
555
+ }
556
+ if (maxSize >= 5) for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) for (let k = j + 1; k < n; k++) for (let l = k + 1; l < n; l++) for (let m = l + 1; m < n; m++) {
557
+ const key = `${classes[i]}|${classes[j]}|${classes[k]}|${classes[l]}|${classes[m]}`;
558
+ patternCounts.set(key, (patternCounts.get(key) || 0) + 1);
559
+ }
560
+ }
561
+ const patterns = [];
562
+ for (const [key, frequency] of patternCounts) if (frequency >= minFrequency) {
563
+ const originalClasses = key.split("|");
564
+ const classCount = originalClasses.length;
565
+ const bytesSaved = (classCount - 1) * 7 * frequency + (classCount - 1) * 25;
566
+ patterns.push({
567
+ originalClasses,
568
+ mergedClass: "",
569
+ declarations: [],
570
+ frequency,
571
+ bytesSaved
572
+ });
573
+ }
574
+ patterns.sort((a, b) => b.bytesSaved - a.bytesSaved);
575
+ return removeSubsumedPatterns(patterns);
576
+ }
577
+ /**
578
+ * Remove patterns that are subsets of larger patterns with similar frequency
579
+ * This prevents double-counting savings
580
+ */
581
+ function removeSubsumedPatterns(patterns) {
582
+ const result = [];
583
+ for (let i = 0; i < patterns.length; i++) {
584
+ const pattern = patterns[i];
585
+ let isDominated = false;
586
+ for (let j = 0; j < patterns.length; j++) {
587
+ if (i === j) continue;
588
+ const other = patterns[j];
589
+ if (other.originalClasses.length > pattern.originalClasses.length && other.frequency >= pattern.frequency * .8) {
590
+ const patternSet = new Set(pattern.originalClasses);
591
+ const otherSet = new Set(other.originalClasses);
592
+ let isSubset = true;
593
+ for (const cls of patternSet) if (!otherSet.has(cls)) {
594
+ isSubset = false;
595
+ break;
596
+ }
597
+ if (isSubset) {
598
+ isDominated = true;
599
+ break;
600
+ }
601
+ }
602
+ }
603
+ if (!isDominated) result.push(pattern);
604
+ }
605
+ return result;
606
+ }
607
+
608
+ //#endregion
609
+ //#region src/css-optimizer/core.ts
610
+ /**
611
+ * Core optimizer - performs pattern analysis and CSS generation
612
+ *
613
+ * This is the framework-agnostic core that:
614
+ * 1. Analyzes class usages to find frequent patterns
615
+ * 2. Generates merged CSS rules
616
+ * 3. Returns a merge map for HTML transformation
617
+ *
618
+ * @param usages - Array of class usages extracted from source
619
+ * @param css - Original CSS content
620
+ * @param classToDeclaration - Map from class name to CSS declaration
621
+ * @param options - Optimization options
622
+ */
623
+ function optimizeCore(usages, css, classToDeclaration, options = {}) {
624
+ const { minFrequency = 2, maxPatternSize = 5, pretty = false, verbose = false } = options;
625
+ const log = (msg) => {
626
+ if (verbose) console.log(`[css-optimizer] ${msg}`);
627
+ };
628
+ if (usages.length === 0) return {
629
+ css,
630
+ mergeMap: /* @__PURE__ */ new Map(),
631
+ patterns: [],
632
+ stats: {
633
+ originalClasses: classToDeclaration.size,
634
+ mergedPatterns: 0,
635
+ estimatedBytesSaved: 0
636
+ }
637
+ };
638
+ log(`Analyzing ${usages.length} class usage sites`);
639
+ const patterns = findFrequentPatterns(usages, minFrequency, maxPatternSize);
640
+ log(`Found ${patterns.length} frequent patterns`);
641
+ const mergeMap = /* @__PURE__ */ new Map();
642
+ const cssRules = [];
643
+ const usedClasses = /* @__PURE__ */ new Set();
644
+ let totalBytesSaved = 0;
645
+ for (const pattern of patterns) {
646
+ const declarations = [];
647
+ let allFound = true;
648
+ for (const cls of pattern.originalClasses) {
649
+ const decl = classToDeclaration.get(cls);
650
+ if (decl) declarations.push(decl);
651
+ else {
652
+ allFound = false;
653
+ break;
654
+ }
655
+ }
656
+ if (!allFound || declarations.length === 0) continue;
657
+ let hasConflict = false;
658
+ for (const cls of pattern.originalClasses) if (usedClasses.has(cls)) {
659
+ hasConflict = true;
660
+ break;
661
+ }
662
+ if (hasConflict) continue;
663
+ const mergedClass = hashMergedClassName(declarations);
664
+ pattern.mergedClass = mergedClass;
665
+ pattern.declarations = declarations;
666
+ const mergeKey = pattern.originalClasses.join(" ");
667
+ mergeMap.set(mergeKey, mergedClass);
668
+ for (const cls of pattern.originalClasses) usedClasses.add(cls);
669
+ const declStr = declarations.join(";");
670
+ if (pretty) cssRules.push(`.${mergedClass} { ${declStr.replace(/;/g, "; ")} }`);
671
+ else cssRules.push(`.${mergedClass}{${declStr}}`);
672
+ totalBytesSaved += pattern.bytesSaved;
673
+ }
674
+ const originalRules = [];
675
+ const rulePattern = /\.([a-zA-Z_][a-zA-Z0-9_-]*)\{([^}]+)\}/g;
676
+ let match;
677
+ rulePattern.lastIndex = 0;
678
+ while ((match = rulePattern.exec(css)) !== null) {
679
+ const cls = match[1];
680
+ if (!usedClasses.has(cls)) originalRules.push(match[0]);
681
+ }
682
+ const mediaPattern = /@media[^{]+\{[^}]+\}/g;
683
+ mediaPattern.lastIndex = 0;
684
+ while ((match = mediaPattern.exec(css)) !== null) originalRules.push(match[0]);
685
+ const pseudoPattern = /\.([a-zA-Z_][a-zA-Z0-9_-]*)(:[a-zA-Z-]+)\{([^}]+)\}/g;
686
+ pseudoPattern.lastIndex = 0;
687
+ while ((match = pseudoPattern.exec(css)) !== null) originalRules.push(match[0]);
688
+ const separator = pretty ? "\n" : "";
689
+ const optimizedCss = [...cssRules, ...originalRules].join(separator);
690
+ const actualPatterns = patterns.filter((p) => p.mergedClass !== "");
691
+ log(`Merged ${actualPatterns.length} patterns`);
692
+ log(`Estimated savings: ${totalBytesSaved} bytes`);
693
+ return {
694
+ css: optimizedCss,
695
+ mergeMap,
696
+ patterns: actualPatterns,
697
+ stats: {
698
+ originalClasses: classToDeclaration.size,
699
+ mergedPatterns: actualPatterns.length,
700
+ estimatedBytesSaved: totalBytesSaved
701
+ }
702
+ };
703
+ }
704
+
705
+ //#endregion
706
+ //#region src/css-optimizer/extractors.ts
707
+ /**
708
+ * HTML Class Extractor
709
+ *
710
+ * Extracts class usages from HTML content by parsing class="" attributes.
711
+ * Works with static HTML, SSR output, template strings, etc.
712
+ */
713
+ var HtmlExtractor = class {
714
+ name = "html";
715
+ pattern = /class\s*=\s*"([^"]+)"/g;
716
+ extract(content, options = {}) {
717
+ const { classPrefix = "_", minClasses = 2, source = "html" } = options;
718
+ const usages = [];
719
+ let match;
720
+ this.pattern.lastIndex = 0;
721
+ while ((match = this.pattern.exec(content)) !== null) {
722
+ const classValue = match[1].trim();
723
+ if (!classValue) continue;
724
+ const classes = classValue.split(/\s+/).filter((c) => c.startsWith(classPrefix) && c.length > classPrefix.length);
725
+ if (classes.length >= minClasses) {
726
+ classes.sort();
727
+ usages.push({
728
+ classes,
729
+ source: `${source}:${match.index}`
730
+ });
731
+ }
732
+ }
733
+ return usages;
734
+ }
735
+ };
736
+ /**
737
+ * JSX Class Extractor (simplified)
738
+ *
739
+ * Extracts class usages from JSX/TSX by finding className attributes.
740
+ * Note: This is a simplified regex-based extractor. For full AST support,
741
+ * consider using @babel/parser or TypeScript compiler.
742
+ */
743
+ var JsxExtractor = class {
744
+ name = "jsx";
745
+ stringPattern = /className\s*=\s*"([^"]+)"/g;
746
+ templatePattern = /className\s*=\s*\{['"`]([^'"`]+)['"`]\}/g;
747
+ extract(content, options = {}) {
748
+ const { classPrefix = "_", minClasses = 2, source = "jsx" } = options;
749
+ const usages = [];
750
+ this.extractFromPattern(content, this.stringPattern, classPrefix, minClasses, source, usages);
751
+ this.extractFromPattern(content, this.templatePattern, classPrefix, minClasses, source, usages);
752
+ return usages;
753
+ }
754
+ extractFromPattern(content, pattern, classPrefix, minClasses, source, usages) {
755
+ let match;
756
+ pattern.lastIndex = 0;
757
+ while ((match = pattern.exec(content)) !== null) {
758
+ const classValue = match[1].trim();
759
+ if (!classValue) continue;
760
+ const classes = classValue.split(/\s+/).filter((c) => c.startsWith(classPrefix) && c.length > classPrefix.length);
761
+ if (classes.length >= minClasses) {
762
+ classes.sort();
763
+ usages.push({
764
+ classes,
765
+ source: `${source}:${match.index}`
766
+ });
767
+ }
768
+ }
769
+ }
770
+ };
771
+ /**
772
+ * Svelte Class Extractor (simplified)
773
+ *
774
+ * Extracts class usages from Svelte templates.
775
+ * Handles both class="..." and class:directive syntax.
776
+ */
777
+ var SvelteExtractor = class {
778
+ name = "svelte";
779
+ classPattern = /class\s*=\s*"([^"]+)"/g;
780
+ extract(content, options = {}) {
781
+ const { classPrefix = "_", minClasses = 2, source = "svelte" } = options;
782
+ const usages = [];
783
+ let match;
784
+ this.classPattern.lastIndex = 0;
785
+ while ((match = this.classPattern.exec(content)) !== null) {
786
+ const classValue = match[1].trim();
787
+ if (!classValue) continue;
788
+ const classes = classValue.replace(/\{[^}]+\}/g, " ").split(/\s+/).filter((c) => c.startsWith(classPrefix) && c.length > classPrefix.length);
789
+ if (classes.length >= minClasses) {
790
+ classes.sort();
791
+ usages.push({
792
+ classes,
793
+ source: `${source}:${match.index}`
794
+ });
795
+ }
796
+ }
797
+ return usages;
798
+ }
799
+ };
800
+ /**
801
+ * Multi-source extractor
802
+ *
803
+ * Combines usages from multiple sources/extractors.
804
+ * Useful for analyzing entire projects with mixed file types.
805
+ */
806
+ var MultiExtractor = class {
807
+ name = "multi";
808
+ extractors;
809
+ constructor() {
810
+ this.extractors = new Map([
811
+ ["html", new HtmlExtractor()],
812
+ ["jsx", new JsxExtractor()],
813
+ ["tsx", new JsxExtractor()],
814
+ ["svelte", new SvelteExtractor()]
815
+ ]);
816
+ }
817
+ /**
818
+ * Register a custom extractor for a file extension
819
+ */
820
+ register(extension, extractor) {
821
+ this.extractors.set(extension, extractor);
822
+ }
823
+ /**
824
+ * Extract from content using the appropriate extractor
825
+ */
826
+ extract(content, options = {}) {
827
+ return this.extractors.get("html").extract(content, options);
828
+ }
829
+ /**
830
+ * Extract from content with explicit file type
831
+ */
832
+ extractWithType(content, fileType, options = {}) {
833
+ return (this.extractors.get(fileType) || this.extractors.get("html")).extract(content, options);
834
+ }
835
+ /**
836
+ * Extract from multiple files
837
+ */
838
+ extractFromFiles(files, options = {}) {
839
+ const allUsages = [];
840
+ for (const file of files) {
841
+ const ext = file.path.split(".").pop() || "html";
842
+ const usages = (this.extractors.get(ext) || this.extractors.get("html")).extract(file.content, {
843
+ ...options,
844
+ source: file.path
845
+ });
846
+ allUsages.push(...usages);
847
+ }
848
+ return allUsages;
849
+ }
850
+ };
851
+ const htmlExtractor = new HtmlExtractor();
852
+ const jsxExtractor = new JsxExtractor();
853
+ const svelteExtractor = new SvelteExtractor();
854
+ const multiExtractor = new MultiExtractor();
855
+
856
+ //#endregion
857
+ //#region src/css-optimizer/transformers.ts
858
+ /**
859
+ * HTML Transformer
860
+ *
861
+ * Transforms HTML by replacing class="" attribute values with merged classes.
862
+ */
863
+ var HtmlTransformer = class {
864
+ name = "html";
865
+ pattern = /class\s*=\s*"([^"]+)"/g;
866
+ transform(content, mergeMap, options = {}) {
867
+ if (mergeMap.size === 0) return content;
868
+ const { classPrefix = "_" } = options;
869
+ const sortedKeys = [...mergeMap.keys()].sort((a, b) => b.length - a.length);
870
+ return content.replace(this.pattern, (match, classValue) => {
871
+ const classes = classValue.trim().split(/\s+/);
872
+ const prefixedClasses = classes.filter((c) => c.startsWith(classPrefix));
873
+ const otherClasses = classes.filter((c) => !c.startsWith(classPrefix));
874
+ prefixedClasses.sort();
875
+ let result = [...prefixedClasses];
876
+ const merged = [];
877
+ for (const key of sortedKeys) {
878
+ const patternClasses = key.split(" ");
879
+ const mergedClass = mergeMap.get(key);
880
+ let allPresent = true;
881
+ for (const cls of patternClasses) if (!result.includes(cls)) {
882
+ allPresent = false;
883
+ break;
884
+ }
885
+ if (allPresent) {
886
+ result = result.filter((c) => !patternClasses.includes(c));
887
+ merged.push(mergedClass);
888
+ }
889
+ }
890
+ return `class="${[
891
+ ...merged,
892
+ ...result,
893
+ ...otherClasses
894
+ ].join(" ")}"`;
895
+ });
896
+ }
897
+ };
898
+ /**
899
+ * JSX Transformer
900
+ *
901
+ * Transforms JSX/TSX by replacing className attribute values.
902
+ */
903
+ var JsxTransformer = class {
904
+ name = "jsx";
905
+ stringPattern = /className\s*=\s*"([^"]+)"/g;
906
+ transform(content, mergeMap, options = {}) {
907
+ if (mergeMap.size === 0) return content;
908
+ const { classPrefix = "_" } = options;
909
+ const sortedKeys = [...mergeMap.keys()].sort((a, b) => b.length - a.length);
910
+ return content.replace(this.stringPattern, (match, classValue) => {
911
+ const classes = classValue.trim().split(/\s+/);
912
+ const prefixedClasses = classes.filter((c) => c.startsWith(classPrefix));
913
+ const otherClasses = classes.filter((c) => !c.startsWith(classPrefix));
914
+ prefixedClasses.sort();
915
+ let result = [...prefixedClasses];
916
+ const merged = [];
917
+ for (const key of sortedKeys) {
918
+ const patternClasses = key.split(" ");
919
+ const mergedClass = mergeMap.get(key);
920
+ let allPresent = true;
921
+ for (const cls of patternClasses) if (!result.includes(cls)) {
922
+ allPresent = false;
923
+ break;
924
+ }
925
+ if (allPresent) {
926
+ result = result.filter((c) => !patternClasses.includes(c));
927
+ merged.push(mergedClass);
928
+ }
929
+ }
930
+ return `className="${[
931
+ ...merged,
932
+ ...result,
933
+ ...otherClasses
934
+ ].join(" ")}"`;
935
+ });
936
+ }
937
+ };
938
+ /**
939
+ * Svelte Transformer
940
+ *
941
+ * Transforms Svelte templates by replacing class attribute values.
942
+ * Preserves dynamic {expression} syntax.
943
+ */
944
+ var SvelteTransformer = class {
945
+ name = "svelte";
946
+ pattern = /class\s*=\s*"([^"]+)"/g;
947
+ transform(content, mergeMap, options = {}) {
948
+ if (mergeMap.size === 0) return content;
949
+ const { classPrefix = "_" } = options;
950
+ const sortedKeys = [...mergeMap.keys()].sort((a, b) => b.length - a.length);
951
+ return content.replace(this.pattern, (match, classValue) => {
952
+ const expressions = [];
953
+ const classes = classValue.replace(/\{[^}]+\}/g, (expr) => {
954
+ expressions.push(expr);
955
+ return `__EXPR_${expressions.length - 1}__`;
956
+ }).trim().split(/\s+/);
957
+ const prefixedClasses = classes.filter((c) => c.startsWith(classPrefix) && !c.startsWith("__EXPR_"));
958
+ const otherClasses = classes.filter((c) => !c.startsWith(classPrefix) || c.startsWith("__EXPR_"));
959
+ prefixedClasses.sort();
960
+ let result = [...prefixedClasses];
961
+ const merged = [];
962
+ for (const key of sortedKeys) {
963
+ const patternClasses = key.split(" ");
964
+ const mergedClass = mergeMap.get(key);
965
+ let allPresent = true;
966
+ for (const cls of patternClasses) if (!result.includes(cls)) {
967
+ allPresent = false;
968
+ break;
969
+ }
970
+ if (allPresent) {
971
+ result = result.filter((c) => !patternClasses.includes(c));
972
+ merged.push(mergedClass);
973
+ }
974
+ }
975
+ let finalClasses = [
976
+ ...merged,
977
+ ...result,
978
+ ...otherClasses
979
+ ].join(" ");
980
+ expressions.forEach((expr, i) => {
981
+ finalClasses = finalClasses.replace(`__EXPR_${i}__`, expr);
982
+ });
983
+ return `class="${finalClasses}"`;
984
+ });
985
+ }
986
+ };
987
+ /**
988
+ * Multi-format transformer
989
+ *
990
+ * Automatically selects the appropriate transformer based on content or file type.
991
+ */
992
+ var MultiTransformer = class {
993
+ name = "multi";
994
+ transformers;
995
+ constructor() {
996
+ this.transformers = new Map([
997
+ ["html", new HtmlTransformer()],
998
+ ["jsx", new JsxTransformer()],
999
+ ["tsx", new JsxTransformer()],
1000
+ ["svelte", new SvelteTransformer()]
1001
+ ]);
1002
+ }
1003
+ /**
1004
+ * Register a custom transformer for a file extension
1005
+ */
1006
+ register(extension, transformer) {
1007
+ this.transformers.set(extension, transformer);
1008
+ }
1009
+ transform(content, mergeMap, options) {
1010
+ return this.transformers.get("html").transform(content, mergeMap, options);
1011
+ }
1012
+ /**
1013
+ * Transform with explicit file type
1014
+ */
1015
+ transformWithType(content, mergeMap, fileType, options) {
1016
+ return (this.transformers.get(fileType) || this.transformers.get("html")).transform(content, mergeMap, options);
1017
+ }
1018
+ /**
1019
+ * Transform multiple files
1020
+ */
1021
+ transformFiles(files, mergeMap, options) {
1022
+ return files.map((file) => {
1023
+ const ext = file.path.split(".").pop() || "html";
1024
+ const transformer = this.transformers.get(ext) || this.transformers.get("html");
1025
+ return {
1026
+ path: file.path,
1027
+ content: transformer.transform(file.content, mergeMap, options)
1028
+ };
1029
+ });
1030
+ }
1031
+ };
1032
+ const htmlTransformer = new HtmlTransformer();
1033
+ const jsxTransformer = new JsxTransformer();
1034
+ const svelteTransformer = new SvelteTransformer();
1035
+ const multiTransformer = new MultiTransformer();
1036
+
1037
+ //#endregion
1038
+ //#region src/css-optimizer/merge.ts
1039
+ /**
1040
+ * Optimize CSS by merging co-occurring classes (Luna convenience wrapper)
1041
+ *
1042
+ * This is a convenience function that:
1043
+ * 1. Extracts class usages from HTML using HtmlExtractor
1044
+ * 2. Converts Luna's declaration mapping format
1045
+ * 3. Calls the core optimizer
1046
+ *
1047
+ * For non-Luna projects, use the `optimize` function instead.
1048
+ *
1049
+ * @param css - Original CSS content
1050
+ * @param html - HTML content to analyze
1051
+ * @param declarationMapping - Luna's declaration -> class name mapping
1052
+ * @param options - Optimization options
1053
+ */
1054
+ function optimizeCss(css, html, declarationMapping, options = {}) {
1055
+ const { classPrefix = "_", verbose = false } = options;
1056
+ const log = (msg) => {
1057
+ if (verbose) console.log(`[optimizer] ${msg}`);
1058
+ };
1059
+ const usages = htmlExtractor.extract(html, {
1060
+ classPrefix,
1061
+ source: "html"
1062
+ });
1063
+ log(`Found ${usages.length} class usage sites`);
1064
+ if (usages.length === 0) return {
1065
+ css,
1066
+ mergeMap: /* @__PURE__ */ new Map(),
1067
+ patterns: [],
1068
+ stats: {
1069
+ originalClasses: Object.keys(declarationMapping).length,
1070
+ mergedPatterns: 0,
1071
+ estimatedBytesSaved: 0
1072
+ }
1073
+ };
1074
+ const classToDecl = /* @__PURE__ */ new Map();
1075
+ for (const [decl, cls] of Object.entries(declarationMapping)) classToDecl.set(cls, decl);
1076
+ return optimizeCore(usages, css, classToDecl, options);
1077
+ }
1078
+ /**
1079
+ * Apply optimization to HTML by replacing class combinations (Luna convenience wrapper)
1080
+ *
1081
+ * This is a convenience function that uses HtmlTransformer.
1082
+ * For non-Luna projects, use the transformer directly.
1083
+ *
1084
+ * @param html - HTML content to transform
1085
+ * @param mergeMap - Merge map from optimizeCss result
1086
+ * @param classPrefix - Class prefix (default: "_")
1087
+ */
1088
+ function optimizeHtml(html, mergeMap, classPrefix = "_") {
1089
+ return htmlTransformer.transform(html, mergeMap, { classPrefix });
1090
+ }
1091
+
1092
+ //#endregion
1093
+ //#region src/vite-plugin.ts
1094
+ const CSS_START_MARKER = "/* LUNA_CSS_START */";
1095
+ const CSS_END_MARKER = "/* LUNA_CSS_END */";
1096
+ const VIRTUAL_CSS_ID = "virtual:luna.css";
1097
+ const VIRTUAL_SHARED_CSS_ID = "virtual:luna-shared.css";
1098
+ const VIRTUAL_RUNTIME_ID = "virtual:luna-css-runtime";
1099
+ const RESOLVED_VIRTUAL_CSS_ID = "\0" + VIRTUAL_CSS_ID;
1100
+ const RESOLVED_VIRTUAL_SHARED_CSS_ID = "\0" + VIRTUAL_SHARED_CSS_ID;
1101
+ const RESOLVED_VIRTUAL_RUNTIME_ID = "\0" + VIRTUAL_RUNTIME_ID;
1102
+ /**
1103
+ * Generate inline dev runtime code
1104
+ * Uses import.meta.env.DEV for tree-shaking in production builds
1105
+ */
1106
+ function generateRuntimeCode() {
1107
+ return `
1108
+ // Luna CSS Dev Runtime - Auto-generated
1109
+ // Production: DCE removes dev-only code via import.meta.env.DEV
1110
+
1111
+ // Hash functions (shared between dev and prod for class name generation)
1112
+ function djb2Hash(s) {
1113
+ let hash = 5381;
1114
+ for (let i = 0; i < s.length; i++) {
1115
+ hash = ((hash << 5) + hash + s.charCodeAt(i)) >>> 0;
1116
+ }
1117
+ return hash;
1118
+ }
1119
+
1120
+ function toBase36(n) {
1121
+ const chars = "0123456789abcdefghijklmnopqrstuvwxyz";
1122
+ n = n & 0xffffff;
1123
+ if (n === 0) return "0";
1124
+ let result = "";
1125
+ while (n > 0) { result = chars[n % 36] + result; n = Math.floor(n / 36); }
1126
+ return result;
1127
+ }
1128
+
1129
+ function hashClassName(decl) { return "_" + toBase36(djb2Hash(decl)); }
1130
+
1131
+ // Dev-only: runtime CSS injection
1132
+ let devState;
1133
+ let devInject;
1134
+
1135
+ if (import.meta.env.DEV) {
1136
+ devState = { rules: new Map(), styleEl: null, initialized: false };
1137
+
1138
+ const init = () => {
1139
+ if (devState.initialized || typeof document === "undefined") return;
1140
+ devState.styleEl = document.createElement("style");
1141
+ devState.styleEl.id = "luna-dev-css";
1142
+ document.head.appendChild(devState.styleEl);
1143
+ devState.initialized = true;
1144
+ };
1145
+
1146
+ devInject = (className, rule) => {
1147
+ init();
1148
+ if (devState.rules.has(className)) return;
1149
+ devState.rules.set(className, rule);
1150
+ if (devState.styleEl) devState.styleEl.textContent += rule;
1151
+ console.warn("[luna-css] Generated at runtime:", rule, "\\n → Run 'luna css extract' to pre-generate");
1152
+ };
1153
+ }
1154
+
1155
+ export function css(prop, val) {
1156
+ const decl = prop + ":" + val;
1157
+ const cls = hashClassName(decl);
1158
+ if (import.meta.env.DEV && devInject) {
1159
+ devInject(cls, "." + cls + "{" + decl + "}");
1160
+ }
1161
+ return cls;
1162
+ }
1163
+
1164
+ export function styles(pairs) { return pairs.map(([p, v]) => css(p, v)).join(" "); }
1165
+
1166
+ export function on(pseudo, prop, val) {
1167
+ const key = pseudo + ":" + prop + ":" + val;
1168
+ const cls = hashClassName(key);
1169
+ if (import.meta.env.DEV && devInject) {
1170
+ devInject(cls, "." + cls + pseudo + "{" + prop + ":" + val + "}");
1171
+ }
1172
+ return cls;
1173
+ }
1174
+
1175
+ export function hover(p, v) { return on(":hover", p, v); }
1176
+ export function focus(p, v) { return on(":focus", p, v); }
1177
+ export function active(p, v) { return on(":active", p, v); }
1178
+ export function combine(classes) { return classes.filter(Boolean).join(" "); }
1179
+
1180
+ // Dev-only utilities
1181
+ export function getGeneratedCount() {
1182
+ if (import.meta.env.DEV && devState) {
1183
+ return devState.rules.size;
1184
+ }
1185
+ return 0;
1186
+ }
1187
+
1188
+ export function getGeneratedCss() {
1189
+ if (import.meta.env.DEV && devState) {
1190
+ return Array.from(devState.rules.values()).join("");
1191
+ }
1192
+ return "";
1193
+ }
1194
+ `;
1195
+ }
1196
+ /**
1197
+ * Inject dev runtime script into HTML
1198
+ */
1199
+ function injectDevRuntime(html) {
1200
+ return html.replace("</head>", "<script type=\"module\">\nimport { css, hover, focus, styles, combine } from \"virtual:luna-css-runtime\";\nwindow.__lunaCss = { css, hover, focus, styles, combine };\n<\/script>\n</head>");
1201
+ }
1202
+ /**
1203
+ * Luna CSS Vite Plugin
1204
+ *
1205
+ * @example
1206
+ * ```ts
1207
+ * // vite.config.ts
1208
+ * import { lunaCss } from "@luna_ui/luna/vite-plugin";
1209
+ *
1210
+ * export default defineConfig({
1211
+ * plugins: [
1212
+ * lunaCss({
1213
+ * src: ["src/examples/todomvc"],
1214
+ * mode: "auto",
1215
+ * threshold: 4096,
1216
+ * }),
1217
+ * ],
1218
+ * });
1219
+ * ```
1220
+ *
1221
+ * @example
1222
+ * ```ts
1223
+ * // main.ts - Import virtual CSS like Tailwind
1224
+ * import "virtual:luna.css";
1225
+ * ```
1226
+ *
1227
+ * @example
1228
+ * ```ts
1229
+ * // Split mode with per-directory chunks
1230
+ * lunaCss({
1231
+ * src: ["src"],
1232
+ * split: true,
1233
+ * sharedThreshold: 3,
1234
+ * })
1235
+ *
1236
+ * // In your entry:
1237
+ * import "virtual:luna.css"; // All CSS
1238
+ * // Or for fine-grained control:
1239
+ * import "virtual:luna-shared.css"; // Shared CSS only
1240
+ * import "virtual:luna-chunk/todomvc.css"; // Per-directory chunk
1241
+ * ```
1242
+ */
1243
+ function lunaCss(options = {}) {
1244
+ const { src = ["src"], mode = "auto", threshold = 4096, cssFileName = "luna", verbose = false, split = false, sharedThreshold = 3, devRuntime, injectRuntime = true, experimental = {} } = options;
1245
+ const { optimize: enableOptimize = false, optimizeMinFrequency = 2, optimizeMaxPatternSize = 5 } = experimental;
1246
+ const srcDirs = Array.isArray(src) ? src : [src];
1247
+ let config;
1248
+ let cachedCss = null;
1249
+ let cachedSplitResult = null;
1250
+ let cachedMapping = {};
1251
+ let cachedMergeMap = null;
1252
+ let cachedOptimizedCss = null;
1253
+ let lastExtractTime = 0;
1254
+ const pluginCwd = process.cwd();
1255
+ const log = (msg) => {
1256
+ if (verbose || enableOptimize) console.log(`[luna-css] ${msg}`);
1257
+ };
1258
+ const resolveSrcDir = (dir) => {
1259
+ if (path.isAbsolute(dir)) return dir;
1260
+ const configDir = config.configFile ? path.dirname(config.configFile) : pluginCwd;
1261
+ return path.resolve(configDir, dir);
1262
+ };
1263
+ const extractCss = () => {
1264
+ const now = Date.now();
1265
+ if (cachedCss && now - lastExtractTime < 1e3) return {
1266
+ css: cachedCss,
1267
+ mapping: cachedMapping
1268
+ };
1269
+ let allCss = "";
1270
+ const seenRules = /* @__PURE__ */ new Set();
1271
+ const combinedMapping = {};
1272
+ for (const dir of srcDirs) {
1273
+ const fullPath = resolveSrcDir(dir);
1274
+ if (fs.existsSync(fullPath)) {
1275
+ const { css, mapping } = extract(fullPath, { warn: false });
1276
+ Object.assign(combinedMapping, mapping);
1277
+ for (const rule of css.split("}")) {
1278
+ const trimmed = rule.trim();
1279
+ if (trimmed && !seenRules.has(trimmed)) {
1280
+ seenRules.add(trimmed);
1281
+ allCss += trimmed + "}";
1282
+ }
1283
+ }
1284
+ } else log(`Warning: Source directory not found: ${fullPath}`);
1285
+ }
1286
+ cachedCss = allCss;
1287
+ cachedMapping = combinedMapping;
1288
+ lastExtractTime = now;
1289
+ log(`Extracted ${allCss.length} bytes of CSS from ${srcDirs.join(", ")}`);
1290
+ return {
1291
+ css: allCss,
1292
+ mapping: combinedMapping
1293
+ };
1294
+ };
1295
+ const findHtmlFiles = (dir) => {
1296
+ const files = [];
1297
+ const walk = (currentDir) => {
1298
+ if (!fs.existsSync(currentDir)) return;
1299
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
1300
+ for (const entry of entries) {
1301
+ const fullPath = path.join(currentDir, entry.name);
1302
+ if (entry.isDirectory() && ![
1303
+ "node_modules",
1304
+ ".git",
1305
+ "dist",
1306
+ "target"
1307
+ ].includes(entry.name)) walk(fullPath);
1308
+ else if (entry.isFile() && entry.name.endsWith(".html")) files.push(fullPath);
1309
+ }
1310
+ };
1311
+ walk(dir);
1312
+ return files;
1313
+ };
1314
+ const runOptimization = () => {
1315
+ if (!enableOptimize) return null;
1316
+ const { css, mapping } = extractCss();
1317
+ if (!css) return null;
1318
+ const htmlFilePaths = findHtmlFiles(config.configFile ? path.dirname(config.configFile) : pluginCwd);
1319
+ if (htmlFilePaths.length === 0) {
1320
+ log("[experimental] No HTML files found for optimization");
1321
+ return null;
1322
+ }
1323
+ let combinedHtml = "";
1324
+ for (const htmlPath of htmlFilePaths) try {
1325
+ combinedHtml += fs.readFileSync(htmlPath, "utf-8");
1326
+ } catch (e) {}
1327
+ if (!combinedHtml) return null;
1328
+ log(`[experimental] Running CSS co-occurrence optimization on ${htmlFilePaths.length} HTML file(s)...`);
1329
+ const optimizeResult = optimizeCss(css, combinedHtml, mapping, {
1330
+ minFrequency: optimizeMinFrequency,
1331
+ maxPatternSize: optimizeMaxPatternSize,
1332
+ verbose
1333
+ });
1334
+ if (optimizeResult.patterns.length > 0) {
1335
+ log(`[experimental] Merged ${optimizeResult.stats.mergedPatterns} patterns`);
1336
+ log(`[experimental] Estimated savings: ${optimizeResult.stats.estimatedBytesSaved} bytes`);
1337
+ for (const pattern of optimizeResult.patterns) log(`[experimental] ${pattern.originalClasses.join(" ")} -> ${pattern.mergedClass} (${pattern.frequency}x)`);
1338
+ return {
1339
+ css: optimizeResult.css,
1340
+ mergeMap: optimizeResult.mergeMap
1341
+ };
1342
+ }
1343
+ log("[experimental] No patterns found for optimization");
1344
+ return null;
1345
+ };
1346
+ const getOptimizedCss = () => {
1347
+ if (!enableOptimize) return extractCss().css;
1348
+ if (cachedOptimizedCss !== null && cachedMergeMap !== null) return cachedOptimizedCss;
1349
+ const result = runOptimization();
1350
+ if (result) {
1351
+ cachedOptimizedCss = result.css;
1352
+ cachedMergeMap = result.mergeMap;
1353
+ return result.css;
1354
+ }
1355
+ return extractCss().css;
1356
+ };
1357
+ const extractSplitCss = () => {
1358
+ const now = Date.now();
1359
+ if (cachedSplitResult && now - lastExtractTime < 1e3) return cachedSplitResult;
1360
+ const combinedChunks = /* @__PURE__ */ new Map();
1361
+ let combinedSharedCss = "";
1362
+ let combinedCss = "";
1363
+ const combinedMapping = {};
1364
+ const combinedStats = /* @__PURE__ */ new Map();
1365
+ for (const dir of srcDirs) {
1366
+ const fullPath = resolveSrcDir(dir);
1367
+ if (fs.existsSync(fullPath)) {
1368
+ const result = extractSplit(fullPath, "dir", {
1369
+ warn: false,
1370
+ sharedThreshold
1371
+ });
1372
+ for (const [key, chunk] of result.chunks) {
1373
+ const prefixedKey = srcDirs.length > 1 ? `${dir}/${key}` : key;
1374
+ combinedChunks.set(prefixedKey, chunk);
1375
+ }
1376
+ if (result.shared.css) combinedSharedCss += result.shared.css;
1377
+ combinedCss += result.combined;
1378
+ Object.assign(combinedMapping, result.mapping);
1379
+ for (const [key, stat] of result.stats) {
1380
+ const prefixedKey = srcDirs.length > 1 ? `${dir}/${key}` : key;
1381
+ combinedStats.set(prefixedKey, stat);
1382
+ }
1383
+ }
1384
+ }
1385
+ cachedSplitResult = {
1386
+ chunks: combinedChunks,
1387
+ shared: {
1388
+ css: combinedSharedCss,
1389
+ styles: {
1390
+ base: /* @__PURE__ */ new Set(),
1391
+ pseudo: [],
1392
+ media: []
1393
+ }
1394
+ },
1395
+ combined: combinedCss,
1396
+ mapping: combinedMapping,
1397
+ stats: combinedStats
1398
+ };
1399
+ lastExtractTime = now;
1400
+ log(`Split extracted: ${combinedChunks.size} chunks, ${combinedSharedCss.length} bytes shared`);
1401
+ return cachedSplitResult;
1402
+ };
1403
+ const determineMode = (css) => {
1404
+ if (mode === "auto") return css.length > threshold ? "external" : "inline";
1405
+ return mode === "external" ? "external" : "inline";
1406
+ };
1407
+ const injectInline = (html, css) => {
1408
+ const startIdx = html.indexOf(CSS_START_MARKER);
1409
+ const endIdx = html.indexOf(CSS_END_MARKER);
1410
+ if (startIdx === -1 || endIdx === -1) {
1411
+ log("Warning: CSS markers not found in HTML");
1412
+ return html;
1413
+ }
1414
+ return `${html.substring(0, startIdx + 20)}\n ${css}\n ${html.substring(endIdx)}`;
1415
+ };
1416
+ return {
1417
+ name: "luna-css",
1418
+ configResolved(resolvedConfig) {
1419
+ config = resolvedConfig;
1420
+ },
1421
+ resolveId(id) {
1422
+ if (id === VIRTUAL_CSS_ID) return RESOLVED_VIRTUAL_CSS_ID;
1423
+ if (id === VIRTUAL_SHARED_CSS_ID) return RESOLVED_VIRTUAL_SHARED_CSS_ID;
1424
+ if (id === VIRTUAL_RUNTIME_ID) return RESOLVED_VIRTUAL_RUNTIME_ID;
1425
+ if (id.startsWith("virtual:luna-chunk/")) return "\0" + id;
1426
+ },
1427
+ load(id) {
1428
+ if (id === RESOLVED_VIRTUAL_CSS_ID) {
1429
+ if (split) return extractSplitCss().combined;
1430
+ if (config.command === "build" && enableOptimize) return getOptimizedCss();
1431
+ return extractCss().css;
1432
+ }
1433
+ if (id === RESOLVED_VIRTUAL_SHARED_CSS_ID) {
1434
+ if (!split) {
1435
+ log("Warning: virtual:luna-shared.css used without split mode");
1436
+ return "";
1437
+ }
1438
+ return extractSplitCss().shared.css;
1439
+ }
1440
+ if (id === RESOLVED_VIRTUAL_RUNTIME_ID) return generateRuntimeCode();
1441
+ if (id.startsWith("\0virtual:luna-chunk/")) {
1442
+ const chunkName = id.replace("\0virtual:luna-chunk/", "").replace(".css", "");
1443
+ if (!split) {
1444
+ log(`Warning: virtual:luna-chunk/${chunkName}.css used without split mode`);
1445
+ return "";
1446
+ }
1447
+ const chunk = extractSplitCss().chunks.get(chunkName);
1448
+ if (chunk) return chunk.css;
1449
+ log(`Warning: Chunk "${chunkName}" not found`);
1450
+ return "";
1451
+ }
1452
+ },
1453
+ transformIndexHtml: {
1454
+ order: "pre",
1455
+ handler(html, ctx) {
1456
+ const isBuild = config.command === "build";
1457
+ const isDev = !isBuild;
1458
+ const shouldInjectRuntime = injectRuntime && (devRuntime ?? isDev) && isDev;
1459
+ if (split && isBuild) {
1460
+ const result$1 = extractSplitCss();
1461
+ const sharedCss = result$1.shared.css;
1462
+ const combinedCss = result$1.combined;
1463
+ log(`Split mode build: ${result$1.chunks.size} chunks, ${sharedCss.length} bytes shared`);
1464
+ return injectInline(html, combinedCss);
1465
+ }
1466
+ const { css } = extractCss();
1467
+ if (!css) {
1468
+ if (shouldInjectRuntime) return injectDevRuntime(html);
1469
+ return html;
1470
+ }
1471
+ let finalCss = css;
1472
+ let finalHtml = html;
1473
+ if (enableOptimize && isBuild) {
1474
+ finalCss = getOptimizedCss();
1475
+ if (cachedMergeMap && cachedMergeMap.size > 0) finalHtml = optimizeHtml(html, cachedMergeMap);
1476
+ }
1477
+ log(`Mode: ${determineMode(finalCss)}, Build: ${isBuild} (${finalCss.length} bytes)`);
1478
+ let result = injectInline(finalHtml, finalCss);
1479
+ if (shouldInjectRuntime) result = injectDevRuntime(result);
1480
+ return result;
1481
+ }
1482
+ },
1483
+ configureServer(server) {
1484
+ server.middlewares.use((req, res, next) => {
1485
+ if (req.url === `/${cssFileName}.css`) {
1486
+ const { css } = extractCss();
1487
+ res.setHeader("Content-Type", "text/css");
1488
+ res.setHeader("Cache-Control", "no-cache");
1489
+ res.end(css);
1490
+ return;
1491
+ }
1492
+ next();
1493
+ });
1494
+ for (const dir of srcDirs) {
1495
+ const fullPath = resolveSrcDir(dir);
1496
+ if (fs.existsSync(fullPath)) server.watcher.add(`${fullPath}/**/*.mbt`);
1497
+ }
1498
+ server.watcher.on("change", (file) => {
1499
+ if (file.endsWith(".mbt")) {
1500
+ log(`File changed: ${file}`);
1501
+ cachedCss = null;
1502
+ cachedSplitResult = null;
1503
+ cachedMergeMap = null;
1504
+ cachedOptimizedCss = null;
1505
+ const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_CSS_ID);
1506
+ if (mod) server.moduleGraph.invalidateModule(mod);
1507
+ const sharedMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_SHARED_CSS_ID);
1508
+ if (sharedMod) server.moduleGraph.invalidateModule(sharedMod);
1509
+ server.ws.send({ type: "full-reload" });
1510
+ }
1511
+ });
1512
+ }
1513
+ };
1514
+ }
1515
+ var vite_plugin_default = lunaCss;
1516
+
1517
+ //#endregion
1518
+ export { vite_plugin_default as default, lunaCss };