@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.
- 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.js +1 -1
- package/dist/{src-BDdxGwvq.js → src-CHiGeWfy.js} +1 -1
- 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/src/css/index.ts
ADDED
|
@@ -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
|
+
}
|