@useavalon/avalon 0.1.11 → 0.1.13

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 (141) hide show
  1. package/README.md +54 -54
  2. package/mod.ts +302 -302
  3. package/package.json +49 -26
  4. package/src/build/integration-bundler-plugin.ts +116 -116
  5. package/src/build/integration-config.ts +168 -168
  6. package/src/build/integration-detection-plugin.ts +117 -117
  7. package/src/build/integration-resolver-plugin.ts +90 -90
  8. package/src/build/island-manifest.ts +269 -269
  9. package/src/build/island-types-generator.ts +476 -476
  10. package/src/build/mdx-island-transform.ts +464 -464
  11. package/src/build/mdx-plugin.ts +98 -98
  12. package/src/build/page-island-transform.ts +598 -598
  13. package/src/build/prop-extractors/index.ts +21 -21
  14. package/src/build/prop-extractors/lit.ts +140 -140
  15. package/src/build/prop-extractors/qwik.ts +16 -16
  16. package/src/build/prop-extractors/solid.ts +125 -125
  17. package/src/build/prop-extractors/svelte.ts +194 -194
  18. package/src/build/prop-extractors/vue.ts +111 -111
  19. package/src/build/sidecar-file-manager.ts +104 -104
  20. package/src/build/sidecar-renderer.ts +30 -30
  21. package/src/client/adapters/index.ts +21 -13
  22. package/src/client/components.ts +35 -35
  23. package/src/client/css-hmr-handler.ts +344 -344
  24. package/src/client/framework-adapter.ts +462 -462
  25. package/src/client/hmr-coordinator.ts +396 -396
  26. package/src/client/hmr-error-overlay.js +533 -533
  27. package/src/client/main.js +824 -816
  28. package/src/client/types/framework-runtime.d.ts +68 -68
  29. package/src/client/types/vite-hmr.d.ts +46 -46
  30. package/src/client/types/vite-virtual-modules.d.ts +70 -60
  31. package/src/components/Image.tsx +123 -123
  32. package/src/components/IslandErrorBoundary.tsx +145 -145
  33. package/src/components/LayoutDataErrorBoundary.tsx +141 -141
  34. package/src/components/LayoutErrorBoundary.tsx +127 -127
  35. package/src/components/PersistentIsland.tsx +52 -52
  36. package/src/components/StreamingErrorBoundary.tsx +233 -233
  37. package/src/components/StreamingLayout.tsx +538 -538
  38. package/src/core/components/component-analyzer.ts +192 -192
  39. package/src/core/components/component-detection.ts +508 -508
  40. package/src/core/components/enhanced-framework-detector.ts +500 -500
  41. package/src/core/components/framework-registry.ts +563 -563
  42. package/src/core/content/mdx-processor.ts +46 -46
  43. package/src/core/integrations/index.ts +19 -19
  44. package/src/core/integrations/loader.ts +125 -125
  45. package/src/core/integrations/registry.ts +175 -175
  46. package/src/core/islands/island-persistence.ts +325 -325
  47. package/src/core/islands/island-state-serializer.ts +258 -258
  48. package/src/core/islands/persistent-island-context.tsx +80 -80
  49. package/src/core/islands/use-persistent-state.ts +68 -68
  50. package/src/core/layout/enhanced-layout-resolver.ts +322 -322
  51. package/src/core/layout/layout-cache-manager.ts +485 -485
  52. package/src/core/layout/layout-composer.ts +357 -357
  53. package/src/core/layout/layout-data-loader.ts +516 -516
  54. package/src/core/layout/layout-discovery.ts +243 -243
  55. package/src/core/layout/layout-matcher.ts +299 -299
  56. package/src/core/layout/layout-types.ts +110 -110
  57. package/src/core/modules/framework-module-resolver.ts +273 -273
  58. package/src/islands/component-analysis.ts +213 -213
  59. package/src/islands/css-utils.ts +565 -565
  60. package/src/islands/discovery/index.ts +80 -80
  61. package/src/islands/discovery/registry.ts +340 -340
  62. package/src/islands/discovery/resolver.ts +477 -477
  63. package/src/islands/discovery/scanner.ts +386 -386
  64. package/src/islands/discovery/types.ts +117 -117
  65. package/src/islands/discovery/validator.ts +544 -544
  66. package/src/islands/discovery/watcher.ts +368 -368
  67. package/src/islands/framework-detection.ts +428 -428
  68. package/src/islands/integration-loader.ts +490 -490
  69. package/src/islands/island.tsx +565 -565
  70. package/src/islands/render-cache.ts +550 -550
  71. package/src/islands/types.ts +80 -80
  72. package/src/islands/universal-css-collector.ts +157 -157
  73. package/src/islands/universal-head-collector.ts +137 -137
  74. package/src/layout-system.d.ts +592 -592
  75. package/src/layout-system.ts +218 -218
  76. package/src/middleware/discovery.ts +268 -268
  77. package/src/middleware/executor.ts +315 -315
  78. package/src/middleware/index.ts +76 -76
  79. package/src/middleware/types.ts +99 -99
  80. package/src/nitro/build-config.ts +575 -575
  81. package/src/nitro/config.ts +483 -483
  82. package/src/nitro/error-handler.ts +636 -636
  83. package/src/nitro/index.ts +173 -173
  84. package/src/nitro/island-manifest.ts +584 -584
  85. package/src/nitro/middleware-adapter.ts +260 -260
  86. package/src/nitro/renderer.ts +1471 -1471
  87. package/src/nitro/route-discovery.ts +439 -439
  88. package/src/nitro/types.ts +321 -321
  89. package/src/render/collect-css.ts +198 -198
  90. package/src/render/error-pages.ts +79 -79
  91. package/src/render/isolated-ssr-renderer.ts +654 -654
  92. package/src/render/ssr.ts +1030 -1030
  93. package/src/schemas/api.ts +30 -30
  94. package/src/schemas/core.ts +64 -64
  95. package/src/schemas/index.ts +212 -212
  96. package/src/schemas/layout.ts +279 -279
  97. package/src/schemas/routing/index.ts +38 -38
  98. package/src/schemas/routing.ts +376 -376
  99. package/src/types/as-island.ts +20 -20
  100. package/src/types/image.d.ts +106 -106
  101. package/src/types/index.d.ts +22 -22
  102. package/src/types/island-jsx.d.ts +33 -33
  103. package/src/types/island-prop.d.ts +20 -20
  104. package/src/types/layout.ts +285 -285
  105. package/src/types/mdx.d.ts +6 -6
  106. package/src/types/routing.ts +555 -555
  107. package/src/types/types.ts +5 -5
  108. package/src/types/urlpattern.d.ts +49 -49
  109. package/src/types/vite-env.d.ts +11 -11
  110. package/src/utils/dev-logger.ts +299 -299
  111. package/src/utils/fs.ts +151 -151
  112. package/src/vite-plugin/auto-discover.ts +551 -551
  113. package/src/vite-plugin/config.ts +266 -266
  114. package/src/vite-plugin/errors.ts +127 -127
  115. package/src/vite-plugin/image-optimization.ts +156 -156
  116. package/src/vite-plugin/integration-activator.ts +126 -126
  117. package/src/vite-plugin/island-sidecar-plugin.ts +176 -176
  118. package/src/vite-plugin/module-discovery.ts +189 -189
  119. package/src/vite-plugin/nitro-integration.ts +1354 -1354
  120. package/src/vite-plugin/plugin.ts +403 -409
  121. package/src/vite-plugin/types.ts +327 -327
  122. package/src/vite-plugin/validation.ts +228 -228
  123. package/src/client/adapters/index.js +0 -12
  124. package/src/client/adapters/lit-adapter.js +0 -467
  125. package/src/client/adapters/lit-adapter.ts +0 -654
  126. package/src/client/adapters/preact-adapter.js +0 -223
  127. package/src/client/adapters/preact-adapter.ts +0 -331
  128. package/src/client/adapters/qwik-adapter.js +0 -259
  129. package/src/client/adapters/qwik-adapter.ts +0 -345
  130. package/src/client/adapters/react-adapter.js +0 -220
  131. package/src/client/adapters/react-adapter.ts +0 -353
  132. package/src/client/adapters/solid-adapter.js +0 -295
  133. package/src/client/adapters/solid-adapter.ts +0 -451
  134. package/src/client/adapters/svelte-adapter.js +0 -368
  135. package/src/client/adapters/svelte-adapter.ts +0 -524
  136. package/src/client/adapters/vue-adapter.js +0 -278
  137. package/src/client/adapters/vue-adapter.ts +0 -467
  138. package/src/client/components.js +0 -23
  139. package/src/client/css-hmr-handler.js +0 -263
  140. package/src/client/framework-adapter.js +0 -283
  141. package/src/client/hmr-coordinator.js +0 -274
@@ -1,565 +1,565 @@
1
- /**
2
- * CSS Utilities Module
3
- *
4
- * This module contains all CSS-related utilities for the Island system:
5
- * - Svelte SSR CSS collection and management
6
- * - CSS scoping for component isolation
7
- * - CSS optimization and minification
8
- * - CSS parsing and deduplication
9
- */
10
-
11
- // Enhanced global CSS collector for SSR with scoping support
12
- declare global {
13
- var __svelteSSRCSS: Map<string, SvelteSSRCSSEntry> | undefined;
14
- }
15
-
16
- interface SvelteSSRCSSEntry {
17
- css: string;
18
- scopeId: string;
19
- src: string;
20
- isGlobal: boolean;
21
- timestamp: number;
22
- }
23
-
24
- // Initialize enhanced global CSS collector
25
- if (typeof globalThis !== "undefined" && !globalThis.__svelteSSRCSS) {
26
- globalThis.__svelteSSRCSS = new Map();
27
- }
28
-
29
- /**
30
- * Add CSS to the global Svelte SSR collection with scoping information
31
- */
32
- export function addSvelteSSRCSS(
33
- css: string,
34
- scopeId: string,
35
- src: string,
36
- isGlobal = false,
37
- ): void {
38
- if (!globalThis.__svelteSSRCSS) {
39
- globalThis.__svelteSSRCSS = new Map();
40
- }
41
-
42
- const entry: SvelteSSRCSSEntry = {
43
- css: css.trim(),
44
- scopeId,
45
- src,
46
- isGlobal,
47
- timestamp: Date.now(),
48
- };
49
-
50
- // Use scopeId as key to prevent duplicates from the same component
51
- globalThis.__svelteSSRCSS.set(scopeId, entry);
52
- }
53
-
54
- /**
55
- * Get collected Svelte SSR CSS with enhanced processing and deduplication
56
- * Enhanced version with better CSS management and document head injection support
57
- */
58
- export function getSvelteSSRCSS(clear = false) {
59
- if (!globalThis.__svelteSSRCSS || globalThis.__svelteSSRCSS.size === 0) {
60
- return "";
61
- }
62
-
63
- const entries = Array.from(globalThis.__svelteSSRCSS.values());
64
-
65
- // Sort entries: global styles first, then component styles by timestamp
66
- entries.sort((a, b) => {
67
- if (a.isGlobal && !b.isGlobal) return -1;
68
- if (!a.isGlobal && b.isGlobal) return 1;
69
- return a.timestamp - b.timestamp;
70
- });
71
-
72
- // Separate global and component-scoped CSS for better organization
73
- const globalEntries = entries.filter((entry) => entry.isGlobal);
74
- const componentEntries = entries.filter((entry) => !entry.isGlobal);
75
-
76
- // Process global CSS
77
- const globalCSS = globalEntries.map((entry) => {
78
- const comment = `/* Global CSS from: ${entry.src} */`;
79
- return `${comment}\n${entry.css}`;
80
- }).join("\n\n");
81
-
82
- // Process component-scoped CSS
83
- const componentCSS = componentEntries.map((entry) => {
84
- const comment = `/* Component CSS: ${entry.src} (${entry.scopeId}) */`;
85
- return `${comment}\n${entry.css}`;
86
- }).join("\n\n");
87
-
88
- // Combine in proper order: global first, then component-specific
89
- const combinedCSS = [globalCSS, componentCSS].filter((css) => css.trim())
90
- .join("\n\n");
91
-
92
- // Apply enhanced CSS optimization and deduplication
93
- const optimizedCSS = optimizeSvelteSSRCSS(combinedCSS);
94
-
95
- if (clear) {
96
- globalThis.__svelteSSRCSS.clear();
97
- }
98
-
99
- return optimizedCSS;
100
- }
101
-
102
- /**
103
- * Get CSS formatted for document head injection during SSR
104
- * Returns CSS wrapped in appropriate style tags with metadata
105
- */
106
- export function getSvelteSSRCSSForHead(clear = false) {
107
- const css = getSvelteSSRCSS(clear);
108
-
109
- if (!css.trim()) {
110
- return "";
111
- }
112
-
113
- // Wrap in style tag with metadata for identification and debugging
114
- const timestamp = new Date().toISOString();
115
- const styleTag =
116
- `<style data-svelte-ssr="true" data-generated="${timestamp}">\n${css}\n</style>`;
117
-
118
- return styleTag;
119
- }
120
-
121
- /**
122
- * Get CSS statistics for debugging and monitoring
123
- */
124
- export function getSvelteSSRCSSStats() {
125
- if (!globalThis.__svelteSSRCSS || globalThis.__svelteSSRCSS.size === 0) {
126
- return {
127
- totalComponents: 0,
128
- globalComponents: 0,
129
- scopedComponents: 0,
130
- totalCSSSize: 0,
131
- averageCSSSize: 0,
132
- oldestTimestamp: 0,
133
- newestTimestamp: 0,
134
- };
135
- }
136
-
137
- const entries = Array.from(globalThis.__svelteSSRCSS.values());
138
- const globalEntries = entries.filter((entry) => entry.isGlobal);
139
- const scopedEntries = entries.filter((entry) => !entry.isGlobal);
140
- const totalCSSSize = entries.reduce(
141
- (sum, entry) => sum + entry.css.length,
142
- 0,
143
- );
144
- const timestamps = entries.map((entry) => entry.timestamp);
145
-
146
- return {
147
- totalComponents: entries.length,
148
- globalComponents: globalEntries.length,
149
- scopedComponents: scopedEntries.length,
150
- totalCSSSize,
151
- averageCSSSize: entries.length > 0
152
- ? Math.round(totalCSSSize / entries.length)
153
- : 0,
154
- oldestTimestamp: Math.min(...timestamps),
155
- newestTimestamp: Math.max(...timestamps),
156
- };
157
- }
158
-
159
- /**
160
- * Get CSS for specific component scope
161
- */
162
- export function getSvelteComponentCSS(scopeId: string) {
163
- if (!globalThis.__svelteSSRCSS) {
164
- return null;
165
- }
166
-
167
- const entry = globalThis.__svelteSSRCSS.get(scopeId);
168
- return entry ? entry.css : null;
169
- }
170
-
171
- /**
172
- * Clear CSS for specific component scope
173
- */
174
- export function clearSvelteComponentCSS(scopeId: string) {
175
- if (!globalThis.__svelteSSRCSS) {
176
- return false;
177
- }
178
-
179
- const deleted = globalThis.__svelteSSRCSS.delete(scopeId);
180
- if (deleted) {
181
- // CSS cleared for component scope
182
- }
183
- return deleted;
184
- }
185
-
186
- /**
187
- * Optimize collected Svelte SSR CSS by removing duplicates and minifying
188
- * Enhanced version with better deduplication and cross-component optimization
189
- */
190
- function optimizeSvelteSSRCSS(css: string) {
191
- try {
192
- // Use the enhanced CSS optimization functions
193
- const optimizedCSS = optimizeComponentCSS(css);
194
-
195
- // Additional optimizations specific to SSR CSS collection
196
- const finalCSS = optimizeSSRCSSCollection(optimizedCSS);
197
-
198
- return finalCSS;
199
- } catch (error) {
200
- console.warn(`⚠️ Failed to optimize Svelte SSR CSS:`, error);
201
- return css;
202
- }
203
- }
204
-
205
- /**
206
- * Apply SSR-specific CSS optimizations across multiple components
207
- */
208
- function optimizeSSRCSSCollection(css: string) {
209
- try {
210
- // Remove duplicate comments
211
- const withoutDuplicateComments = removeDuplicateComments(css);
212
-
213
- // Optimize cross-component CSS patterns
214
- const crossOptimized = optimizeCrossComponentCSS(withoutDuplicateComments);
215
-
216
- // Apply final formatting based on environment
217
- const isDev = process.env.NODE_ENV !== "production";
218
- if (!isDev) {
219
- return minifyCSS(crossOptimized);
220
- }
221
-
222
- // Keep readable format in development with proper indentation
223
- return formatDevelopmentCSS(crossOptimized);
224
- } catch (error) {
225
- console.warn(`⚠️ Failed to apply SSR CSS collection optimizations:`, error);
226
- return css;
227
- }
228
- }
229
-
230
- /**
231
- * Remove duplicate CSS comments while preserving important ones
232
- */
233
- function removeDuplicateComments(css: string) {
234
- const seenComments = new Set<string>();
235
-
236
- return css.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, (comment) => {
237
- // Keep important comments (those with specific markers)
238
- if (
239
- comment.includes("!important") || comment.includes("@preserve") ||
240
- comment.includes("license")
241
- ) {
242
- return comment;
243
- }
244
-
245
- // Remove duplicate comments
246
- if (seenComments.has(comment)) {
247
- return "";
248
- }
249
-
250
- seenComments.add(comment);
251
- return comment;
252
- });
253
- }
254
-
255
- /**
256
- * Optimize CSS patterns that appear across multiple components
257
- */
258
- function optimizeCrossComponentCSS(css: string) {
259
- // This could be extended to merge similar rules across components
260
- // For now, just clean up whitespace and formatting
261
- return css
262
- .replace(/\n\s*\n\s*\n/g, "\n\n") // Collapse multiple empty lines
263
- .replace(/^\s*\n/gm, "") // Remove empty lines at start of sections
264
- .trim();
265
- }
266
-
267
- /**
268
- * Format CSS for development with proper indentation and spacing
269
- */
270
- function formatDevelopmentCSS(css: string) {
271
- return css
272
- .replace(/\{/g, " {\n ")
273
- .replace(/;/g, ";\n ")
274
- .replace(/\}/g, "\n}\n")
275
- .replace(/\n {2}\n/g, "\n")
276
- .replace(/\n\n+/g, "\n\n")
277
- .trim();
278
- }
279
-
280
- /**
281
- * Apply CSS scoping to selectors with advanced logic
282
- * Enhanced version with better selector parsing and scoping rules
283
- */
284
- export function applyCSSScoping(cssContent: string, scopeId: string) {
285
- return cssContent.replace(/([^{}]+){/g, (match, selector) => {
286
- const trimmedSelector = selector.trim();
287
-
288
- // Skip at-rules (@media, @keyframes, @import, etc.)
289
- if (trimmedSelector.startsWith("@")) {
290
- return match;
291
- }
292
-
293
- // Skip selectors that already have scoping
294
- if (trimmedSelector.includes(`[data-${scopeId}]`)) {
295
- return match;
296
- }
297
-
298
- // Skip :global() selectors (remove the wrapper)
299
- if (trimmedSelector.includes(":global(")) {
300
- const globalSelector = trimmedSelector.replace(
301
- /:global\(([^)]+)\)/g,
302
- "$1",
303
- );
304
- return `${globalSelector} {`;
305
- }
306
-
307
- // Apply scoping to each selector in a comma-separated list
308
- const scopedSelectors = trimmedSelector
309
- .split(",")
310
- .map((sel: string) => applySelectorScoping(sel.trim(), scopeId))
311
- .join(", ");
312
-
313
- return `${scopedSelectors} {`;
314
- });
315
- }
316
-
317
- /**
318
- * Apply scoping to a single CSS selector with comprehensive logic
319
- */
320
- export function applySelectorScoping(selector: string, scopeId: string) {
321
- // Handle empty or invalid selectors
322
- if (!selector || selector.length === 0) {
323
- return selector;
324
- }
325
-
326
- // Skip already scoped selectors
327
- if (selector.includes(`[data-${scopeId}]`)) {
328
- return selector;
329
- }
330
-
331
- // Handle complex selectors with combinators (>, +, ~, space)
332
- const combinatorRegex = /(\s*[>+~]\s*|\s+)/;
333
- const parts = selector.split(combinatorRegex);
334
-
335
- if (parts.length > 1) {
336
- // Complex selector with combinators - scope the first part only
337
- const firstPart = parts[0].trim();
338
- const rest = parts.slice(1).join("");
339
- return applySingleSelectorScoping(firstPart, scopeId) + rest;
340
- }
341
-
342
- // Simple selector
343
- return applySingleSelectorScoping(selector, scopeId);
344
- }
345
-
346
- /**
347
- * Apply scoping to a single, simple CSS selector
348
- */
349
- function applySingleSelectorScoping(selector: string, scopeId: string) {
350
- // Handle pseudo-elements (::before, ::after)
351
- const pseudoElementMatch = selector.match(/^([^:]+)(::.*)?$/);
352
- if (pseudoElementMatch) {
353
- const [, baseSelector, pseudoElement] = pseudoElementMatch;
354
- return `${baseSelector}[data-${scopeId}]${pseudoElement || ""}`;
355
- }
356
-
357
- // Handle pseudo-classes (:hover, :focus, :nth-child, etc.)
358
- const pseudoClassMatch = selector.match(/^([^:]+)(:.*)?$/);
359
- if (pseudoClassMatch) {
360
- const [, baseSelector, pseudoClass] = pseudoClassMatch;
361
- return `${baseSelector}[data-${scopeId}]${pseudoClass || ""}`;
362
- }
363
-
364
- // Simple selector without pseudo-classes/elements
365
- return `${selector}[data-${scopeId}]`;
366
- }
367
-
368
- /**
369
- * Optimize component CSS by removing duplicates and applying minification
370
- * Enhanced version with better deduplication and optimization strategies
371
- */
372
- export function optimizeComponentCSS(css: string) {
373
- try {
374
- // Parse CSS into rules with better handling
375
- const rules = parseCSRules(css);
376
- const optimizedRules = deduplicateCSRules(rules);
377
-
378
- // Reconstruct CSS
379
- const optimizedCSS = optimizedRules.join("\n");
380
-
381
- // Apply environment-specific optimizations
382
- const isDev = process.env.NODE_ENV !== "production";
383
- if (!isDev) {
384
- return minifyCSS(optimizedCSS);
385
- }
386
-
387
- return optimizedCSS;
388
- } catch (error) {
389
- console.warn(`⚠️ Failed to optimize component CSS:`, error);
390
- return css;
391
- }
392
- }
393
-
394
- /**
395
- * Parse CSS into individual rules with proper handling of nested structures
396
- */
397
- function parseCSRules(css: string) {
398
- const rules: string[] = [];
399
- let currentRule = "";
400
- let braceDepth = 0;
401
- let inString = false;
402
- let stringChar = "";
403
-
404
- for (let i = 0; i < css.length; i++) {
405
- const char = css[i];
406
- const prevChar = i > 0 ? css[i - 1] : "";
407
-
408
- // Handle string literals
409
- if ((char === '"' || char === "'") && prevChar !== "\\") {
410
- if (!inString) {
411
- inString = true;
412
- stringChar = char;
413
- } else if (char === stringChar) {
414
- inString = false;
415
- stringChar = "";
416
- }
417
- }
418
-
419
- if (!inString) {
420
- if (char === "{") {
421
- braceDepth++;
422
- } else if (char === "}") {
423
- braceDepth--;
424
-
425
- if (braceDepth === 0) {
426
- // End of a complete rule
427
- currentRule += char;
428
- const trimmedRule = currentRule.trim();
429
- if (trimmedRule && !isEmptyRule(trimmedRule)) {
430
- rules.push(trimmedRule);
431
- }
432
- currentRule = "";
433
- continue;
434
- }
435
- }
436
- }
437
-
438
- currentRule += char;
439
- }
440
-
441
- // Handle any remaining content
442
- if (currentRule.trim()) {
443
- rules.push(currentRule.trim());
444
- }
445
-
446
- return rules;
447
- }
448
-
449
- /**
450
- * Deduplicate CSS rules while preserving order and handling specificity
451
- */
452
- function deduplicateCSRules(rules: string[]) {
453
- const seenRules = new Map<string, { rule: string; index: number }>();
454
- const result: string[] = [];
455
-
456
- for (let i = 0; i < rules.length; i++) {
457
- const rule = rules[i];
458
- const ruleKey = extractRuleKey(rule);
459
-
460
- if (ruleKey) {
461
- const existing = seenRules.get(ruleKey);
462
- if (existing) {
463
- // Replace earlier rule with later one (cascade order)
464
- result[existing.index] = rule;
465
- seenRules.set(ruleKey, { rule, index: existing.index });
466
- } else {
467
- // New rule
468
- const index = result.length;
469
- result.push(rule);
470
- seenRules.set(ruleKey, { rule, index });
471
- }
472
- } else {
473
- // Non-standard rule (at-rules, etc.) - keep as-is
474
- result.push(rule);
475
- }
476
- }
477
-
478
- return result.filter((rule) => rule !== null);
479
- }
480
-
481
- /**
482
- * Extract a key for rule deduplication (selector + property combination)
483
- */
484
- function extractRuleKey(rule: string) {
485
- const match = rule.match(/^([^{]+)\{([^}]+)\}/);
486
- if (!match) return null;
487
-
488
- const selector = match[1].trim();
489
- const declarations = match[2].trim();
490
-
491
- // For deduplication, we consider rules with the same selector as duplicates
492
- // Later rules will override earlier ones (CSS cascade)
493
- return selector;
494
- }
495
-
496
- /**
497
- * Check if a CSS rule is effectively empty
498
- */
499
- function isEmptyRule(rule: string) {
500
- const match = rule.match(/^[^{]+\{([^}]*)\}/);
501
- if (!match) return true;
502
-
503
- const declarations = match[1].trim();
504
- return declarations.length === 0 || declarations === ";";
505
- }
506
-
507
- /**
508
- * Minify CSS for production builds
509
- */
510
- export function minifyCSS(css: string) {
511
- return css
512
- // Remove comments
513
- .replace(/\/\*[\s\S]*?\*\//g, "")
514
- // Remove unnecessary whitespace
515
- .replace(/\s+/g, " ")
516
- // Remove whitespace around braces and semicolons
517
- .replace(/\s*{\s*/g, "{")
518
- .replace(/\s*}\s*/g, "}")
519
- .replace(/\s*;\s*/g, ";")
520
- .replace(/;\s*}/g, "}")
521
- // Remove trailing semicolons before closing braces
522
- .replace(/;}/g, "}")
523
- // Trim
524
- .trim();
525
- }
526
-
527
- /**
528
- * Generate consistent scope ID for any component framework
529
- * Provides unified scoping across Vue, Svelte, and other frameworks
530
- */
531
- export function generateComponentScopeId(
532
- src: string,
533
- framework: string = "component",
534
- ) {
535
- const cleanPath = src
536
- .replace(/^\/+/, "") // Remove leading slashes
537
- .replace(/\.(svelte|tsx|jsx|vue|ts|js)$/, "") // Remove file extensions
538
- .replace(/[^a-zA-Z0-9\/]/g, "-") // Replace special chars with hyphens
539
- .replace(/\/+/g, "-") // Replace path separators with hyphens
540
- .replace(/-+/g, "-") // Collapse multiple hyphens
541
- .replace(/^-|-$/g, "") // Remove leading/trailing hyphens
542
- .toLowerCase();
543
-
544
- // Add framework prefix and hash for collision resistance
545
- const hash = simpleHash(src);
546
- return `${framework}-${cleanPath}-${hash}`;
547
- }
548
-
549
- /**
550
- * Simple hash function for generating consistent short hashes
551
- */
552
- export function simpleHash(str: string) {
553
- let hash = 0;
554
- for (let i = 0; i < str.length; i++) {
555
- const char = str.charCodeAt(i);
556
- hash = ((hash << 5) - hash) + char;
557
- hash = hash & hash; // Convert to 32-bit integer
558
- }
559
- return Math.abs(hash).toString(36).substring(0, 6);
560
- }
561
-
562
- // Note: processSvelteSSRCSS, extractRawCSSFromHead, and applySvelteComponentScoping
563
- // were removed as they are not used anywhere in the codebase.
564
- // They were originally intended for Svelte SSR CSS processing but are superseded
565
- // by the Svelte 5 render() function which handles CSS differently.
1
+ /**
2
+ * CSS Utilities Module
3
+ *
4
+ * This module contains all CSS-related utilities for the Island system:
5
+ * - Svelte SSR CSS collection and management
6
+ * - CSS scoping for component isolation
7
+ * - CSS optimization and minification
8
+ * - CSS parsing and deduplication
9
+ */
10
+
11
+ // Enhanced global CSS collector for SSR with scoping support
12
+ declare global {
13
+ var __svelteSSRCSS: Map<string, SvelteSSRCSSEntry> | undefined;
14
+ }
15
+
16
+ interface SvelteSSRCSSEntry {
17
+ css: string;
18
+ scopeId: string;
19
+ src: string;
20
+ isGlobal: boolean;
21
+ timestamp: number;
22
+ }
23
+
24
+ // Initialize enhanced global CSS collector
25
+ if (typeof globalThis !== "undefined" && !globalThis.__svelteSSRCSS) {
26
+ globalThis.__svelteSSRCSS = new Map();
27
+ }
28
+
29
+ /**
30
+ * Add CSS to the global Svelte SSR collection with scoping information
31
+ */
32
+ export function addSvelteSSRCSS(
33
+ css: string,
34
+ scopeId: string,
35
+ src: string,
36
+ isGlobal = false,
37
+ ): void {
38
+ if (!globalThis.__svelteSSRCSS) {
39
+ globalThis.__svelteSSRCSS = new Map();
40
+ }
41
+
42
+ const entry: SvelteSSRCSSEntry = {
43
+ css: css.trim(),
44
+ scopeId,
45
+ src,
46
+ isGlobal,
47
+ timestamp: Date.now(),
48
+ };
49
+
50
+ // Use scopeId as key to prevent duplicates from the same component
51
+ globalThis.__svelteSSRCSS.set(scopeId, entry);
52
+ }
53
+
54
+ /**
55
+ * Get collected Svelte SSR CSS with enhanced processing and deduplication
56
+ * Enhanced version with better CSS management and document head injection support
57
+ */
58
+ export function getSvelteSSRCSS(clear = false) {
59
+ if (!globalThis.__svelteSSRCSS || globalThis.__svelteSSRCSS.size === 0) {
60
+ return "";
61
+ }
62
+
63
+ const entries = Array.from(globalThis.__svelteSSRCSS.values());
64
+
65
+ // Sort entries: global styles first, then component styles by timestamp
66
+ entries.sort((a, b) => {
67
+ if (a.isGlobal && !b.isGlobal) return -1;
68
+ if (!a.isGlobal && b.isGlobal) return 1;
69
+ return a.timestamp - b.timestamp;
70
+ });
71
+
72
+ // Separate global and component-scoped CSS for better organization
73
+ const globalEntries = entries.filter((entry) => entry.isGlobal);
74
+ const componentEntries = entries.filter((entry) => !entry.isGlobal);
75
+
76
+ // Process global CSS
77
+ const globalCSS = globalEntries.map((entry) => {
78
+ const comment = `/* Global CSS from: ${entry.src} */`;
79
+ return `${comment}\n${entry.css}`;
80
+ }).join("\n\n");
81
+
82
+ // Process component-scoped CSS
83
+ const componentCSS = componentEntries.map((entry) => {
84
+ const comment = `/* Component CSS: ${entry.src} (${entry.scopeId}) */`;
85
+ return `${comment}\n${entry.css}`;
86
+ }).join("\n\n");
87
+
88
+ // Combine in proper order: global first, then component-specific
89
+ const combinedCSS = [globalCSS, componentCSS].filter((css) => css.trim())
90
+ .join("\n\n");
91
+
92
+ // Apply enhanced CSS optimization and deduplication
93
+ const optimizedCSS = optimizeSvelteSSRCSS(combinedCSS);
94
+
95
+ if (clear) {
96
+ globalThis.__svelteSSRCSS.clear();
97
+ }
98
+
99
+ return optimizedCSS;
100
+ }
101
+
102
+ /**
103
+ * Get CSS formatted for document head injection during SSR
104
+ * Returns CSS wrapped in appropriate style tags with metadata
105
+ */
106
+ export function getSvelteSSRCSSForHead(clear = false) {
107
+ const css = getSvelteSSRCSS(clear);
108
+
109
+ if (!css.trim()) {
110
+ return "";
111
+ }
112
+
113
+ // Wrap in style tag with metadata for identification and debugging
114
+ const timestamp = new Date().toISOString();
115
+ const styleTag =
116
+ `<style data-svelte-ssr="true" data-generated="${timestamp}">\n${css}\n</style>`;
117
+
118
+ return styleTag;
119
+ }
120
+
121
+ /**
122
+ * Get CSS statistics for debugging and monitoring
123
+ */
124
+ export function getSvelteSSRCSSStats() {
125
+ if (!globalThis.__svelteSSRCSS || globalThis.__svelteSSRCSS.size === 0) {
126
+ return {
127
+ totalComponents: 0,
128
+ globalComponents: 0,
129
+ scopedComponents: 0,
130
+ totalCSSSize: 0,
131
+ averageCSSSize: 0,
132
+ oldestTimestamp: 0,
133
+ newestTimestamp: 0,
134
+ };
135
+ }
136
+
137
+ const entries = Array.from(globalThis.__svelteSSRCSS.values());
138
+ const globalEntries = entries.filter((entry) => entry.isGlobal);
139
+ const scopedEntries = entries.filter((entry) => !entry.isGlobal);
140
+ const totalCSSSize = entries.reduce(
141
+ (sum, entry) => sum + entry.css.length,
142
+ 0,
143
+ );
144
+ const timestamps = entries.map((entry) => entry.timestamp);
145
+
146
+ return {
147
+ totalComponents: entries.length,
148
+ globalComponents: globalEntries.length,
149
+ scopedComponents: scopedEntries.length,
150
+ totalCSSSize,
151
+ averageCSSSize: entries.length > 0
152
+ ? Math.round(totalCSSSize / entries.length)
153
+ : 0,
154
+ oldestTimestamp: Math.min(...timestamps),
155
+ newestTimestamp: Math.max(...timestamps),
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Get CSS for specific component scope
161
+ */
162
+ export function getSvelteComponentCSS(scopeId: string) {
163
+ if (!globalThis.__svelteSSRCSS) {
164
+ return null;
165
+ }
166
+
167
+ const entry = globalThis.__svelteSSRCSS.get(scopeId);
168
+ return entry ? entry.css : null;
169
+ }
170
+
171
+ /**
172
+ * Clear CSS for specific component scope
173
+ */
174
+ export function clearSvelteComponentCSS(scopeId: string) {
175
+ if (!globalThis.__svelteSSRCSS) {
176
+ return false;
177
+ }
178
+
179
+ const deleted = globalThis.__svelteSSRCSS.delete(scopeId);
180
+ if (deleted) {
181
+ // CSS cleared for component scope
182
+ }
183
+ return deleted;
184
+ }
185
+
186
+ /**
187
+ * Optimize collected Svelte SSR CSS by removing duplicates and minifying
188
+ * Enhanced version with better deduplication and cross-component optimization
189
+ */
190
+ function optimizeSvelteSSRCSS(css: string) {
191
+ try {
192
+ // Use the enhanced CSS optimization functions
193
+ const optimizedCSS = optimizeComponentCSS(css);
194
+
195
+ // Additional optimizations specific to SSR CSS collection
196
+ const finalCSS = optimizeSSRCSSCollection(optimizedCSS);
197
+
198
+ return finalCSS;
199
+ } catch (error) {
200
+ console.warn(`⚠️ Failed to optimize Svelte SSR CSS:`, error);
201
+ return css;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Apply SSR-specific CSS optimizations across multiple components
207
+ */
208
+ function optimizeSSRCSSCollection(css: string) {
209
+ try {
210
+ // Remove duplicate comments
211
+ const withoutDuplicateComments = removeDuplicateComments(css);
212
+
213
+ // Optimize cross-component CSS patterns
214
+ const crossOptimized = optimizeCrossComponentCSS(withoutDuplicateComments);
215
+
216
+ // Apply final formatting based on environment
217
+ const isDev = process.env.NODE_ENV !== "production";
218
+ if (!isDev) {
219
+ return minifyCSS(crossOptimized);
220
+ }
221
+
222
+ // Keep readable format in development with proper indentation
223
+ return formatDevelopmentCSS(crossOptimized);
224
+ } catch (error) {
225
+ console.warn(`⚠️ Failed to apply SSR CSS collection optimizations:`, error);
226
+ return css;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Remove duplicate CSS comments while preserving important ones
232
+ */
233
+ function removeDuplicateComments(css: string) {
234
+ const seenComments = new Set<string>();
235
+
236
+ return css.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, (comment) => {
237
+ // Keep important comments (those with specific markers)
238
+ if (
239
+ comment.includes("!important") || comment.includes("@preserve") ||
240
+ comment.includes("license")
241
+ ) {
242
+ return comment;
243
+ }
244
+
245
+ // Remove duplicate comments
246
+ if (seenComments.has(comment)) {
247
+ return "";
248
+ }
249
+
250
+ seenComments.add(comment);
251
+ return comment;
252
+ });
253
+ }
254
+
255
+ /**
256
+ * Optimize CSS patterns that appear across multiple components
257
+ */
258
+ function optimizeCrossComponentCSS(css: string) {
259
+ // This could be extended to merge similar rules across components
260
+ // For now, just clean up whitespace and formatting
261
+ return css
262
+ .replace(/\n\s*\n\s*\n/g, "\n\n") // Collapse multiple empty lines
263
+ .replace(/^\s*\n/gm, "") // Remove empty lines at start of sections
264
+ .trim();
265
+ }
266
+
267
+ /**
268
+ * Format CSS for development with proper indentation and spacing
269
+ */
270
+ function formatDevelopmentCSS(css: string) {
271
+ return css
272
+ .replace(/\{/g, " {\n ")
273
+ .replace(/;/g, ";\n ")
274
+ .replace(/\}/g, "\n}\n")
275
+ .replace(/\n {2}\n/g, "\n")
276
+ .replace(/\n\n+/g, "\n\n")
277
+ .trim();
278
+ }
279
+
280
+ /**
281
+ * Apply CSS scoping to selectors with advanced logic
282
+ * Enhanced version with better selector parsing and scoping rules
283
+ */
284
+ export function applyCSSScoping(cssContent: string, scopeId: string) {
285
+ return cssContent.replace(/([^{}]+){/g, (match, selector) => {
286
+ const trimmedSelector = selector.trim();
287
+
288
+ // Skip at-rules (@media, @keyframes, @import, etc.)
289
+ if (trimmedSelector.startsWith("@")) {
290
+ return match;
291
+ }
292
+
293
+ // Skip selectors that already have scoping
294
+ if (trimmedSelector.includes(`[data-${scopeId}]`)) {
295
+ return match;
296
+ }
297
+
298
+ // Skip :global() selectors (remove the wrapper)
299
+ if (trimmedSelector.includes(":global(")) {
300
+ const globalSelector = trimmedSelector.replace(
301
+ /:global\(([^)]+)\)/g,
302
+ "$1",
303
+ );
304
+ return `${globalSelector} {`;
305
+ }
306
+
307
+ // Apply scoping to each selector in a comma-separated list
308
+ const scopedSelectors = trimmedSelector
309
+ .split(",")
310
+ .map((sel: string) => applySelectorScoping(sel.trim(), scopeId))
311
+ .join(", ");
312
+
313
+ return `${scopedSelectors} {`;
314
+ });
315
+ }
316
+
317
+ /**
318
+ * Apply scoping to a single CSS selector with comprehensive logic
319
+ */
320
+ export function applySelectorScoping(selector: string, scopeId: string) {
321
+ // Handle empty or invalid selectors
322
+ if (!selector || selector.length === 0) {
323
+ return selector;
324
+ }
325
+
326
+ // Skip already scoped selectors
327
+ if (selector.includes(`[data-${scopeId}]`)) {
328
+ return selector;
329
+ }
330
+
331
+ // Handle complex selectors with combinators (>, +, ~, space)
332
+ const combinatorRegex = /(\s*[>+~]\s*|\s+)/;
333
+ const parts = selector.split(combinatorRegex);
334
+
335
+ if (parts.length > 1) {
336
+ // Complex selector with combinators - scope the first part only
337
+ const firstPart = parts[0].trim();
338
+ const rest = parts.slice(1).join("");
339
+ return applySingleSelectorScoping(firstPart, scopeId) + rest;
340
+ }
341
+
342
+ // Simple selector
343
+ return applySingleSelectorScoping(selector, scopeId);
344
+ }
345
+
346
+ /**
347
+ * Apply scoping to a single, simple CSS selector
348
+ */
349
+ function applySingleSelectorScoping(selector: string, scopeId: string) {
350
+ // Handle pseudo-elements (::before, ::after)
351
+ const pseudoElementMatch = selector.match(/^([^:]+)(::.*)?$/);
352
+ if (pseudoElementMatch) {
353
+ const [, baseSelector, pseudoElement] = pseudoElementMatch;
354
+ return `${baseSelector}[data-${scopeId}]${pseudoElement || ""}`;
355
+ }
356
+
357
+ // Handle pseudo-classes (:hover, :focus, :nth-child, etc.)
358
+ const pseudoClassMatch = selector.match(/^([^:]+)(:.*)?$/);
359
+ if (pseudoClassMatch) {
360
+ const [, baseSelector, pseudoClass] = pseudoClassMatch;
361
+ return `${baseSelector}[data-${scopeId}]${pseudoClass || ""}`;
362
+ }
363
+
364
+ // Simple selector without pseudo-classes/elements
365
+ return `${selector}[data-${scopeId}]`;
366
+ }
367
+
368
+ /**
369
+ * Optimize component CSS by removing duplicates and applying minification
370
+ * Enhanced version with better deduplication and optimization strategies
371
+ */
372
+ export function optimizeComponentCSS(css: string) {
373
+ try {
374
+ // Parse CSS into rules with better handling
375
+ const rules = parseCSRules(css);
376
+ const optimizedRules = deduplicateCSRules(rules);
377
+
378
+ // Reconstruct CSS
379
+ const optimizedCSS = optimizedRules.join("\n");
380
+
381
+ // Apply environment-specific optimizations
382
+ const isDev = process.env.NODE_ENV !== "production";
383
+ if (!isDev) {
384
+ return minifyCSS(optimizedCSS);
385
+ }
386
+
387
+ return optimizedCSS;
388
+ } catch (error) {
389
+ console.warn(`⚠️ Failed to optimize component CSS:`, error);
390
+ return css;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Parse CSS into individual rules with proper handling of nested structures
396
+ */
397
+ function parseCSRules(css: string) {
398
+ const rules: string[] = [];
399
+ let currentRule = "";
400
+ let braceDepth = 0;
401
+ let inString = false;
402
+ let stringChar = "";
403
+
404
+ for (let i = 0; i < css.length; i++) {
405
+ const char = css[i];
406
+ const prevChar = i > 0 ? css[i - 1] : "";
407
+
408
+ // Handle string literals
409
+ if ((char === '"' || char === "'") && prevChar !== "\\") {
410
+ if (!inString) {
411
+ inString = true;
412
+ stringChar = char;
413
+ } else if (char === stringChar) {
414
+ inString = false;
415
+ stringChar = "";
416
+ }
417
+ }
418
+
419
+ if (!inString) {
420
+ if (char === "{") {
421
+ braceDepth++;
422
+ } else if (char === "}") {
423
+ braceDepth--;
424
+
425
+ if (braceDepth === 0) {
426
+ // End of a complete rule
427
+ currentRule += char;
428
+ const trimmedRule = currentRule.trim();
429
+ if (trimmedRule && !isEmptyRule(trimmedRule)) {
430
+ rules.push(trimmedRule);
431
+ }
432
+ currentRule = "";
433
+ continue;
434
+ }
435
+ }
436
+ }
437
+
438
+ currentRule += char;
439
+ }
440
+
441
+ // Handle any remaining content
442
+ if (currentRule.trim()) {
443
+ rules.push(currentRule.trim());
444
+ }
445
+
446
+ return rules;
447
+ }
448
+
449
+ /**
450
+ * Deduplicate CSS rules while preserving order and handling specificity
451
+ */
452
+ function deduplicateCSRules(rules: string[]) {
453
+ const seenRules = new Map<string, { rule: string; index: number }>();
454
+ const result: string[] = [];
455
+
456
+ for (let i = 0; i < rules.length; i++) {
457
+ const rule = rules[i];
458
+ const ruleKey = extractRuleKey(rule);
459
+
460
+ if (ruleKey) {
461
+ const existing = seenRules.get(ruleKey);
462
+ if (existing) {
463
+ // Replace earlier rule with later one (cascade order)
464
+ result[existing.index] = rule;
465
+ seenRules.set(ruleKey, { rule, index: existing.index });
466
+ } else {
467
+ // New rule
468
+ const index = result.length;
469
+ result.push(rule);
470
+ seenRules.set(ruleKey, { rule, index });
471
+ }
472
+ } else {
473
+ // Non-standard rule (at-rules, etc.) - keep as-is
474
+ result.push(rule);
475
+ }
476
+ }
477
+
478
+ return result.filter((rule) => rule !== null);
479
+ }
480
+
481
+ /**
482
+ * Extract a key for rule deduplication (selector + property combination)
483
+ */
484
+ function extractRuleKey(rule: string) {
485
+ const match = rule.match(/^([^{]+)\{([^}]+)\}/);
486
+ if (!match) return null;
487
+
488
+ const selector = match[1].trim();
489
+ const declarations = match[2].trim();
490
+
491
+ // For deduplication, we consider rules with the same selector as duplicates
492
+ // Later rules will override earlier ones (CSS cascade)
493
+ return selector;
494
+ }
495
+
496
+ /**
497
+ * Check if a CSS rule is effectively empty
498
+ */
499
+ function isEmptyRule(rule: string) {
500
+ const match = rule.match(/^[^{]+\{([^}]*)\}/);
501
+ if (!match) return true;
502
+
503
+ const declarations = match[1].trim();
504
+ return declarations.length === 0 || declarations === ";";
505
+ }
506
+
507
+ /**
508
+ * Minify CSS for production builds
509
+ */
510
+ export function minifyCSS(css: string) {
511
+ return css
512
+ // Remove comments
513
+ .replace(/\/\*[\s\S]*?\*\//g, "")
514
+ // Remove unnecessary whitespace
515
+ .replace(/\s+/g, " ")
516
+ // Remove whitespace around braces and semicolons
517
+ .replace(/\s*{\s*/g, "{")
518
+ .replace(/\s*}\s*/g, "}")
519
+ .replace(/\s*;\s*/g, ";")
520
+ .replace(/;\s*}/g, "}")
521
+ // Remove trailing semicolons before closing braces
522
+ .replace(/;}/g, "}")
523
+ // Trim
524
+ .trim();
525
+ }
526
+
527
+ /**
528
+ * Generate consistent scope ID for any component framework
529
+ * Provides unified scoping across Vue, Svelte, and other frameworks
530
+ */
531
+ export function generateComponentScopeId(
532
+ src: string,
533
+ framework: string = "component",
534
+ ) {
535
+ const cleanPath = src
536
+ .replace(/^\/+/, "") // Remove leading slashes
537
+ .replace(/\.(svelte|tsx|jsx|vue|ts|js)$/, "") // Remove file extensions
538
+ .replace(/[^a-zA-Z0-9\/]/g, "-") // Replace special chars with hyphens
539
+ .replace(/\/+/g, "-") // Replace path separators with hyphens
540
+ .replace(/-+/g, "-") // Collapse multiple hyphens
541
+ .replace(/^-|-$/g, "") // Remove leading/trailing hyphens
542
+ .toLowerCase();
543
+
544
+ // Add framework prefix and hash for collision resistance
545
+ const hash = simpleHash(src);
546
+ return `${framework}-${cleanPath}-${hash}`;
547
+ }
548
+
549
+ /**
550
+ * Simple hash function for generating consistent short hashes
551
+ */
552
+ export function simpleHash(str: string) {
553
+ let hash = 0;
554
+ for (let i = 0; i < str.length; i++) {
555
+ const char = str.charCodeAt(i);
556
+ hash = ((hash << 5) - hash) + char;
557
+ hash = hash & hash; // Convert to 32-bit integer
558
+ }
559
+ return Math.abs(hash).toString(36).substring(0, 6);
560
+ }
561
+
562
+ // Note: processSvelteSSRCSS, extractRawCSSFromHead, and applySvelteComponentScoping
563
+ // were removed as they are not used anywhere in the codebase.
564
+ // They were originally intended for Svelte SSR CSS processing but are superseded
565
+ // by the Svelte 5 render() function which handles CSS differently.