@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.
- package/dist/cli.mjs +1264 -27
- package/dist/css/index.d.ts +194 -0
- package/dist/css/index.js +721 -0
- package/dist/css/runtime.d.ts +92 -0
- package/dist/css/runtime.js +179 -0
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +5 -0
- package/dist/jsx-runtime.js +1 -1
- package/dist/src-CHiGeWfy.js +1 -0
- package/dist/vite-plugin.d.ts +122 -0
- package/dist/vite-plugin.js +1518 -0
- package/package.json +16 -2
- package/src/css/extract.ts +798 -0
- package/src/css/index.ts +10 -0
- package/src/css/inject.ts +205 -0
- package/src/css/inline.ts +182 -0
- package/src/css/minify.ts +70 -0
- package/src/css/optimizer.ts +6 -0
- package/src/css/runtime.ts +344 -0
- package/src/css-optimizer/README.md +353 -0
- package/src/css-optimizer/cooccurrence.ts +100 -0
- package/src/css-optimizer/core.ts +263 -0
- package/src/css-optimizer/extractors.ts +243 -0
- package/src/css-optimizer/hash.ts +54 -0
- package/src/css-optimizer/index.ts +129 -0
- package/src/css-optimizer/merge.ts +109 -0
- package/src/css-optimizer/moonbit-analyzer.ts +210 -0
- package/src/css-optimizer/parser.ts +120 -0
- package/src/css-optimizer/pattern.ts +171 -0
- package/src/css-optimizer/transformers.ts +301 -0
- package/src/css-optimizer/types.ts +128 -0
- package/src/event-utils.ts +227 -0
- package/src/index.ts +890 -0
- package/src/jsx-dev-runtime.ts +2 -0
- package/src/jsx-runtime.ts +398 -0
- package/src/vite-plugin.ts +718 -0
- package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
- 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
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
- 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
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
- package/tests/context.test.ts +118 -0
- package/tests/css-optimizer-extractors.test.ts +264 -0
- package/tests/css-optimizer-integration.test.ts +566 -0
- package/tests/css-optimizer-transformers.test.ts +301 -0
- package/tests/css-optimizer.test.ts +646 -0
- package/tests/css-runtime.bench.ts +442 -0
- package/tests/css-runtime.test.ts +342 -0
- package/tests/dom.test.ts +872 -0
- package/tests/integration.test.ts +405 -0
- package/tests/issue-5-for-infinite-loop.test.ts +516 -0
- package/tests/jsx-runtime.test.tsx +393 -0
- package/tests/lifecycle.test.ts +833 -0
- package/tests/move-before.bench.ts +304 -0
- package/tests/preact-signals-comparison.test.ts +1608 -0
- package/tests/resource.test.ts +160 -0
- package/tests/router.test.ts +117 -0
- package/tests/show-initial-mount-leak.test.tsx +182 -0
- package/tests/solidjs-api.test.ts +659 -0
- package/tests/static-perf.bench.ts +64 -0
- package/tests/store.test.ts +263 -0
- package/tests/tsx-syntax.test.tsx +404 -0
- package/dist/src-DGWY0NYx.js +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS Co-occurrence Optimizer
|
|
3
|
+
*
|
|
4
|
+
* A standalone, framework-agnostic library for optimizing CSS by merging
|
|
5
|
+
* frequently co-occurring classes. Works with HTML, React, Svelte, and more.
|
|
6
|
+
*
|
|
7
|
+
* ## Architecture
|
|
8
|
+
*
|
|
9
|
+
* - **Core**: Pure functions for pattern analysis (no framework dependency)
|
|
10
|
+
* - **Extractors**: Pluggable class extraction (HTML, JSX, Svelte, etc.)
|
|
11
|
+
* - **Transformers**: Pluggable output transformation
|
|
12
|
+
*
|
|
13
|
+
* ## Quick Start (Framework-agnostic)
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import {
|
|
17
|
+
* optimizeCore,
|
|
18
|
+
* htmlExtractor,
|
|
19
|
+
* htmlTransformer
|
|
20
|
+
* } from "@luna_ui/luna/css-optimizer";
|
|
21
|
+
*
|
|
22
|
+
* // 1. Extract class usages
|
|
23
|
+
* const usages = htmlExtractor.extract(html);
|
|
24
|
+
*
|
|
25
|
+
* // 2. Optimize
|
|
26
|
+
* const classToDecl = new Map([["_flex", "display:flex"], ...]);
|
|
27
|
+
* const result = optimizeCore(usages, css, classToDecl);
|
|
28
|
+
*
|
|
29
|
+
* // 3. Transform output
|
|
30
|
+
* const optimizedHtml = htmlTransformer.transform(html, result.mergeMap);
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* ## Legacy API (Luna-specific convenience)
|
|
34
|
+
*
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { optimizeCss, optimizeHtml } from "@luna_ui/luna/css-optimizer";
|
|
37
|
+
*
|
|
38
|
+
* const result = optimizeCss(css, html, mapping, { minFrequency: 2 });
|
|
39
|
+
* const optimizedHtml = optimizeHtml(html, result.mergeMap);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// Types
|
|
44
|
+
export type {
|
|
45
|
+
ClassUsage,
|
|
46
|
+
CoOccurrence,
|
|
47
|
+
MergePattern,
|
|
48
|
+
OptimizeResult,
|
|
49
|
+
OptimizeOptions,
|
|
50
|
+
ClassNameGenerator,
|
|
51
|
+
CssRule,
|
|
52
|
+
// MoonBit analyzer types
|
|
53
|
+
ClassCooccurrence,
|
|
54
|
+
AnalyzerWarning,
|
|
55
|
+
} from "./types.js";
|
|
56
|
+
|
|
57
|
+
// Core (framework-agnostic)
|
|
58
|
+
export {
|
|
59
|
+
optimizeCore,
|
|
60
|
+
applyMergeToClasses,
|
|
61
|
+
type CoreOptimizeOptions,
|
|
62
|
+
type CoreOptimizeResult,
|
|
63
|
+
} from "./core.js";
|
|
64
|
+
|
|
65
|
+
// Extractors
|
|
66
|
+
export {
|
|
67
|
+
HtmlExtractor,
|
|
68
|
+
JsxExtractor,
|
|
69
|
+
SvelteExtractor,
|
|
70
|
+
MultiExtractor,
|
|
71
|
+
htmlExtractor,
|
|
72
|
+
jsxExtractor,
|
|
73
|
+
svelteExtractor,
|
|
74
|
+
multiExtractor,
|
|
75
|
+
type ClassExtractor,
|
|
76
|
+
type ExtractorOptions,
|
|
77
|
+
} from "./extractors.js";
|
|
78
|
+
|
|
79
|
+
// Transformers
|
|
80
|
+
export {
|
|
81
|
+
HtmlTransformer,
|
|
82
|
+
JsxTransformer,
|
|
83
|
+
SvelteTransformer,
|
|
84
|
+
MultiTransformer,
|
|
85
|
+
htmlTransformer,
|
|
86
|
+
jsxTransformer,
|
|
87
|
+
svelteTransformer,
|
|
88
|
+
multiTransformer,
|
|
89
|
+
type ClassTransformer,
|
|
90
|
+
type TransformerOptions,
|
|
91
|
+
} from "./transformers.js";
|
|
92
|
+
|
|
93
|
+
// Hash utilities
|
|
94
|
+
export { djb2Hash, toBase36, hashClassName, hashMergedClassName } from "./hash.js";
|
|
95
|
+
|
|
96
|
+
// Parsing utilities (for advanced use)
|
|
97
|
+
export {
|
|
98
|
+
extractClassUsages,
|
|
99
|
+
parseCssRules,
|
|
100
|
+
buildClassToDeclarationMap,
|
|
101
|
+
extractUniqueClasses,
|
|
102
|
+
} from "./parser.js";
|
|
103
|
+
|
|
104
|
+
// Co-occurrence analysis (for advanced use)
|
|
105
|
+
export {
|
|
106
|
+
buildCooccurrenceMatrix,
|
|
107
|
+
matrixToCooccurrences,
|
|
108
|
+
getTopCooccurrences,
|
|
109
|
+
buildAdjacencyList,
|
|
110
|
+
} from "./cooccurrence.js";
|
|
111
|
+
|
|
112
|
+
// Pattern mining (for advanced use)
|
|
113
|
+
export {
|
|
114
|
+
findFrequentPatterns,
|
|
115
|
+
removeSubsumedPatterns,
|
|
116
|
+
groupByClassSet,
|
|
117
|
+
} from "./pattern.js";
|
|
118
|
+
|
|
119
|
+
// Legacy API (convenience wrappers)
|
|
120
|
+
export { optimizeCss, optimizeHtml, optimize } from "./merge.js";
|
|
121
|
+
|
|
122
|
+
// MoonBit static analyzer (for Luna projects)
|
|
123
|
+
export {
|
|
124
|
+
analyzeFile,
|
|
125
|
+
analyzeDirectory,
|
|
126
|
+
convertToOptimizerInput,
|
|
127
|
+
type MoonBitAnalysisResult,
|
|
128
|
+
type ConvertedResult,
|
|
129
|
+
} from "./moonbit-analyzer.js";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS class merging utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides Luna-specific convenience wrappers around the
|
|
5
|
+
* framework-agnostic core optimizer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ClassUsage, OptimizeResult, OptimizeOptions } from "./types.js";
|
|
9
|
+
import { optimizeCore } from "./core.js";
|
|
10
|
+
import { htmlExtractor } from "./extractors.js";
|
|
11
|
+
import { htmlTransformer } from "./transformers.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Framework-agnostic optimize function
|
|
15
|
+
*
|
|
16
|
+
* This is the recommended API for non-Luna projects.
|
|
17
|
+
* It takes pre-extracted class usages and a class-to-declaration map.
|
|
18
|
+
*
|
|
19
|
+
* @param usages - Class usages extracted from your source files
|
|
20
|
+
* @param css - Original CSS content
|
|
21
|
+
* @param classToDeclaration - Map from class name to CSS declaration
|
|
22
|
+
* @param options - Optimization options
|
|
23
|
+
*/
|
|
24
|
+
export function optimize(
|
|
25
|
+
usages: ClassUsage[],
|
|
26
|
+
css: string,
|
|
27
|
+
classToDeclaration: Map<string, string>,
|
|
28
|
+
options: OptimizeOptions = {}
|
|
29
|
+
): OptimizeResult {
|
|
30
|
+
return optimizeCore(usages, css, classToDeclaration, options);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optimize CSS by merging co-occurring classes (Luna convenience wrapper)
|
|
35
|
+
*
|
|
36
|
+
* This is a convenience function that:
|
|
37
|
+
* 1. Extracts class usages from HTML using HtmlExtractor
|
|
38
|
+
* 2. Converts Luna's declaration mapping format
|
|
39
|
+
* 3. Calls the core optimizer
|
|
40
|
+
*
|
|
41
|
+
* For non-Luna projects, use the `optimize` function instead.
|
|
42
|
+
*
|
|
43
|
+
* @param css - Original CSS content
|
|
44
|
+
* @param html - HTML content to analyze
|
|
45
|
+
* @param declarationMapping - Luna's declaration -> class name mapping
|
|
46
|
+
* @param options - Optimization options
|
|
47
|
+
*/
|
|
48
|
+
export function optimizeCss(
|
|
49
|
+
css: string,
|
|
50
|
+
html: string,
|
|
51
|
+
declarationMapping: Record<string, string>,
|
|
52
|
+
options: OptimizeOptions = {}
|
|
53
|
+
): OptimizeResult {
|
|
54
|
+
const { classPrefix = "_", verbose = false } = options;
|
|
55
|
+
|
|
56
|
+
const log = (msg: string) => {
|
|
57
|
+
if (verbose) {
|
|
58
|
+
console.log(`[optimizer] ${msg}`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Step 1: Extract class usages using the HTML extractor
|
|
63
|
+
const usages = htmlExtractor.extract(html, {
|
|
64
|
+
classPrefix,
|
|
65
|
+
source: "html",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
log(`Found ${usages.length} class usage sites`);
|
|
69
|
+
|
|
70
|
+
if (usages.length === 0) {
|
|
71
|
+
return {
|
|
72
|
+
css,
|
|
73
|
+
mergeMap: new Map(),
|
|
74
|
+
patterns: [],
|
|
75
|
+
stats: {
|
|
76
|
+
originalClasses: Object.keys(declarationMapping).length,
|
|
77
|
+
mergedPatterns: 0,
|
|
78
|
+
estimatedBytesSaved: 0,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Step 2: Build class -> declaration map (reverse of Luna's mapping)
|
|
84
|
+
const classToDecl = new Map<string, string>();
|
|
85
|
+
for (const [decl, cls] of Object.entries(declarationMapping)) {
|
|
86
|
+
classToDecl.set(cls, decl);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Step 3: Call core optimizer
|
|
90
|
+
return optimizeCore(usages, css, classToDecl, options);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Apply optimization to HTML by replacing class combinations (Luna convenience wrapper)
|
|
95
|
+
*
|
|
96
|
+
* This is a convenience function that uses HtmlTransformer.
|
|
97
|
+
* For non-Luna projects, use the transformer directly.
|
|
98
|
+
*
|
|
99
|
+
* @param html - HTML content to transform
|
|
100
|
+
* @param mergeMap - Merge map from optimizeCss result
|
|
101
|
+
* @param classPrefix - Class prefix (default: "_")
|
|
102
|
+
*/
|
|
103
|
+
export function optimizeHtml(
|
|
104
|
+
html: string,
|
|
105
|
+
mergeMap: Map<string, string>,
|
|
106
|
+
classPrefix = "_"
|
|
107
|
+
): string {
|
|
108
|
+
return htmlTransformer.transform(html, mergeMap, { classPrefix });
|
|
109
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MoonBit Static Analyzer Integration
|
|
3
|
+
*
|
|
4
|
+
* Calls the MoonBit-based CSS static analyzer to extract class co-occurrences
|
|
5
|
+
* from MoonBit source files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import type { ClassCooccurrence, AnalyzerWarning, ClassUsage } from "./types.js";
|
|
12
|
+
|
|
13
|
+
// Lazy-loaded MoonBit analyzer module
|
|
14
|
+
let analyzerModule: { analyze_file_json: (source: string, file: string) => string } | null = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the path to the MoonBit analyzer JS module
|
|
18
|
+
*/
|
|
19
|
+
function getAnalyzerPath(): string {
|
|
20
|
+
// When running from dist, the analyzer is relative to the package
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
|
|
23
|
+
// Try multiple possible locations
|
|
24
|
+
const candidates = [
|
|
25
|
+
// Development: from dist/ to target/ (bundled cli.mjs)
|
|
26
|
+
path.resolve(__dirname, "../../../target/js/release/build/luna/css/analyzer/analyzer.js"),
|
|
27
|
+
// Development: from src/css-optimizer/ to target/
|
|
28
|
+
path.resolve(__dirname, "../../../../target/js/release/build/luna/css/analyzer/analyzer.js"),
|
|
29
|
+
// Installed package
|
|
30
|
+
path.resolve(__dirname, "../moonbit/analyzer.js"),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (fs.existsSync(candidate)) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new Error(
|
|
40
|
+
`MoonBit analyzer not found. Tried:\n${candidates.join("\n")}\nRun 'moon build --target js src/luna/css/analyzer' first.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load the MoonBit analyzer module
|
|
46
|
+
*/
|
|
47
|
+
async function loadAnalyzer(): Promise<typeof analyzerModule> {
|
|
48
|
+
if (analyzerModule) {
|
|
49
|
+
return analyzerModule;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const analyzerPath = getAnalyzerPath();
|
|
53
|
+
analyzerModule = await import(analyzerPath);
|
|
54
|
+
return analyzerModule;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Result from analyzing a single file
|
|
59
|
+
*/
|
|
60
|
+
export interface MoonBitAnalysisResult {
|
|
61
|
+
cooccurrences: ClassCooccurrence[];
|
|
62
|
+
warnings: AnalyzerWarning[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Analyze a single MoonBit source file
|
|
67
|
+
*/
|
|
68
|
+
export async function analyzeFile(
|
|
69
|
+
source: string,
|
|
70
|
+
filePath: string
|
|
71
|
+
): Promise<MoonBitAnalysisResult> {
|
|
72
|
+
const analyzer = await loadAnalyzer();
|
|
73
|
+
if (!analyzer) {
|
|
74
|
+
throw new Error("Failed to load MoonBit analyzer");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const jsonResult = analyzer.analyze_file_json(source, filePath);
|
|
78
|
+
return JSON.parse(jsonResult);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Analyze all .mbt files in a directory
|
|
83
|
+
*/
|
|
84
|
+
export async function analyzeDirectory(
|
|
85
|
+
dir: string,
|
|
86
|
+
options: { recursive?: boolean } = {}
|
|
87
|
+
): Promise<MoonBitAnalysisResult> {
|
|
88
|
+
const { recursive = true } = options;
|
|
89
|
+
const allCooccurrences: ClassCooccurrence[] = [];
|
|
90
|
+
const allWarnings: AnalyzerWarning[] = [];
|
|
91
|
+
|
|
92
|
+
const files = findMbtFiles(dir, recursive);
|
|
93
|
+
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
const source = fs.readFileSync(file, "utf-8");
|
|
96
|
+
const result = await analyzeFile(source, file);
|
|
97
|
+
allCooccurrences.push(...result.cooccurrences);
|
|
98
|
+
allWarnings.push(...result.warnings);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
cooccurrences: allCooccurrences,
|
|
103
|
+
warnings: allWarnings,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Find all .mbt files in a directory
|
|
109
|
+
*/
|
|
110
|
+
function findMbtFiles(dir: string, recursive: boolean): string[] {
|
|
111
|
+
const files: string[] = [];
|
|
112
|
+
|
|
113
|
+
function walk(currentDir: string) {
|
|
114
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
115
|
+
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
118
|
+
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
// Skip common non-source directories
|
|
121
|
+
if (
|
|
122
|
+
entry.name === "node_modules" ||
|
|
123
|
+
entry.name === "target" ||
|
|
124
|
+
entry.name === ".git" ||
|
|
125
|
+
entry.name === ".mooncakes"
|
|
126
|
+
) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (recursive) {
|
|
131
|
+
walk(fullPath);
|
|
132
|
+
}
|
|
133
|
+
} else if (entry.isFile() && entry.name.endsWith(".mbt")) {
|
|
134
|
+
// Skip test files
|
|
135
|
+
if (!entry.name.endsWith("_test.mbt")) {
|
|
136
|
+
files.push(fullPath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
walk(dir);
|
|
143
|
+
return files;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convert MoonBit analysis result to css-optimizer input format
|
|
148
|
+
*/
|
|
149
|
+
export interface ConvertedResult {
|
|
150
|
+
/** Class usages in css-optimizer format */
|
|
151
|
+
usages: ClassUsage[];
|
|
152
|
+
/** Map from class name to CSS declaration */
|
|
153
|
+
classToDeclaration: Map<string, string>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Convert MoonBit analyzer output to css-optimizer format
|
|
158
|
+
*
|
|
159
|
+
* @param result - MoonBit analysis result
|
|
160
|
+
* @param hashFn - Function to generate class name from declaration
|
|
161
|
+
*/
|
|
162
|
+
export function convertToOptimizerInput(
|
|
163
|
+
result: MoonBitAnalysisResult,
|
|
164
|
+
hashFn: (decl: string) => string
|
|
165
|
+
): ConvertedResult {
|
|
166
|
+
const classToDeclaration = new Map<string, string>();
|
|
167
|
+
const usages: ClassUsage[] = [];
|
|
168
|
+
|
|
169
|
+
for (const co of result.cooccurrences) {
|
|
170
|
+
// Only process static patterns (can be safely optimized)
|
|
171
|
+
if (!co.isStatic) continue;
|
|
172
|
+
|
|
173
|
+
const classes: string[] = [];
|
|
174
|
+
|
|
175
|
+
for (const decl of co.classes) {
|
|
176
|
+
const className = hashFn(decl);
|
|
177
|
+
classToDeclaration.set(className, decl);
|
|
178
|
+
classes.push(className);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
usages.push({
|
|
182
|
+
classes,
|
|
183
|
+
source: `${co.file}:${co.line}`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { usages, classToDeclaration };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* CLI entry point for testing
|
|
192
|
+
*/
|
|
193
|
+
export async function main(args: string[]): Promise<void> {
|
|
194
|
+
const dir = args[0] || ".";
|
|
195
|
+
|
|
196
|
+
console.error(`Analyzing MoonBit files in: ${dir}`);
|
|
197
|
+
|
|
198
|
+
const result = await analyzeDirectory(dir);
|
|
199
|
+
|
|
200
|
+
console.log(JSON.stringify(result, null, 2));
|
|
201
|
+
|
|
202
|
+
if (result.warnings.length > 0) {
|
|
203
|
+
console.error(`\nWarnings: ${result.warnings.length}`);
|
|
204
|
+
for (const w of result.warnings) {
|
|
205
|
+
console.error(` ${w.file}:${w.line} - ${w.kind}: ${w.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.error(`\nFound ${result.cooccurrences.length} class co-occurrences`);
|
|
210
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML and CSS parsing utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ClassUsage, CssRule } from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pattern to match class attributes in HTML
|
|
9
|
+
*/
|
|
10
|
+
const CLASS_ATTR_PATTERN = /class\s*=\s*"([^"]+)"/g;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pattern to match CSS rules
|
|
14
|
+
*/
|
|
15
|
+
const CSS_RULE_PATTERN = /\.([a-zA-Z_][a-zA-Z0-9_-]*)\s*\{([^}]+)\}/g;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extract class usages from HTML content
|
|
19
|
+
* @param html - HTML content
|
|
20
|
+
* @param source - Source identifier for debugging
|
|
21
|
+
* @param classPrefix - Only extract classes starting with this prefix
|
|
22
|
+
*/
|
|
23
|
+
export function extractClassUsages(
|
|
24
|
+
html: string,
|
|
25
|
+
source = "html",
|
|
26
|
+
classPrefix = "_"
|
|
27
|
+
): ClassUsage[] {
|
|
28
|
+
const usages: ClassUsage[] = [];
|
|
29
|
+
let match: RegExpExecArray | null;
|
|
30
|
+
|
|
31
|
+
CLASS_ATTR_PATTERN.lastIndex = 0;
|
|
32
|
+
while ((match = CLASS_ATTR_PATTERN.exec(html)) !== null) {
|
|
33
|
+
const classValue = match[1].trim();
|
|
34
|
+
if (!classValue) continue;
|
|
35
|
+
|
|
36
|
+
// Split by whitespace and filter by prefix
|
|
37
|
+
const classes = classValue
|
|
38
|
+
.split(/\s+/)
|
|
39
|
+
.filter((c) => c.startsWith(classPrefix) && c.length > classPrefix.length);
|
|
40
|
+
|
|
41
|
+
// Only consider elements with 2+ matching classes
|
|
42
|
+
if (classes.length >= 2) {
|
|
43
|
+
// Sort for consistent ordering
|
|
44
|
+
classes.sort();
|
|
45
|
+
usages.push({ classes, source: `${source}:${match.index}` });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return usages;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse CSS rules from CSS content
|
|
54
|
+
* @param css - CSS content
|
|
55
|
+
*/
|
|
56
|
+
export function parseCssRules(css: string): CssRule[] {
|
|
57
|
+
const rules: CssRule[] = [];
|
|
58
|
+
let match: RegExpExecArray | null;
|
|
59
|
+
|
|
60
|
+
CSS_RULE_PATTERN.lastIndex = 0;
|
|
61
|
+
while ((match = CSS_RULE_PATTERN.exec(css)) !== null) {
|
|
62
|
+
rules.push({
|
|
63
|
+
selector: match[1],
|
|
64
|
+
declarations: match[2].trim(),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return rules;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build a mapping from class name to CSS declaration
|
|
73
|
+
* @param css - CSS content
|
|
74
|
+
* @param classPrefix - Class prefix filter
|
|
75
|
+
*/
|
|
76
|
+
export function buildClassToDeclarationMap(
|
|
77
|
+
css: string,
|
|
78
|
+
classPrefix = "_"
|
|
79
|
+
): Map<string, string> {
|
|
80
|
+
const map = new Map<string, string>();
|
|
81
|
+
const rules = parseCssRules(css);
|
|
82
|
+
|
|
83
|
+
for (const rule of rules) {
|
|
84
|
+
if (rule.selector.startsWith(classPrefix)) {
|
|
85
|
+
// Normalize declarations (remove extra whitespace)
|
|
86
|
+
const normalized = rule.declarations
|
|
87
|
+
.split(";")
|
|
88
|
+
.map((d) => d.trim())
|
|
89
|
+
.filter((d) => d)
|
|
90
|
+
.join(";");
|
|
91
|
+
map.set(rule.selector, normalized);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return map;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Extract all unique classes from HTML
|
|
100
|
+
* @param html - HTML content
|
|
101
|
+
* @param classPrefix - Class prefix filter
|
|
102
|
+
*/
|
|
103
|
+
export function extractUniqueClasses(html: string, classPrefix = "_"): Set<string> {
|
|
104
|
+
const classes = new Set<string>();
|
|
105
|
+
let match: RegExpExecArray | null;
|
|
106
|
+
|
|
107
|
+
CLASS_ATTR_PATTERN.lastIndex = 0;
|
|
108
|
+
while ((match = CLASS_ATTR_PATTERN.exec(html)) !== null) {
|
|
109
|
+
const classValue = match[1].trim();
|
|
110
|
+
if (!classValue) continue;
|
|
111
|
+
|
|
112
|
+
for (const cls of classValue.split(/\s+/)) {
|
|
113
|
+
if (cls.startsWith(classPrefix) && cls.length > classPrefix.length) {
|
|
114
|
+
classes.add(cls);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return classes;
|
|
120
|
+
}
|