@luna_ui/luna 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/dist/cli.mjs +1264 -27
  2. package/dist/css/index.d.ts +194 -0
  3. package/dist/css/index.js +721 -0
  4. package/dist/css/runtime.d.ts +92 -0
  5. package/dist/css/runtime.js +179 -0
  6. package/dist/index.js +1 -1
  7. package/dist/jsx-dev-runtime.js +1 -1
  8. package/dist/jsx-runtime.d.ts +5 -0
  9. package/dist/jsx-runtime.js +1 -1
  10. package/dist/src-CHiGeWfy.js +1 -0
  11. package/dist/vite-plugin.d.ts +122 -0
  12. package/dist/vite-plugin.js +1518 -0
  13. package/package.json +16 -2
  14. package/src/css/extract.ts +798 -0
  15. package/src/css/index.ts +10 -0
  16. package/src/css/inject.ts +205 -0
  17. package/src/css/inline.ts +182 -0
  18. package/src/css/minify.ts +70 -0
  19. package/src/css/optimizer.ts +6 -0
  20. package/src/css/runtime.ts +344 -0
  21. package/src/css-optimizer/README.md +353 -0
  22. package/src/css-optimizer/cooccurrence.ts +100 -0
  23. package/src/css-optimizer/core.ts +263 -0
  24. package/src/css-optimizer/extractors.ts +243 -0
  25. package/src/css-optimizer/hash.ts +54 -0
  26. package/src/css-optimizer/index.ts +129 -0
  27. package/src/css-optimizer/merge.ts +109 -0
  28. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  29. package/src/css-optimizer/parser.ts +120 -0
  30. package/src/css-optimizer/pattern.ts +171 -0
  31. package/src/css-optimizer/transformers.ts +301 -0
  32. package/src/css-optimizer/types.ts +128 -0
  33. package/src/event-utils.ts +227 -0
  34. package/src/index.ts +890 -0
  35. package/src/jsx-dev-runtime.ts +2 -0
  36. package/src/jsx-runtime.ts +398 -0
  37. package/src/vite-plugin.ts +718 -0
  38. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  39. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  40. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  41. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  42. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  43. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  44. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  45. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  46. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  50. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  51. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-empty-to-non-empty-transition-in-SVG-1.png +0 -0
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  58. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  59. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-renders-correctly-without-show--initial-items--1.png +0 -0
  60. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-with-context-renders-correctly-without-show-1.png +0 -0
  61. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-nested-components-with-context--forEach--and-show-1.png +0 -0
  62. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-inherit-context-from-Owner--fixed--1.png +0 -0
  63. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-work-together--context-uses-default--1.png +0 -0
  64. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  65. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  66. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  67. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  68. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  69. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  70. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  71. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  72. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  73. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  74. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  75. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-works-with-For-loop-items--component-body-style--1.png +0 -0
  76. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  77. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  78. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  79. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  80. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  81. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  82. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  83. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  84. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  85. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  117. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  118. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  119. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  120. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  121. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  122. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  123. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  124. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  125. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  137. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  138. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  139. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  140. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  141. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  142. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  143. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  144. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  145. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  147. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  148. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  149. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  150. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  151. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  152. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  153. package/tests/context.test.ts +118 -0
  154. package/tests/css-optimizer-extractors.test.ts +264 -0
  155. package/tests/css-optimizer-integration.test.ts +566 -0
  156. package/tests/css-optimizer-transformers.test.ts +301 -0
  157. package/tests/css-optimizer.test.ts +646 -0
  158. package/tests/css-runtime.bench.ts +442 -0
  159. package/tests/css-runtime.test.ts +342 -0
  160. package/tests/dom.test.ts +872 -0
  161. package/tests/integration.test.ts +405 -0
  162. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  163. package/tests/jsx-runtime.test.tsx +393 -0
  164. package/tests/lifecycle.test.ts +833 -0
  165. package/tests/move-before.bench.ts +304 -0
  166. package/tests/preact-signals-comparison.test.ts +1608 -0
  167. package/tests/resource.test.ts +160 -0
  168. package/tests/router.test.ts +117 -0
  169. package/tests/show-initial-mount-leak.test.tsx +182 -0
  170. package/tests/solidjs-api.test.ts +659 -0
  171. package/tests/static-perf.bench.ts +64 -0
  172. package/tests/store.test.ts +263 -0
  173. package/tests/tsx-syntax.test.tsx +404 -0
  174. package/dist/src-DGWY0NYx.js +0 -1
@@ -0,0 +1,442 @@
1
+ /**
2
+ * CSS Performance Benchmark
3
+ *
4
+ * Compares zero-runtime CSS (Luna's approach) vs runtime CSS-in-JS patterns.
5
+ *
6
+ * Key metrics:
7
+ * 1. Initial render with pre-generated CSS (static) vs runtime-generated CSS (dynamic)
8
+ * 2. Style updates: class switching vs inline style mutation
9
+ * 3. CSSOM manipulation overhead
10
+ *
11
+ * References:
12
+ * - https://calendar.perfplanet.com/2019/the-unseen-performance-costs-of-css-in-js-in-react-apps/
13
+ * - https://dev.to/srmagura/why-were-breaking-up-wiht-css-in-js-4g9b
14
+ */
15
+
16
+ import { bench, describe } from "vitest";
17
+
18
+ // =============================================================================
19
+ // Test Helpers
20
+ // =============================================================================
21
+
22
+ function createContainer(): HTMLDivElement {
23
+ const container = document.createElement("div");
24
+ document.body.appendChild(container);
25
+ return container;
26
+ }
27
+
28
+ function createStyleSheet(): { styleEl: HTMLStyleElement; sheet: CSSStyleSheet } {
29
+ const styleEl = document.createElement("style");
30
+ document.head.appendChild(styleEl);
31
+ return { styleEl, sheet: styleEl.sheet! };
32
+ }
33
+
34
+ // =============================================================================
35
+ // CSS Generation Helpers
36
+ // =============================================================================
37
+
38
+ /**
39
+ * Simulate Luna's zero-runtime approach:
40
+ * CSS is pre-generated at SSR time, only class names are applied at runtime
41
+ */
42
+ function generateStaticCSS(count: number): { css: string; classMap: Map<string, string> } {
43
+ const classMap = new Map<string, string>();
44
+ let css = "";
45
+
46
+ const properties = [
47
+ ["display", "flex"],
48
+ ["align-items", "center"],
49
+ ["justify-content", "space-between"],
50
+ ["padding", "1rem"],
51
+ ["margin", "0.5rem"],
52
+ ["background", "#f5f5f5"],
53
+ ["border-radius", "0.5rem"],
54
+ ["font-size", "1rem"],
55
+ ["color", "#333"],
56
+ ["box-shadow", "0 1px 3px rgba(0,0,0,0.1)"],
57
+ ];
58
+
59
+ for (let i = 0; i < count; i++) {
60
+ const [prop, val] = properties[i % properties.length];
61
+ const decl = `${prop}:${val}`;
62
+ if (!classMap.has(decl)) {
63
+ const className = `_${classMap.size.toString(36)}`;
64
+ classMap.set(decl, className);
65
+ css += `.${className}{${decl}}`;
66
+ }
67
+ }
68
+
69
+ return { css, classMap };
70
+ }
71
+
72
+ /**
73
+ * Simulate runtime CSS-in-JS:
74
+ * CSS is generated and inserted during component render
75
+ */
76
+ function runtimeGenerateAndInsert(sheet: CSSStyleSheet, property: string, value: string): string {
77
+ const className = `_r${Math.random().toString(36).slice(2, 8)}`;
78
+ const rule = `.${className}{${property}:${value}}`;
79
+ sheet.insertRule(rule, sheet.cssRules.length);
80
+ return className;
81
+ }
82
+
83
+ /**
84
+ * Simulate styled-components pattern:
85
+ * Hash-based class generation with style injection
86
+ */
87
+ function styledComponentsPattern(sheet: CSSStyleSheet, styles: Record<string, string>): string {
88
+ const hash = Object.entries(styles)
89
+ .map(([k, v]) => `${k}:${v}`)
90
+ .join(";");
91
+ const className = `sc-${hash.split("").reduce((a, c) => ((a << 5) - a + c.charCodeAt(0)) | 0, 0).toString(36)}`;
92
+
93
+ const rule = `.${className}{${Object.entries(styles).map(([k, v]) => `${k}:${v}`).join(";")}}`;
94
+ try {
95
+ sheet.insertRule(rule, sheet.cssRules.length);
96
+ } catch {
97
+ // Rule might already exist
98
+ }
99
+ return className;
100
+ }
101
+
102
+ // =============================================================================
103
+ // Benchmark: Initial Render
104
+ // =============================================================================
105
+
106
+ describe("Initial Render: Static vs Dynamic CSS", () => {
107
+ const ELEMENT_COUNT = 100;
108
+
109
+ describe(`${ELEMENT_COUNT} elements`, () => {
110
+ // Pre-generate CSS for static approach
111
+ const { classMap } = generateStaticCSS(ELEMENT_COUNT);
112
+ const classNames = Array.from(classMap.values());
113
+
114
+ bench("Static CSS (Luna zero-runtime)", () => {
115
+ const container = createContainer();
116
+ // CSS is already in <style>, just apply class names
117
+ const fragment = document.createDocumentFragment();
118
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
119
+ const div = document.createElement("div");
120
+ div.className = classNames[i % classNames.length];
121
+ fragment.appendChild(div);
122
+ }
123
+ container.appendChild(fragment);
124
+ container.remove();
125
+ });
126
+
127
+ bench("Runtime CSS-in-JS (insertRule per element)", () => {
128
+ const container = createContainer();
129
+ const { styleEl, sheet } = createStyleSheet();
130
+ const properties = [
131
+ ["display", "flex"],
132
+ ["padding", "1rem"],
133
+ ["margin", "0.5rem"],
134
+ ["background", "#f5f5f5"],
135
+ ["border-radius", "0.5rem"],
136
+ ];
137
+
138
+ const fragment = document.createDocumentFragment();
139
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
140
+ const [prop, val] = properties[i % properties.length];
141
+ const className = runtimeGenerateAndInsert(sheet, prop, val);
142
+ const div = document.createElement("div");
143
+ div.className = className;
144
+ fragment.appendChild(div);
145
+ }
146
+ container.appendChild(fragment);
147
+ container.remove();
148
+ styleEl.remove();
149
+ });
150
+
151
+ bench("Inline styles (no CSS classes)", () => {
152
+ const container = createContainer();
153
+ const properties = [
154
+ { display: "flex" },
155
+ { padding: "1rem" },
156
+ { margin: "0.5rem" },
157
+ { background: "#f5f5f5" },
158
+ { borderRadius: "0.5rem" },
159
+ ];
160
+
161
+ const fragment = document.createDocumentFragment();
162
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
163
+ const div = document.createElement("div");
164
+ Object.assign(div.style, properties[i % properties.length]);
165
+ fragment.appendChild(div);
166
+ }
167
+ container.appendChild(fragment);
168
+ container.remove();
169
+ });
170
+ });
171
+ });
172
+
173
+ // =============================================================================
174
+ // Benchmark: Style Updates
175
+ // =============================================================================
176
+
177
+ describe("Style Updates: Class Toggle vs Inline Mutation", () => {
178
+ const UPDATE_COUNT = 1000;
179
+
180
+ bench("Class toggle (zero-runtime pattern)", () => {
181
+ const container = createContainer();
182
+ const div = document.createElement("div");
183
+ div.className = "_a";
184
+ container.appendChild(div);
185
+
186
+ for (let i = 0; i < UPDATE_COUNT; i++) {
187
+ div.className = i % 2 === 0 ? "_a" : "_b";
188
+ }
189
+
190
+ container.remove();
191
+ });
192
+
193
+ bench("classList.toggle", () => {
194
+ const container = createContainer();
195
+ const div = document.createElement("div");
196
+ div.className = "_a";
197
+ container.appendChild(div);
198
+
199
+ for (let i = 0; i < UPDATE_COUNT; i++) {
200
+ div.classList.toggle("_active");
201
+ }
202
+
203
+ container.remove();
204
+ });
205
+
206
+ bench("Inline style mutation", () => {
207
+ const container = createContainer();
208
+ const div = document.createElement("div");
209
+ container.appendChild(div);
210
+
211
+ for (let i = 0; i < UPDATE_COUNT; i++) {
212
+ div.style.backgroundColor = i % 2 === 0 ? "#f5f5f5" : "#e5e5e5";
213
+ }
214
+
215
+ container.remove();
216
+ });
217
+
218
+ bench("style.cssText replacement", () => {
219
+ const container = createContainer();
220
+ const div = document.createElement("div");
221
+ container.appendChild(div);
222
+
223
+ for (let i = 0; i < UPDATE_COUNT; i++) {
224
+ div.style.cssText = i % 2 === 0 ? "background:#f5f5f5" : "background:#e5e5e5";
225
+ }
226
+
227
+ container.remove();
228
+ });
229
+ });
230
+
231
+ // =============================================================================
232
+ // Benchmark: CSSOM Operations
233
+ // =============================================================================
234
+
235
+ describe("CSSOM: insertRule vs innerHTML", () => {
236
+ const RULE_COUNT = 100;
237
+
238
+ bench("CSSStyleSheet.insertRule (one by one)", () => {
239
+ const style = document.createElement("style");
240
+ document.head.appendChild(style);
241
+ const sheet = style.sheet!;
242
+
243
+ for (let i = 0; i < RULE_COUNT; i++) {
244
+ sheet.insertRule(`._x${i}{color:#${i.toString(16).padStart(6, "0")}}`, sheet.cssRules.length);
245
+ }
246
+
247
+ style.remove();
248
+ });
249
+
250
+ bench("style.textContent (batch)", () => {
251
+ const style = document.createElement("style");
252
+ document.head.appendChild(style);
253
+
254
+ let css = "";
255
+ for (let i = 0; i < RULE_COUNT; i++) {
256
+ css += `._x${i}{color:#${i.toString(16).padStart(6, "0")}}`;
257
+ }
258
+ style.textContent = css;
259
+
260
+ style.remove();
261
+ });
262
+
263
+ bench("style.innerHTML (batch)", () => {
264
+ const style = document.createElement("style");
265
+ document.head.appendChild(style);
266
+
267
+ let css = "";
268
+ for (let i = 0; i < RULE_COUNT; i++) {
269
+ css += `._x${i}{color:#${i.toString(16).padStart(6, "0")}}`;
270
+ }
271
+ style.innerHTML = css;
272
+
273
+ style.remove();
274
+ });
275
+ });
276
+
277
+ // =============================================================================
278
+ // Benchmark: styled-components Pattern Simulation
279
+ // =============================================================================
280
+
281
+ describe("styled-components Pattern Overhead", () => {
282
+ const COMPONENT_COUNT = 50;
283
+
284
+ bench("Pre-computed class names (Luna)", () => {
285
+ const container = createContainer();
286
+ // Simulate: classes are computed at SSR, just apply them
287
+ const classes = ["_a _b _c", "_d _e _f", "_g _h _i", "_j _k _l", "_m _n _o"];
288
+
289
+ const fragment = document.createDocumentFragment();
290
+ for (let i = 0; i < COMPONENT_COUNT; i++) {
291
+ const div = document.createElement("div");
292
+ div.className = classes[i % classes.length];
293
+ fragment.appendChild(div);
294
+ }
295
+ container.appendChild(fragment);
296
+ container.remove();
297
+ });
298
+
299
+ bench("Hash + insertRule per component (styled-components)", () => {
300
+ const container = createContainer();
301
+ const { styleEl, sheet } = createStyleSheet();
302
+ const styleVariants = [
303
+ { display: "flex", padding: "1rem", background: "#fff" },
304
+ { display: "grid", padding: "0.5rem", background: "#f5f5f5" },
305
+ { display: "block", padding: "2rem", background: "#e5e5e5" },
306
+ { display: "inline-flex", padding: "0.25rem", background: "#ddd" },
307
+ { display: "flex", padding: "1.5rem", background: "#ccc" },
308
+ ];
309
+
310
+ const fragment = document.createDocumentFragment();
311
+ for (let i = 0; i < COMPONENT_COUNT; i++) {
312
+ const styles = styleVariants[i % styleVariants.length];
313
+ const className = styledComponentsPattern(sheet, styles);
314
+ const div = document.createElement("div");
315
+ div.className = className;
316
+ fragment.appendChild(div);
317
+ }
318
+ container.appendChild(fragment);
319
+ container.remove();
320
+ styleEl.remove();
321
+ });
322
+ });
323
+
324
+ // =============================================================================
325
+ // Benchmark: Large Scale Rendering
326
+ // =============================================================================
327
+
328
+ describe("Large Scale: 1000 Elements", () => {
329
+ const ELEMENT_COUNT = 1000;
330
+
331
+ bench("Static CSS + class names only", () => {
332
+ const container = createContainer();
333
+ const classes = ["_a", "_b", "_c", "_d", "_e", "_f", "_g", "_h", "_i", "_j"];
334
+
335
+ const fragment = document.createDocumentFragment();
336
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
337
+ const div = document.createElement("div");
338
+ div.className = classes[i % classes.length];
339
+ fragment.appendChild(div);
340
+ }
341
+ container.appendChild(fragment);
342
+ container.remove();
343
+ });
344
+
345
+ bench("Runtime insertRule + class names", () => {
346
+ const container = createContainer();
347
+ const { styleEl, sheet } = createStyleSheet();
348
+ const props = ["padding", "margin", "color", "background", "display"];
349
+ const vals = ["1rem", "0.5rem", "#333", "#fff", "flex"];
350
+
351
+ const fragment = document.createDocumentFragment();
352
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
353
+ const className = runtimeGenerateAndInsert(
354
+ sheet,
355
+ props[i % props.length],
356
+ vals[i % vals.length]
357
+ );
358
+ const div = document.createElement("div");
359
+ div.className = className;
360
+ fragment.appendChild(div);
361
+ }
362
+ container.appendChild(fragment);
363
+ container.remove();
364
+ styleEl.remove();
365
+ });
366
+
367
+ bench("Inline styles only", () => {
368
+ const container = createContainer();
369
+ const styles = [
370
+ { padding: "1rem" },
371
+ { margin: "0.5rem" },
372
+ { color: "#333" },
373
+ { background: "#fff" },
374
+ { display: "flex" },
375
+ ];
376
+
377
+ const fragment = document.createDocumentFragment();
378
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
379
+ const div = document.createElement("div");
380
+ Object.assign(div.style, styles[i % styles.length]);
381
+ fragment.appendChild(div);
382
+ }
383
+ container.appendChild(fragment);
384
+ container.remove();
385
+ });
386
+ });
387
+
388
+ // =============================================================================
389
+ // Benchmark: Deduplication Efficiency
390
+ // =============================================================================
391
+
392
+ describe("Deduplication: Repeated Styles", () => {
393
+ const ELEMENT_COUNT = 200;
394
+ const UNIQUE_STYLES = 10; // Only 10 unique styles, repeated
395
+
396
+ bench("Luna: deduplicated (10 classes for 200 elements)", () => {
397
+ const container = createContainer();
398
+ // Pre-computed: same style = same class
399
+ const classes: string[] = [];
400
+ for (let i = 0; i < UNIQUE_STYLES; i++) {
401
+ classes.push(`_${i.toString(36)}`);
402
+ }
403
+
404
+ const fragment = document.createDocumentFragment();
405
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
406
+ const div = document.createElement("div");
407
+ div.className = classes[i % UNIQUE_STYLES];
408
+ fragment.appendChild(div);
409
+ }
410
+ container.appendChild(fragment);
411
+ container.remove();
412
+ });
413
+
414
+ bench("No dedup: 200 insertRule calls", () => {
415
+ const container = createContainer();
416
+ const { styleEl, sheet } = createStyleSheet();
417
+ const props = [
418
+ "display:flex",
419
+ "padding:1rem",
420
+ "margin:0.5rem",
421
+ "background:#fff",
422
+ "color:#333",
423
+ "border-radius:4px",
424
+ "font-size:1rem",
425
+ "line-height:1.5",
426
+ "gap:0.5rem",
427
+ "justify-content:center",
428
+ ];
429
+
430
+ const fragment = document.createDocumentFragment();
431
+ for (let i = 0; i < ELEMENT_COUNT; i++) {
432
+ const className = `_r${i}`;
433
+ sheet.insertRule(`.${className}{${props[i % UNIQUE_STYLES]}}`, sheet.cssRules.length);
434
+ const div = document.createElement("div");
435
+ div.className = className;
436
+ fragment.appendChild(div);
437
+ }
438
+ container.appendChild(fragment);
439
+ container.remove();
440
+ styleEl.remove();
441
+ });
442
+ });