@luna_ui/luna 0.3.4 → 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 (172) 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.js +1 -1
  9. package/dist/{src-BDdxGwvq.js → src-CHiGeWfy.js} +1 -1
  10. package/dist/vite-plugin.d.ts +122 -0
  11. package/dist/vite-plugin.js +1518 -0
  12. package/package.json +16 -2
  13. package/src/css/extract.ts +798 -0
  14. package/src/css/index.ts +10 -0
  15. package/src/css/inject.ts +205 -0
  16. package/src/css/inline.ts +182 -0
  17. package/src/css/minify.ts +70 -0
  18. package/src/css/optimizer.ts +6 -0
  19. package/src/css/runtime.ts +344 -0
  20. package/src/css-optimizer/README.md +353 -0
  21. package/src/css-optimizer/cooccurrence.ts +100 -0
  22. package/src/css-optimizer/core.ts +263 -0
  23. package/src/css-optimizer/extractors.ts +243 -0
  24. package/src/css-optimizer/hash.ts +54 -0
  25. package/src/css-optimizer/index.ts +129 -0
  26. package/src/css-optimizer/merge.ts +109 -0
  27. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  28. package/src/css-optimizer/parser.ts +120 -0
  29. package/src/css-optimizer/pattern.ts +171 -0
  30. package/src/css-optimizer/transformers.ts +301 -0
  31. package/src/css-optimizer/types.ts +128 -0
  32. package/src/event-utils.ts +227 -0
  33. package/src/index.ts +890 -0
  34. package/src/jsx-dev-runtime.ts +2 -0
  35. package/src/jsx-runtime.ts +398 -0
  36. package/src/vite-plugin.ts +718 -0
  37. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  38. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  39. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  40. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  41. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  42. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  43. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  44. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  45. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  46. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  50. 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
  51. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  64. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  65. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  66. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  67. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  68. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  69. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  70. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  71. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  72. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  73. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  74. 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
  75. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  76. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  77. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  78. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  79. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  80. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  81. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  82. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  83. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  84. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  85. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  117. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  118. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  119. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  120. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  121. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  122. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  123. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  124. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  125. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  137. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  138. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  139. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  140. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  141. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  142. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  143. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  144. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  145. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  147. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  148. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  149. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  150. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  151. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  152. package/tests/context.test.ts +118 -0
  153. package/tests/css-optimizer-extractors.test.ts +264 -0
  154. package/tests/css-optimizer-integration.test.ts +566 -0
  155. package/tests/css-optimizer-transformers.test.ts +301 -0
  156. package/tests/css-optimizer.test.ts +646 -0
  157. package/tests/css-runtime.bench.ts +442 -0
  158. package/tests/css-runtime.test.ts +342 -0
  159. package/tests/dom.test.ts +872 -0
  160. package/tests/integration.test.ts +405 -0
  161. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  162. package/tests/jsx-runtime.test.tsx +393 -0
  163. package/tests/lifecycle.test.ts +833 -0
  164. package/tests/move-before.bench.ts +304 -0
  165. package/tests/preact-signals-comparison.test.ts +1608 -0
  166. package/tests/resource.test.ts +160 -0
  167. package/tests/router.test.ts +117 -0
  168. package/tests/show-initial-mount-leak.test.tsx +182 -0
  169. package/tests/solidjs-api.test.ts +659 -0
  170. package/tests/static-perf.bench.ts +64 -0
  171. package/tests/store.test.ts +263 -0
  172. package/tests/tsx-syntax.test.tsx +404 -0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Luna CSS Utilities
3
+ *
4
+ * Zero-runtime CSS generation and extraction tools.
5
+ */
6
+
7
+ export * from "./extract.js";
8
+ export * from "./minify.js";
9
+ export * from "./inline.js";
10
+ export * from "./inject.js";
@@ -0,0 +1,205 @@
1
+ /**
2
+ * CSS Injection into HTML files
3
+ *
4
+ * Replaces CSS between markers in HTML files with extracted CSS.
5
+ * Supports inline embedding or external file generation.
6
+ */
7
+
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { extract } from "./extract.js";
11
+
12
+ // Markers for CSS injection
13
+ const CSS_START_MARKER = "/* LUNA_CSS_START */";
14
+ const CSS_END_MARKER = "/* LUNA_CSS_END */";
15
+
16
+ // Link marker for external CSS
17
+ const LINK_MARKER = "<!-- LUNA_CSS_LINK -->";
18
+
19
+ export type OutputMode = "inline" | "external" | "auto";
20
+
21
+ export interface InjectHtmlOptions {
22
+ srcDir: string;
23
+ htmlFile: string;
24
+ outputFile?: string;
25
+ /** Output mode: "inline" embeds CSS in HTML, "external" creates separate .css file, "auto" chooses based on threshold */
26
+ mode?: OutputMode;
27
+ /** Size threshold in bytes for "auto" mode (default: 4096) */
28
+ threshold?: number;
29
+ /** CSS filename for external mode (default: "luna.css") */
30
+ cssFileName?: string;
31
+ verbose?: boolean;
32
+ }
33
+
34
+ export interface InjectHtmlResult {
35
+ html: string;
36
+ css: string;
37
+ replaced: boolean;
38
+ /** Path to external CSS file if created */
39
+ cssFile?: string;
40
+ /** Actual mode used */
41
+ mode: OutputMode;
42
+ }
43
+
44
+ /**
45
+ * Inject extracted CSS into HTML file between markers
46
+ */
47
+ export function injectCssToHtml(options: InjectHtmlOptions): InjectHtmlResult {
48
+ const {
49
+ srcDir,
50
+ htmlFile,
51
+ mode = "inline",
52
+ threshold = 4096,
53
+ cssFileName = "luna.css",
54
+ verbose = false,
55
+ } = options;
56
+
57
+ // Extract CSS from source
58
+ const { css } = extract(srcDir, { warn: false });
59
+
60
+ if (verbose) {
61
+ console.error(`Extracted CSS: ${css.length} bytes`);
62
+ }
63
+
64
+ // Read HTML file
65
+ const html = fs.readFileSync(htmlFile, "utf-8");
66
+
67
+ // Determine actual mode
68
+ let actualMode: OutputMode = mode;
69
+ if (mode === "auto") {
70
+ actualMode = css.length > threshold ? "external" : "inline";
71
+ if (verbose) {
72
+ console.error(
73
+ `Auto mode: ${css.length} bytes ${css.length > threshold ? ">" : "<="} ${threshold} threshold → ${actualMode}`
74
+ );
75
+ }
76
+ }
77
+
78
+ if (actualMode === "external") {
79
+ return injectExternal(html, css, htmlFile, cssFileName, verbose);
80
+ } else {
81
+ return injectInline(html, css, htmlFile, verbose);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Inject CSS inline between markers
87
+ */
88
+ function injectInline(
89
+ html: string,
90
+ css: string,
91
+ htmlFile: string,
92
+ verbose: boolean
93
+ ): InjectHtmlResult {
94
+ // Find markers
95
+ const startIdx = html.indexOf(CSS_START_MARKER);
96
+ const endIdx = html.indexOf(CSS_END_MARKER);
97
+
98
+ if (startIdx === -1 || endIdx === -1) {
99
+ if (verbose) {
100
+ console.error(
101
+ `Warning: Markers not found in ${htmlFile}. Add /* LUNA_CSS_START */ and /* LUNA_CSS_END */ to your HTML.`
102
+ );
103
+ }
104
+ return { html, css, replaced: false, mode: "inline" };
105
+ }
106
+
107
+ // Replace content between markers
108
+ const before = html.substring(0, startIdx + CSS_START_MARKER.length);
109
+ const after = html.substring(endIdx);
110
+ const newHtml = `${before}\n ${css}\n ${after}`;
111
+
112
+ if (verbose) {
113
+ console.error(`Injected CSS inline into ${htmlFile}`);
114
+ }
115
+
116
+ return { html: newHtml, css, replaced: true, mode: "inline" };
117
+ }
118
+
119
+ /**
120
+ * Create external CSS file and add link tag
121
+ */
122
+ function injectExternal(
123
+ html: string,
124
+ css: string,
125
+ htmlFile: string,
126
+ cssFileName: string,
127
+ verbose: boolean
128
+ ): InjectHtmlResult {
129
+ const htmlDir = path.dirname(htmlFile);
130
+ const cssFilePath = path.join(htmlDir, cssFileName);
131
+
132
+ // Check for link marker or style markers
133
+ const hasLinkMarker = html.includes(LINK_MARKER);
134
+ const startIdx = html.indexOf(CSS_START_MARKER);
135
+ const endIdx = html.indexOf(CSS_END_MARKER);
136
+
137
+ let newHtml = html;
138
+ let replaced = false;
139
+
140
+ if (hasLinkMarker) {
141
+ // Replace link marker with actual link tag
142
+ newHtml = html.replace(
143
+ LINK_MARKER,
144
+ `<link rel="stylesheet" href="${cssFileName}">`
145
+ );
146
+ replaced = true;
147
+ } else if (startIdx !== -1 && endIdx !== -1) {
148
+ // Clear inline markers and add link before </head>
149
+ const before = html.substring(0, startIdx + CSS_START_MARKER.length);
150
+ const after = html.substring(endIdx);
151
+ newHtml = `${before}\n /* External: ${cssFileName} */\n ${after}`;
152
+
153
+ // Add link tag before </head> if not already present
154
+ if (!newHtml.includes(`href="${cssFileName}"`)) {
155
+ newHtml = newHtml.replace(
156
+ "</head>",
157
+ ` <link rel="stylesheet" href="${cssFileName}">\n</head>`
158
+ );
159
+ }
160
+ replaced = true;
161
+ } else {
162
+ if (verbose) {
163
+ console.error(
164
+ `Warning: No markers found in ${htmlFile}. Add <!-- LUNA_CSS_LINK --> or /* LUNA_CSS_START/END */ markers.`
165
+ );
166
+ }
167
+ }
168
+
169
+ if (verbose && replaced) {
170
+ console.error(`Created external CSS: ${cssFilePath}`);
171
+ }
172
+
173
+ return {
174
+ html: newHtml,
175
+ css,
176
+ replaced,
177
+ cssFile: cssFilePath,
178
+ mode: "external",
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Inject CSS and write to file(s)
184
+ */
185
+ export function injectAndWrite(options: InjectHtmlOptions): InjectHtmlResult {
186
+ const result = injectCssToHtml(options);
187
+ const outputFile = options.outputFile || options.htmlFile;
188
+
189
+ if (result.replaced) {
190
+ fs.writeFileSync(outputFile, result.html);
191
+ if (options.verbose) {
192
+ console.error(`Written HTML to: ${outputFile}`);
193
+ }
194
+
195
+ // Write external CSS file if needed
196
+ if (result.mode === "external" && result.cssFile) {
197
+ fs.writeFileSync(result.cssFile, result.css);
198
+ if (options.verbose) {
199
+ console.error(`Written CSS to: ${result.cssFile}`);
200
+ }
201
+ }
202
+ }
203
+
204
+ return result;
205
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * CSS Class Name Inliner
3
+ *
4
+ * Replaces CSS utility function calls with pre-computed class names.
5
+ * This enables true zero-runtime CSS by eliminating runtime style registration.
6
+ */
7
+
8
+ import { extract } from "./extract.js";
9
+
10
+ // =============================================================================
11
+ // Pattern Matching for Compiled MoonBit Output
12
+ // =============================================================================
13
+
14
+ // Pattern for register_decl calls (base CSS)
15
+ // mizchi$luna$luna$css$$register_decl("display:flex")
16
+ const REGISTER_DECL_PATTERN =
17
+ /mizchi\$luna\$luna\$css\$\$register_decl\s*\(\s*"([^"]+)"\s*\)/g;
18
+
19
+ // Pattern for register_pseudo calls
20
+ // mizchi$luna$luna$css$$register_pseudo(":hover", "background", "#2563eb")
21
+ const REGISTER_PSEUDO_PATTERN =
22
+ /mizchi\$luna\$luna\$css\$\$register_pseudo\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
23
+
24
+ // Pattern for register_media calls
25
+ // mizchi$luna$luna$css$$register_media("min-width:768px", "padding", "2rem")
26
+ const REGISTER_MEDIA_PATTERN =
27
+ /mizchi\$luna\$luna\$css\$\$register_media\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)/g;
28
+
29
+ // =============================================================================
30
+ // Types
31
+ // =============================================================================
32
+
33
+ export interface InlineOptions {
34
+ verbose?: boolean;
35
+ dryRun?: boolean;
36
+ removeRegistry?: boolean;
37
+ }
38
+
39
+ export interface Replacement {
40
+ type: "base" | "pseudo" | "media";
41
+ from: string;
42
+ to: string;
43
+ key?: string;
44
+ decl?: string;
45
+ }
46
+
47
+ export interface InlineResult {
48
+ code: string;
49
+ replacements: Replacement[];
50
+ originalSize: number;
51
+ finalSize: number;
52
+ }
53
+
54
+ // =============================================================================
55
+ // Inlining Logic
56
+ // =============================================================================
57
+
58
+ /**
59
+ * Replace CSS function calls with pre-computed class names
60
+ */
61
+ export function inlineCSS(
62
+ code: string,
63
+ mapping: Record<string, string>,
64
+ options: InlineOptions = {}
65
+ ): InlineResult {
66
+ const { verbose = false } = options;
67
+ const replacements: Replacement[] = [];
68
+ const originalSize = Buffer.byteLength(code, "utf-8");
69
+
70
+ let result = code;
71
+
72
+ // Replace register_decl calls (base CSS)
73
+ result = result.replace(REGISTER_DECL_PATTERN, (match, decl) => {
74
+ const className = mapping[decl];
75
+ if (className) {
76
+ replacements.push({
77
+ type: "base",
78
+ from: match,
79
+ to: `"${className}"`,
80
+ decl,
81
+ });
82
+ return `"${className}"`;
83
+ }
84
+ if (verbose) {
85
+ console.error(`Warning: No mapping for base declaration: ${decl}`);
86
+ }
87
+ return match;
88
+ });
89
+
90
+ // Replace register_pseudo calls
91
+ result = result.replace(
92
+ REGISTER_PSEUDO_PATTERN,
93
+ (match, pseudo, property, value) => {
94
+ const key = `${pseudo}:${property}:${value}`;
95
+ const className = mapping[key];
96
+ if (className) {
97
+ replacements.push({
98
+ type: "pseudo",
99
+ from: match,
100
+ to: `"${className}"`,
101
+ key,
102
+ });
103
+ return `"${className}"`;
104
+ }
105
+ if (verbose) {
106
+ console.error(`Warning: No mapping for pseudo: ${key}`);
107
+ }
108
+ return match;
109
+ }
110
+ );
111
+
112
+ // Replace register_media calls
113
+ result = result.replace(
114
+ REGISTER_MEDIA_PATTERN,
115
+ (match, condition, property, value) => {
116
+ const key = `@media(${condition}):${property}:${value}`;
117
+ const className = mapping[key];
118
+ if (className) {
119
+ replacements.push({
120
+ type: "media",
121
+ from: match,
122
+ to: `"${className}"`,
123
+ key,
124
+ });
125
+ return `"${className}"`;
126
+ }
127
+ if (verbose) {
128
+ console.error(`Warning: No mapping for media: ${key}`);
129
+ }
130
+ return match;
131
+ }
132
+ );
133
+
134
+ const finalSize = Buffer.byteLength(result, "utf-8");
135
+
136
+ return { code: result, replacements, originalSize, finalSize };
137
+ }
138
+
139
+ /**
140
+ * Remove CSS registry code (dead code after inlining)
141
+ */
142
+ export function removeRegistryCode(code: string): string {
143
+ const patterns = [
144
+ // Registry objects
145
+ /const mizchi\$luna\$luna\$css\$\$registry\s*=\s*\{[^}]+\};?\n?/g,
146
+ /const mizchi\$luna\$luna\$css\$\$pseudo_registry\s*=\s*\{[^}]+\};?\n?/g,
147
+ /const mizchi\$luna\$luna\$css\$\$media_registry\s*=\s*\{[^}]+\};?\n?/g,
148
+ // Class chars constant
149
+ /const mizchi\$luna\$luna\$css\$\$class_chars\s*=\s*"[^"]+";?\n?/g,
150
+ // Bind constants
151
+ /const mizchi\$luna\$luna\$css\$\$[a-z_]+\$46\$42\$bind[^;]+;?\n?/g,
152
+ ];
153
+
154
+ let result = code;
155
+ for (const pattern of patterns) {
156
+ result = result.replace(pattern, "");
157
+ }
158
+
159
+ return result;
160
+ }
161
+
162
+ /**
163
+ * Inline CSS with mapping extracted from source directory
164
+ */
165
+ export function inlineFromSource(
166
+ code: string,
167
+ srcDir: string,
168
+ options: InlineOptions = {}
169
+ ): InlineResult {
170
+ const { mapping } = extract(srcDir, { warn: false });
171
+ let result = inlineCSS(code, mapping, options);
172
+
173
+ if (options.removeRegistry) {
174
+ result = {
175
+ ...result,
176
+ code: removeRegistryCode(result.code),
177
+ finalSize: Buffer.byteLength(removeRegistryCode(result.code), "utf-8"),
178
+ };
179
+ }
180
+
181
+ return result;
182
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Simple CSS Minifier
3
+ *
4
+ * Minifies CSS without changing class names.
5
+ * Safe for use with existing templates.
6
+ */
7
+
8
+ export interface MinifyOptions {
9
+ verbose?: boolean;
10
+ }
11
+
12
+ export interface MinifyResult {
13
+ minified: string;
14
+ originalSize: number;
15
+ minifiedSize: number;
16
+ reduction: number;
17
+ }
18
+
19
+ /**
20
+ * Minify CSS content
21
+ */
22
+ export function minifyCSS(css: string): string {
23
+ return (
24
+ css
25
+ // Remove comments
26
+ .replace(/\/\*[\s\S]*?\*\//g, "")
27
+ // Remove newlines and multiple spaces
28
+ .replace(/\s+/g, " ")
29
+ // Remove space around { } : ; ,
30
+ .replace(/\s*([{};:,])\s*/g, "$1")
31
+ // Remove trailing semicolons before }
32
+ .replace(/;}/g, "}")
33
+ // Remove space after ( and before )
34
+ .replace(/\(\s+/g, "(")
35
+ .replace(/\s+\)/g, ")")
36
+ // Trim
37
+ .trim()
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Format file size
43
+ */
44
+ export function formatSize(bytes: number): string {
45
+ if (bytes < 1024) return `${bytes} B`;
46
+ return `${(bytes / 1024).toFixed(1)} KB`;
47
+ }
48
+
49
+ /**
50
+ * Minify CSS and return result with stats
51
+ */
52
+ export function minify(css: string, options: MinifyOptions = {}): MinifyResult {
53
+ const originalSize = Buffer.byteLength(css, "utf-8");
54
+ const minified = minifyCSS(css);
55
+ const minifiedSize = Buffer.byteLength(minified, "utf-8");
56
+ const reduction = ((1 - minifiedSize / originalSize) * 100);
57
+
58
+ if (options.verbose) {
59
+ console.error(
60
+ `Minified: ${formatSize(originalSize)} → ${formatSize(minifiedSize)} (${reduction.toFixed(1)}% reduction)`
61
+ );
62
+ }
63
+
64
+ return {
65
+ minified,
66
+ originalSize,
67
+ minifiedSize,
68
+ reduction,
69
+ };
70
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * CSS Co-occurrence Optimizer
3
+ * Re-exports from the standalone css-optimizer module
4
+ */
5
+
6
+ export * from "../css-optimizer/index.js";