@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
- import type { JSX } from 'preact';
2
- import { h } from 'preact';
3
- import type { ViteDevServer } from 'vite';
4
- import type { AnalyzerOptions } from '../core/components/component-analyzer.ts';
5
- import type { Framework } from './types.ts';
6
- import { detectFramework } from './framework-detection.ts';
7
- import { analyzeComponentFile, renderComponentSSROnly } from './component-analysis.ts';
8
- import { loadIntegration, detectFrameworkFromPath } from './integration-loader.ts';
9
- import { addUniversalCSS } from './universal-css-collector.ts';
10
- import { addUniversalHead } from './universal-head-collector.ts';
11
- import { getIslandBundlePath } from '../build/island-manifest.ts';
12
- import type { Integration } from '../../../integrations/core/types.ts';
13
- import { isDev, devLog, devWarn, devError, logRenderTiming } from '../utils/dev-logger.ts';
14
-
15
- // Enhanced global CSS collector for SSR with scoping support
16
- declare global {
17
- var __viteDevServer: ViteDevServer | undefined;
18
- }
19
-
20
- /** Supported hydration conditions for island components */
21
- export type HydrationCondition = 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}`;
22
-
23
- /** Supported framework identifiers (without "unknown") */
24
- export type FrameworkId = Exclude<Framework, 'unknown'>;
25
-
26
- export interface IslandProps {
27
- /** Path to the island component (e.g., "/islands/Counter.tsx") */
28
- src: string;
29
- /** Hydration condition */
30
- condition?: HydrationCondition;
31
- /** Props to pass to the island component */
32
- props?: Record<string, unknown>;
33
- /** Children to render inside the island (for SSR) */
34
- children?: import('preact').ComponentChildren;
35
- /** Whether to render server-side (default: true unless condition is 'on:client') */
36
- ssr?: boolean;
37
- /** Framework hint for client hydration */
38
- framework?: FrameworkId;
39
- /** Force SSR-only rendering without hydration */
40
- ssrOnly?: boolean;
41
- /** Component render options for intelligent detection */
42
- renderOptions?: AnalyzerOptions;
43
- /** Hydration data from integration renderer */
44
- hydrationData?: Record<string, unknown>;
45
- }
46
-
47
- // ---------------------------------------------------------------------------
48
- // Shared helpers (extracted to reduce cognitive complexity of Island/renderIsland)
49
- // ---------------------------------------------------------------------------
50
-
51
- /** Generate a deterministic island element ID from the source path */
52
- function toIslandId(src: string): string {
53
- return `island-${src.replaceAll(/[^a-zA-Z0-9]/g, '-')}`;
54
- }
55
-
56
- /** Build the extra hydration data-attributes from integration render output */
57
- function buildHydrationDataAttrs(hydrationData: Record<string, unknown>): Record<string, string> {
58
- const attrs: Record<string, string> = {};
59
- if (hydrationData.renderId) {
60
- attrs['data-solid-render-id'] = hydrationData.renderId as string;
61
- }
62
- const metadata = hydrationData.metadata as Record<string, unknown> | undefined;
63
- if (metadata?.tagName) {
64
- attrs['data-tag-name'] = metadata.tagName as string;
65
- }
66
- return attrs;
67
- }
68
-
69
- /** Build the full set of attributes for an `<avalon-island>` element that will be hydrated */
70
- function buildHydrateAttributes(
71
- src: string,
72
- condition: HydrationCondition,
73
- props: Record<string, unknown>,
74
- hydrationData: Record<string, unknown>,
75
- ): Record<string, string> {
76
- return {
77
- 'data-condition': condition,
78
- 'data-src': getIslandBundlePath(src),
79
- 'data-props': JSON.stringify(props),
80
- 'data-render-strategy': 'hydrate',
81
- ...buildHydrationDataAttrs(hydrationData),
82
- };
83
- }
84
-
85
- /** Detect the head-content type from an HTML string returned by an integration */
86
- function classifyHeadContent(headContent: string): 'script' | 'meta' | 'link' | 'style' | 'other' {
87
- if (headContent.startsWith('<script')) return 'script';
88
- if (headContent.startsWith('<style')) return 'style';
89
- if (headContent.startsWith('<meta')) return 'meta';
90
- if (headContent.startsWith('<link')) return 'link';
91
- if (headContent.includes('window._$HY') || headContent.includes('_$HY=')) return 'script';
92
- return 'other';
93
- }
94
-
95
- /** Extract CSS content from a <style> tag */
96
- function extractCSSFromStyleTag(styleTag: string): string | null {
97
- const match = styleTag.match(/<style[^>]*>([\s\S]*?)<\/style>/i);
98
- return match ? match[1].trim() : null;
99
- }
100
-
101
- /** Collect CSS and head content produced by an integration render */
102
- function collectRenderAssets(
103
- renderResult: { css?: string; head?: string; scopeId?: string },
104
- src: string,
105
- framework: string,
106
- logPrefix: string,
107
- ): void {
108
- if (renderResult.css) {
109
- addUniversalCSS(renderResult.css, src, framework, (renderResult as { scopeId?: string }).scopeId);
110
- }
111
- if (renderResult.head) {
112
- const headContent = renderResult.head.trim();
113
- const contentType = classifyHeadContent(headContent);
114
- if (contentType === 'style') {
115
- // Extract CSS from <style> tag and add to universal CSS collector
116
- const cssContent = extractCSSFromStyleTag(headContent);
117
- if (cssContent) {
118
- devLog(`${logPrefix} Extracting CSS from head <style> tag`);
119
- addUniversalCSS(cssContent, src, framework, (renderResult as { scopeId?: string }).scopeId);
120
- }
121
- return;
122
- }
123
- addUniversalHead(renderResult.head, src, framework, contentType);
124
- }
125
- }
126
-
127
- // ---------------------------------------------------------------------------
128
- // Island – the synchronous component that emits <avalon-island> custom elements
129
- // ---------------------------------------------------------------------------
130
-
131
- /** Render the SSR path: we already have rendered children to embed */
132
- function renderIslandSSR(opts: {
133
- islandId: string;
134
- detectedFramework: string;
135
- shouldSkipHydration: boolean;
136
- src: string;
137
- condition: HydrationCondition;
138
- props: Record<string, unknown>;
139
- hydrationData: Record<string, unknown>;
140
- children: import('preact').ComponentChildren;
141
- }): JSX.Element {
142
- const { islandId, detectedFramework, shouldSkipHydration, src, condition, props, hydrationData, children } = opts;
143
- const baseAttributes: Record<string, string> = {
144
- id: islandId,
145
- 'data-framework': detectedFramework,
146
- };
147
-
148
- const hydrationAttributes = shouldSkipHydration
149
- ? { 'data-render-strategy': 'ssr-only' }
150
- : buildHydrateAttributes(src, condition, props, hydrationData);
151
-
152
- if (detectedFramework === 'lit') {
153
- devLog(`🔍 [Island Component] ${src} - Lit hydration data:`, {
154
- hydrationDataKeys: Object.keys(hydrationData),
155
- metadata: hydrationData.metadata,
156
- });
157
- }
158
-
159
- const allAttributes = { ...baseAttributes, ...hydrationAttributes };
160
-
161
- if (typeof children === 'string') {
162
- return h('avalon-island', { ...allAttributes, dangerouslySetInnerHTML: { __html: children } });
163
- }
164
- return h('avalon-island', allAttributes, children);
165
- }
166
-
167
- /** Render the client-only path: empty shell that will be hydrated on the client */
168
- function renderIslandClientOnly(
169
- islandId: string,
170
- detectedFramework: string,
171
- shouldSkipHydration: boolean,
172
- src: string,
173
- condition: HydrationCondition,
174
- props: Record<string, unknown>,
175
- hydrationData: Record<string, unknown>,
176
- ): JSX.Element {
177
- if (shouldSkipHydration) {
178
- return h('avalon-island', {
179
- id: islandId,
180
- 'data-render-strategy': 'ssr-only',
181
- 'data-framework': detectedFramework,
182
- });
183
- }
184
-
185
- return h('avalon-island', {
186
- id: islandId,
187
- 'data-condition': condition,
188
- 'data-src': getIslandBundlePath(src),
189
- 'data-props': JSON.stringify(props),
190
- 'data-render-strategy': 'hydrate',
191
- 'data-framework': detectedFramework,
192
- ...buildHydrationDataAttrs(hydrationData),
193
- });
194
- }
195
-
196
- /**
197
- * Universal Island component – renders `<avalon-island>` custom elements for better DOM structure.
198
- *
199
- * Uses custom elements instead of div wrappers for cleaner, more semantic markup.
200
- * Supports intelligent rendering strategy detection to skip hydration for SSR-only components.
201
- */
202
- export default function Island({
203
- src,
204
- condition = 'on:client',
205
- props = {},
206
- children,
207
- ssr = condition !== 'on:client',
208
- framework,
209
- ssrOnly = false,
210
- renderOptions = {},
211
- hydrationData = {},
212
- }: IslandProps): JSX.Element {
213
- const islandId = toIslandId(src);
214
- const shouldSkipHydration = ssrOnly || !!renderOptions.forceSSROnly;
215
- const detectedFramework = framework || detectFrameworkFromPath(src);
216
- const hasValidChildren = children !== undefined && children !== null && children !== '';
217
-
218
- devLog(`🔍 [Island Component] ${src}`, { ssr, ssrOnly, hasChildren: hasValidChildren, framework, condition });
219
-
220
- if (ssr && hasValidChildren) {
221
- return renderIslandSSR({
222
- islandId,
223
- detectedFramework,
224
- shouldSkipHydration,
225
- src,
226
- condition,
227
- props,
228
- hydrationData,
229
- children,
230
- });
231
- }
232
-
233
- if (ssr && !hasValidChildren && shouldSkipHydration) {
234
- devWarn(`${src}: SSR-only component has no rendered content. This may indicate a rendering error.`);
235
- }
236
-
237
- return renderIslandClientOnly(islandId, detectedFramework, shouldSkipHydration, src, condition, props, hydrationData);
238
- }
239
-
240
- // ---------------------------------------------------------------------------
241
- // renderErrorPlaceholder
242
- // ---------------------------------------------------------------------------
243
-
244
- /**
245
- * Render an error placeholder when island SSR fails.
246
- * @internal
247
- */
248
- function renderErrorPlaceholder(src: string, error: unknown): JSX.Element {
249
- const errorMessage = error instanceof Error ? error.message : String(error);
250
- devError(`🚨 Island SSR failed for ${src}:`, error);
251
- if (error instanceof Error && error.stack) {
252
- devError(`Stack trace:`, error.stack);
253
- }
254
- return h('avalon-island', {
255
- id: toIslandId(src),
256
- 'data-src': getIslandBundlePath(src),
257
- 'data-ssr-error': errorMessage,
258
- 'data-render-strategy': 'client-only',
259
- });
260
- }
261
-
262
- // ---------------------------------------------------------------------------
263
- // renderWithExplicitFramework (fast path)
264
- // ---------------------------------------------------------------------------
265
-
266
- /**
267
- * Render an island using the fast path when framework is explicitly provided.
268
- * @internal
269
- */
270
- async function renderWithExplicitFramework({
271
- src,
272
- condition,
273
- props,
274
- children,
275
- ssr,
276
- framework,
277
- ssrOnly,
278
- renderOptions,
279
- }: {
280
- src: string;
281
- condition: IslandProps['condition'];
282
- props: Record<string, unknown>;
283
- children?: import('preact').ComponentChildren;
284
- ssr: boolean;
285
- framework: NonNullable<IslandProps['framework']>;
286
- ssrOnly: boolean;
287
- renderOptions: AnalyzerOptions;
288
- }): Promise<JSX.Element> {
289
- const logPrefix = `🏝️ [${src}]`;
290
-
291
- if (!ssr || children) {
292
- return Island({ src, condition, props, children, ssr, framework, ssrOnly, renderOptions });
293
- }
294
-
295
- let integration: Integration;
296
- try {
297
- integration = await loadIntegration(framework);
298
- } catch (error) {
299
- devError(`${logPrefix} Failed to load ${framework} integration:`, error);
300
- return Island({ src, condition, props, ssr: false, framework, ssrOnly, renderOptions });
301
- }
302
-
303
- try {
304
- const renderResult = await integration.render({
305
- component: null,
306
- props,
307
- src,
308
- condition,
309
- ssrOnly,
310
- viteServer: globalThis.__viteDevServer,
311
- isDev: isDev(),
312
- });
313
-
314
- collectRenderAssets(renderResult, src, framework, logPrefix);
315
-
316
- return Island({
317
- src,
318
- condition,
319
- props,
320
- children: renderResult.html,
321
- ssr: true,
322
- framework,
323
- ssrOnly,
324
- renderOptions,
325
- hydrationData: ssrOnly ? undefined : renderResult.hydrationData,
326
- });
327
- } catch (error) {
328
- devError(`${logPrefix} Fast path SSR failed:`, error);
329
- return Island({ src, condition, props, ssr: false, framework, ssrOnly, renderOptions });
330
- }
331
- }
332
-
333
- // ---------------------------------------------------------------------------
334
- // renderIsland slow-path helpers
335
- // ---------------------------------------------------------------------------
336
-
337
- /** Determine whether the component should skip hydration via analysis */
338
- async function analyzeHydrationStrategy(
339
- src: string,
340
- ssrOnly: boolean,
341
- renderOptions: AnalyzerOptions,
342
- logPrefix: string,
343
- ): Promise<boolean> {
344
- if (ssrOnly || renderOptions.detectScripts === false) return ssrOnly;
345
-
346
- try {
347
- const analysisResult = await analyzeComponentFile(src, renderOptions);
348
- if (analysisResult.decision.warnings?.length) {
349
- for (const warning of analysisResult.decision.warnings) {
350
- devWarn(`${logPrefix} Analysis warning: ${warning}`);
351
- }
352
- }
353
- return !analysisResult.decision.shouldHydrate;
354
- } catch (error) {
355
- devWarn(`${logPrefix} Component analysis failed:`, error);
356
- return ssrOnly;
357
- }
358
- }
359
-
360
- /** Auto-detect framework from file extension / content */
361
- async function detectFrameworkForSrc(src: string): Promise<string> {
362
- if (src.endsWith('.vue')) return 'vue';
363
- if (src.endsWith('.svelte')) return 'svelte';
364
- if (src.endsWith('.tsx') || src.endsWith('.jsx') || src.endsWith('.ts') || src.endsWith('.js')) {
365
- return detectFramework(src);
366
- }
367
- return 'unknown';
368
- }
369
-
370
- /** Load an integration and render the component, returning the Island element */
371
- async function renderSlowPathSSR(
372
- src: string,
373
- condition: HydrationCondition,
374
- props: Record<string, unknown>,
375
- ssrOnly: boolean,
376
- renderOptions: AnalyzerOptions,
377
- logPrefix: string,
378
- ): Promise<JSX.Element> {
379
- const detectedFramework = await detectFrameworkForSrc(src);
380
- const frameworkId = detectedFramework as FrameworkId;
381
-
382
- const integration = await loadIntegrationOrThrow(detectedFramework, logPrefix);
383
- const renderResult = await integration.render({
384
- component: null,
385
- props,
386
- src,
387
- condition,
388
- ssrOnly,
389
- viteServer: globalThis.__viteDevServer,
390
- isDev: isDev(),
391
- });
392
-
393
- collectRenderAssets(renderResult, src, detectedFramework, logPrefix);
394
-
395
- const result = Island({
396
- src,
397
- condition,
398
- props,
399
- children: renderResult.html,
400
- ssr: true,
401
- framework: frameworkId,
402
- ssrOnly,
403
- renderOptions,
404
- hydrationData: ssrOnly ? undefined : renderResult.hydrationData,
405
- });
406
-
407
- return result;
408
- }
409
-
410
- /** Load an integration, throwing a descriptive error on failure */
411
- async function loadIntegrationOrThrow(framework: string, logPrefix: string): Promise<Integration> {
412
- try {
413
- devLog(`${logPrefix} Loading integration for framework: ${framework}`);
414
- const integration = await loadIntegration(framework);
415
- devLog(`${logPrefix} ✅ Integration loaded successfully`);
416
- return integration;
417
- } catch (error) {
418
- devError(`${logPrefix} Failed to load ${framework} integration:`, error);
419
- throw new Error(
420
- `Failed to load integration for framework '${framework}'. ` +
421
- `Make sure @useavalon/${framework} is installed.\n` +
422
- `Install it with: deno add @useavalon/${framework}`,
423
- { cause: error },
424
- );
425
- }
426
- }
427
-
428
- // ---------------------------------------------------------------------------
429
- // renderIsland – the main async entry point
430
- // ---------------------------------------------------------------------------
431
-
432
- /**
433
- * Universal renderIsland function – auto-detects framework and handles SSR + hydration.
434
- *
435
- * This is the main function you should use – it automatically:
436
- * - Detects the component framework (Vue, Solid.js, Preact/React)
437
- * - Analyzes component for intelligent rendering strategy detection
438
- * - Handles server-side rendering when possible
439
- * - Falls back to client-only rendering when needed
440
- * - Returns the appropriate Island component
441
- *
442
- * Performance tip: Providing an explicit `framework` prop skips component analysis
443
- * and framework detection, significantly improving render performance.
444
- *
445
- * Error isolation: If SSR fails, returns an error placeholder instead of throwing,
446
- * allowing the page to continue rendering other islands.
447
- */
448
- export async function renderIsland({
449
- src,
450
- condition = 'on:client',
451
- props = {},
452
- children,
453
- ssr = condition !== 'on:client',
454
- framework,
455
- ssrOnly = false,
456
- renderOptions = {},
457
- }: IslandProps): Promise<JSX.Element> {
458
- const startTime = isDev() ? performance.now() : 0;
459
- const logPrefix = `🏝️ [${src}]`;
460
-
461
- try {
462
- // If ssrOnly is true we MUST enable SSR to render the component
463
- if (ssrOnly && !ssr) {
464
- ssr = true;
465
- }
466
-
467
- // Fast path: explicit framework skips all analysis/detection
468
- if (framework) {
469
- return await renderWithExplicitFramework({
470
- src,
471
- condition,
472
- props,
473
- children,
474
- ssr,
475
- framework,
476
- ssrOnly,
477
- renderOptions,
478
- });
479
- }
480
-
481
- // Slow path: detect framework and analyze component
482
- return await renderIslandSlowPath({
483
- src,
484
- condition,
485
- props,
486
- children,
487
- ssr,
488
- ssrOnly,
489
- renderOptions,
490
- logPrefix,
491
- });
492
- } catch (error) {
493
- return renderErrorPlaceholder(src, error);
494
- } finally {
495
- if (isDev()) {
496
- logRenderTiming(src, performance.now() - startTime);
497
- }
498
- }
499
- }
500
-
501
- /** Slow path for renderIsland – framework detection + component analysis */
502
- async function renderIslandSlowPath(opts: {
503
- src: string;
504
- condition: HydrationCondition;
505
- props: Record<string, unknown>;
506
- children: import('preact').ComponentChildren | undefined;
507
- ssr: boolean;
508
- ssrOnly: boolean;
509
- renderOptions: AnalyzerOptions;
510
- logPrefix: string;
511
- }): Promise<JSX.Element> {
512
- const { src, condition, props, children, ssr, ssrOnly, renderOptions, logPrefix } = opts;
513
- devLog(`🔍 [renderIsland] ${src} - Starting render (slow path)`, {
514
- ssr,
515
- ssrOnly,
516
- hasChildren: !!children,
517
- condition,
518
- });
519
-
520
- const shouldSkipHydration = await analyzeHydrationStrategy(src, ssrOnly, renderOptions, logPrefix);
521
-
522
- if (shouldSkipHydration) {
523
- return renderSSROnlyPath(src, condition, props, children, ssr, renderOptions, logPrefix);
524
- }
525
-
526
- // If SSR is disabled or we already have children, use basic Island
527
- if (!ssr || children) {
528
- return Island({ src, condition, props, children, ssr, renderOptions });
529
- }
530
-
531
- // Full SSR rendering with auto-detected framework
532
- try {
533
- return await renderSlowPathSSR(src, condition, props, ssrOnly, renderOptions, logPrefix);
534
- } catch (error) {
535
- const detectedFramework = await detectFrameworkForSrc(src);
536
- devError(`${logPrefix} Framework rendering failed:`, error);
537
- return Island({
538
- src,
539
- condition,
540
- props,
541
- ssr: false,
542
- framework: detectedFramework as FrameworkId,
543
- renderOptions,
544
- });
545
- }
546
- }
547
-
548
- /** Handle the SSR-only path when hydration should be skipped */
549
- function renderSSROnlyPath(
550
- src: string,
551
- condition: HydrationCondition,
552
- props: Record<string, unknown>,
553
- children: import('preact').ComponentChildren | undefined,
554
- ssr: boolean,
555
- renderOptions: AnalyzerOptions,
556
- logPrefix: string,
557
- ): Promise<JSX.Element> | JSX.Element {
558
- if (ssr && !children) {
559
- return renderComponentSSROnly({ src, condition, props, renderOptions }).catch(error => {
560
- devError(`${logPrefix} SSR failed for SSR-only component:`, error);
561
- return Island({ src, condition, props, ssr: false, ssrOnly: true, renderOptions });
562
- });
563
- }
564
- return Island({ src, condition, props, children, ssr, ssrOnly: true, renderOptions });
565
- }
1
+ import type { JSX } from 'preact';
2
+ import { h } from 'preact';
3
+ import type { ViteDevServer } from 'vite';
4
+ import type { AnalyzerOptions } from '../core/components/component-analyzer.ts';
5
+ import type { Framework } from './types.ts';
6
+ import { detectFramework } from './framework-detection.ts';
7
+ import { analyzeComponentFile, renderComponentSSROnly } from './component-analysis.ts';
8
+ import { loadIntegration, detectFrameworkFromPath } from './integration-loader.ts';
9
+ import { addUniversalCSS } from './universal-css-collector.ts';
10
+ import { addUniversalHead } from './universal-head-collector.ts';
11
+ import { getIslandBundlePath } from '../build/island-manifest.ts';
12
+ import type { Integration } from '../../../integrations/core/types.ts';
13
+ import { isDev, devLog, devWarn, devError, logRenderTiming } from '../utils/dev-logger.ts';
14
+
15
+ // Enhanced global CSS collector for SSR with scoping support
16
+ declare global {
17
+ var __viteDevServer: ViteDevServer | undefined;
18
+ }
19
+
20
+ /** Supported hydration conditions for island components */
21
+ export type HydrationCondition = 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}`;
22
+
23
+ /** Supported framework identifiers (without "unknown") */
24
+ export type FrameworkId = Exclude<Framework, 'unknown'>;
25
+
26
+ export interface IslandProps {
27
+ /** Path to the island component (e.g., "/islands/Counter.tsx") */
28
+ src: string;
29
+ /** Hydration condition */
30
+ condition?: HydrationCondition;
31
+ /** Props to pass to the island component */
32
+ props?: Record<string, unknown>;
33
+ /** Children to render inside the island (for SSR) */
34
+ children?: import('preact').ComponentChildren;
35
+ /** Whether to render server-side (default: true unless condition is 'on:client') */
36
+ ssr?: boolean;
37
+ /** Framework hint for client hydration */
38
+ framework?: FrameworkId;
39
+ /** Force SSR-only rendering without hydration */
40
+ ssrOnly?: boolean;
41
+ /** Component render options for intelligent detection */
42
+ renderOptions?: AnalyzerOptions;
43
+ /** Hydration data from integration renderer */
44
+ hydrationData?: Record<string, unknown>;
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Shared helpers (extracted to reduce cognitive complexity of Island/renderIsland)
49
+ // ---------------------------------------------------------------------------
50
+
51
+ /** Generate a deterministic island element ID from the source path */
52
+ function toIslandId(src: string): string {
53
+ return `island-${src.replaceAll(/[^a-zA-Z0-9]/g, '-')}`;
54
+ }
55
+
56
+ /** Build the extra hydration data-attributes from integration render output */
57
+ function buildHydrationDataAttrs(hydrationData: Record<string, unknown>): Record<string, string> {
58
+ const attrs: Record<string, string> = {};
59
+ if (hydrationData.renderId) {
60
+ attrs['data-solid-render-id'] = hydrationData.renderId as string;
61
+ }
62
+ const metadata = hydrationData.metadata as Record<string, unknown> | undefined;
63
+ if (metadata?.tagName) {
64
+ attrs['data-tag-name'] = metadata.tagName as string;
65
+ }
66
+ return attrs;
67
+ }
68
+
69
+ /** Build the full set of attributes for an `<avalon-island>` element that will be hydrated */
70
+ function buildHydrateAttributes(
71
+ src: string,
72
+ condition: HydrationCondition,
73
+ props: Record<string, unknown>,
74
+ hydrationData: Record<string, unknown>,
75
+ ): Record<string, string> {
76
+ return {
77
+ 'data-condition': condition,
78
+ 'data-src': getIslandBundlePath(src),
79
+ 'data-props': JSON.stringify(props),
80
+ 'data-render-strategy': 'hydrate',
81
+ ...buildHydrationDataAttrs(hydrationData),
82
+ };
83
+ }
84
+
85
+ /** Detect the head-content type from an HTML string returned by an integration */
86
+ function classifyHeadContent(headContent: string): 'script' | 'meta' | 'link' | 'style' | 'other' {
87
+ if (headContent.startsWith('<script')) return 'script';
88
+ if (headContent.startsWith('<style')) return 'style';
89
+ if (headContent.startsWith('<meta')) return 'meta';
90
+ if (headContent.startsWith('<link')) return 'link';
91
+ if (headContent.includes('window._$HY') || headContent.includes('_$HY=')) return 'script';
92
+ return 'other';
93
+ }
94
+
95
+ /** Extract CSS content from a <style> tag */
96
+ function extractCSSFromStyleTag(styleTag: string): string | null {
97
+ const match = styleTag.match(/<style[^>]*>([\s\S]*?)<\/style>/i);
98
+ return match ? match[1].trim() : null;
99
+ }
100
+
101
+ /** Collect CSS and head content produced by an integration render */
102
+ function collectRenderAssets(
103
+ renderResult: { css?: string; head?: string; scopeId?: string },
104
+ src: string,
105
+ framework: string,
106
+ logPrefix: string,
107
+ ): void {
108
+ if (renderResult.css) {
109
+ addUniversalCSS(renderResult.css, src, framework, (renderResult as { scopeId?: string }).scopeId);
110
+ }
111
+ if (renderResult.head) {
112
+ const headContent = renderResult.head.trim();
113
+ const contentType = classifyHeadContent(headContent);
114
+ if (contentType === 'style') {
115
+ // Extract CSS from <style> tag and add to universal CSS collector
116
+ const cssContent = extractCSSFromStyleTag(headContent);
117
+ if (cssContent) {
118
+ devLog(`${logPrefix} Extracting CSS from head <style> tag`);
119
+ addUniversalCSS(cssContent, src, framework, (renderResult as { scopeId?: string }).scopeId);
120
+ }
121
+ return;
122
+ }
123
+ addUniversalHead(renderResult.head, src, framework, contentType);
124
+ }
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Island – the synchronous component that emits <avalon-island> custom elements
129
+ // ---------------------------------------------------------------------------
130
+
131
+ /** Render the SSR path: we already have rendered children to embed */
132
+ function renderIslandSSR(opts: {
133
+ islandId: string;
134
+ detectedFramework: string;
135
+ shouldSkipHydration: boolean;
136
+ src: string;
137
+ condition: HydrationCondition;
138
+ props: Record<string, unknown>;
139
+ hydrationData: Record<string, unknown>;
140
+ children: import('preact').ComponentChildren;
141
+ }): JSX.Element {
142
+ const { islandId, detectedFramework, shouldSkipHydration, src, condition, props, hydrationData, children } = opts;
143
+ const baseAttributes: Record<string, string> = {
144
+ id: islandId,
145
+ 'data-framework': detectedFramework,
146
+ };
147
+
148
+ const hydrationAttributes = shouldSkipHydration
149
+ ? { 'data-render-strategy': 'ssr-only' }
150
+ : buildHydrateAttributes(src, condition, props, hydrationData);
151
+
152
+ if (detectedFramework === 'lit') {
153
+ devLog(`🔍 [Island Component] ${src} - Lit hydration data:`, {
154
+ hydrationDataKeys: Object.keys(hydrationData),
155
+ metadata: hydrationData.metadata,
156
+ });
157
+ }
158
+
159
+ const allAttributes = { ...baseAttributes, ...hydrationAttributes };
160
+
161
+ if (typeof children === 'string') {
162
+ return h('avalon-island', { ...allAttributes, dangerouslySetInnerHTML: { __html: children } });
163
+ }
164
+ return h('avalon-island', allAttributes, children);
165
+ }
166
+
167
+ /** Render the client-only path: empty shell that will be hydrated on the client */
168
+ function renderIslandClientOnly(
169
+ islandId: string,
170
+ detectedFramework: string,
171
+ shouldSkipHydration: boolean,
172
+ src: string,
173
+ condition: HydrationCondition,
174
+ props: Record<string, unknown>,
175
+ hydrationData: Record<string, unknown>,
176
+ ): JSX.Element {
177
+ if (shouldSkipHydration) {
178
+ return h('avalon-island', {
179
+ id: islandId,
180
+ 'data-render-strategy': 'ssr-only',
181
+ 'data-framework': detectedFramework,
182
+ });
183
+ }
184
+
185
+ return h('avalon-island', {
186
+ id: islandId,
187
+ 'data-condition': condition,
188
+ 'data-src': getIslandBundlePath(src),
189
+ 'data-props': JSON.stringify(props),
190
+ 'data-render-strategy': 'hydrate',
191
+ 'data-framework': detectedFramework,
192
+ ...buildHydrationDataAttrs(hydrationData),
193
+ });
194
+ }
195
+
196
+ /**
197
+ * Universal Island component – renders `<avalon-island>` custom elements for better DOM structure.
198
+ *
199
+ * Uses custom elements instead of div wrappers for cleaner, more semantic markup.
200
+ * Supports intelligent rendering strategy detection to skip hydration for SSR-only components.
201
+ */
202
+ export default function Island({
203
+ src,
204
+ condition = 'on:client',
205
+ props = {},
206
+ children,
207
+ ssr = condition !== 'on:client',
208
+ framework,
209
+ ssrOnly = false,
210
+ renderOptions = {},
211
+ hydrationData = {},
212
+ }: IslandProps): JSX.Element {
213
+ const islandId = toIslandId(src);
214
+ const shouldSkipHydration = ssrOnly || !!renderOptions.forceSSROnly;
215
+ const detectedFramework = framework || detectFrameworkFromPath(src);
216
+ const hasValidChildren = children !== undefined && children !== null && children !== '';
217
+
218
+ devLog(`🔍 [Island Component] ${src}`, { ssr, ssrOnly, hasChildren: hasValidChildren, framework, condition });
219
+
220
+ if (ssr && hasValidChildren) {
221
+ return renderIslandSSR({
222
+ islandId,
223
+ detectedFramework,
224
+ shouldSkipHydration,
225
+ src,
226
+ condition,
227
+ props,
228
+ hydrationData,
229
+ children,
230
+ });
231
+ }
232
+
233
+ if (ssr && !hasValidChildren && shouldSkipHydration) {
234
+ devWarn(`${src}: SSR-only component has no rendered content. This may indicate a rendering error.`);
235
+ }
236
+
237
+ return renderIslandClientOnly(islandId, detectedFramework, shouldSkipHydration, src, condition, props, hydrationData);
238
+ }
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // renderErrorPlaceholder
242
+ // ---------------------------------------------------------------------------
243
+
244
+ /**
245
+ * Render an error placeholder when island SSR fails.
246
+ * @internal
247
+ */
248
+ function renderErrorPlaceholder(src: string, error: unknown): JSX.Element {
249
+ const errorMessage = error instanceof Error ? error.message : String(error);
250
+ devError(`🚨 Island SSR failed for ${src}:`, error);
251
+ if (error instanceof Error && error.stack) {
252
+ devError(`Stack trace:`, error.stack);
253
+ }
254
+ return h('avalon-island', {
255
+ id: toIslandId(src),
256
+ 'data-src': getIslandBundlePath(src),
257
+ 'data-ssr-error': errorMessage,
258
+ 'data-render-strategy': 'client-only',
259
+ });
260
+ }
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // renderWithExplicitFramework (fast path)
264
+ // ---------------------------------------------------------------------------
265
+
266
+ /**
267
+ * Render an island using the fast path when framework is explicitly provided.
268
+ * @internal
269
+ */
270
+ async function renderWithExplicitFramework({
271
+ src,
272
+ condition,
273
+ props,
274
+ children,
275
+ ssr,
276
+ framework,
277
+ ssrOnly,
278
+ renderOptions,
279
+ }: {
280
+ src: string;
281
+ condition: IslandProps['condition'];
282
+ props: Record<string, unknown>;
283
+ children?: import('preact').ComponentChildren;
284
+ ssr: boolean;
285
+ framework: NonNullable<IslandProps['framework']>;
286
+ ssrOnly: boolean;
287
+ renderOptions: AnalyzerOptions;
288
+ }): Promise<JSX.Element> {
289
+ const logPrefix = `🏝️ [${src}]`;
290
+
291
+ if (!ssr || children) {
292
+ return Island({ src, condition, props, children, ssr, framework, ssrOnly, renderOptions });
293
+ }
294
+
295
+ let integration: Integration;
296
+ try {
297
+ integration = await loadIntegration(framework);
298
+ } catch (error) {
299
+ devError(`${logPrefix} Failed to load ${framework} integration:`, error);
300
+ return Island({ src, condition, props, ssr: false, framework, ssrOnly, renderOptions });
301
+ }
302
+
303
+ try {
304
+ const renderResult = await integration.render({
305
+ component: null,
306
+ props,
307
+ src,
308
+ condition,
309
+ ssrOnly,
310
+ viteServer: globalThis.__viteDevServer,
311
+ isDev: isDev(),
312
+ });
313
+
314
+ collectRenderAssets(renderResult, src, framework, logPrefix);
315
+
316
+ return Island({
317
+ src,
318
+ condition,
319
+ props,
320
+ children: renderResult.html,
321
+ ssr: true,
322
+ framework,
323
+ ssrOnly,
324
+ renderOptions,
325
+ hydrationData: ssrOnly ? undefined : renderResult.hydrationData,
326
+ });
327
+ } catch (error) {
328
+ devError(`${logPrefix} Fast path SSR failed:`, error);
329
+ return Island({ src, condition, props, ssr: false, framework, ssrOnly, renderOptions });
330
+ }
331
+ }
332
+
333
+ // ---------------------------------------------------------------------------
334
+ // renderIsland slow-path helpers
335
+ // ---------------------------------------------------------------------------
336
+
337
+ /** Determine whether the component should skip hydration via analysis */
338
+ async function analyzeHydrationStrategy(
339
+ src: string,
340
+ ssrOnly: boolean,
341
+ renderOptions: AnalyzerOptions,
342
+ logPrefix: string,
343
+ ): Promise<boolean> {
344
+ if (ssrOnly || renderOptions.detectScripts === false) return ssrOnly;
345
+
346
+ try {
347
+ const analysisResult = await analyzeComponentFile(src, renderOptions);
348
+ if (analysisResult.decision.warnings?.length) {
349
+ for (const warning of analysisResult.decision.warnings) {
350
+ devWarn(`${logPrefix} Analysis warning: ${warning}`);
351
+ }
352
+ }
353
+ return !analysisResult.decision.shouldHydrate;
354
+ } catch (error) {
355
+ devWarn(`${logPrefix} Component analysis failed:`, error);
356
+ return ssrOnly;
357
+ }
358
+ }
359
+
360
+ /** Auto-detect framework from file extension / content */
361
+ async function detectFrameworkForSrc(src: string): Promise<string> {
362
+ if (src.endsWith('.vue')) return 'vue';
363
+ if (src.endsWith('.svelte')) return 'svelte';
364
+ if (src.endsWith('.tsx') || src.endsWith('.jsx') || src.endsWith('.ts') || src.endsWith('.js')) {
365
+ return detectFramework(src);
366
+ }
367
+ return 'unknown';
368
+ }
369
+
370
+ /** Load an integration and render the component, returning the Island element */
371
+ async function renderSlowPathSSR(
372
+ src: string,
373
+ condition: HydrationCondition,
374
+ props: Record<string, unknown>,
375
+ ssrOnly: boolean,
376
+ renderOptions: AnalyzerOptions,
377
+ logPrefix: string,
378
+ ): Promise<JSX.Element> {
379
+ const detectedFramework = await detectFrameworkForSrc(src);
380
+ const frameworkId = detectedFramework as FrameworkId;
381
+
382
+ const integration = await loadIntegrationOrThrow(detectedFramework, logPrefix);
383
+ const renderResult = await integration.render({
384
+ component: null,
385
+ props,
386
+ src,
387
+ condition,
388
+ ssrOnly,
389
+ viteServer: globalThis.__viteDevServer,
390
+ isDev: isDev(),
391
+ });
392
+
393
+ collectRenderAssets(renderResult, src, detectedFramework, logPrefix);
394
+
395
+ const result = Island({
396
+ src,
397
+ condition,
398
+ props,
399
+ children: renderResult.html,
400
+ ssr: true,
401
+ framework: frameworkId,
402
+ ssrOnly,
403
+ renderOptions,
404
+ hydrationData: ssrOnly ? undefined : renderResult.hydrationData,
405
+ });
406
+
407
+ return result;
408
+ }
409
+
410
+ /** Load an integration, throwing a descriptive error on failure */
411
+ async function loadIntegrationOrThrow(framework: string, logPrefix: string): Promise<Integration> {
412
+ try {
413
+ devLog(`${logPrefix} Loading integration for framework: ${framework}`);
414
+ const integration = await loadIntegration(framework);
415
+ devLog(`${logPrefix} ✅ Integration loaded successfully`);
416
+ return integration;
417
+ } catch (error) {
418
+ devError(`${logPrefix} Failed to load ${framework} integration:`, error);
419
+ throw new Error(
420
+ `Failed to load integration for framework '${framework}'. ` +
421
+ `Make sure @useavalon/${framework} is installed.\n` +
422
+ `Install it with: deno add @useavalon/${framework}`,
423
+ { cause: error },
424
+ );
425
+ }
426
+ }
427
+
428
+ // ---------------------------------------------------------------------------
429
+ // renderIsland – the main async entry point
430
+ // ---------------------------------------------------------------------------
431
+
432
+ /**
433
+ * Universal renderIsland function – auto-detects framework and handles SSR + hydration.
434
+ *
435
+ * This is the main function you should use – it automatically:
436
+ * - Detects the component framework (Vue, Solid.js, Preact/React)
437
+ * - Analyzes component for intelligent rendering strategy detection
438
+ * - Handles server-side rendering when possible
439
+ * - Falls back to client-only rendering when needed
440
+ * - Returns the appropriate Island component
441
+ *
442
+ * Performance tip: Providing an explicit `framework` prop skips component analysis
443
+ * and framework detection, significantly improving render performance.
444
+ *
445
+ * Error isolation: If SSR fails, returns an error placeholder instead of throwing,
446
+ * allowing the page to continue rendering other islands.
447
+ */
448
+ export async function renderIsland({
449
+ src,
450
+ condition = 'on:client',
451
+ props = {},
452
+ children,
453
+ ssr = condition !== 'on:client',
454
+ framework,
455
+ ssrOnly = false,
456
+ renderOptions = {},
457
+ }: IslandProps): Promise<JSX.Element> {
458
+ const startTime = isDev() ? performance.now() : 0;
459
+ const logPrefix = `🏝️ [${src}]`;
460
+
461
+ try {
462
+ // If ssrOnly is true we MUST enable SSR to render the component
463
+ if (ssrOnly && !ssr) {
464
+ ssr = true;
465
+ }
466
+
467
+ // Fast path: explicit framework skips all analysis/detection
468
+ if (framework) {
469
+ return await renderWithExplicitFramework({
470
+ src,
471
+ condition,
472
+ props,
473
+ children,
474
+ ssr,
475
+ framework,
476
+ ssrOnly,
477
+ renderOptions,
478
+ });
479
+ }
480
+
481
+ // Slow path: detect framework and analyze component
482
+ return await renderIslandSlowPath({
483
+ src,
484
+ condition,
485
+ props,
486
+ children,
487
+ ssr,
488
+ ssrOnly,
489
+ renderOptions,
490
+ logPrefix,
491
+ });
492
+ } catch (error) {
493
+ return renderErrorPlaceholder(src, error);
494
+ } finally {
495
+ if (isDev()) {
496
+ logRenderTiming(src, performance.now() - startTime);
497
+ }
498
+ }
499
+ }
500
+
501
+ /** Slow path for renderIsland – framework detection + component analysis */
502
+ async function renderIslandSlowPath(opts: {
503
+ src: string;
504
+ condition: HydrationCondition;
505
+ props: Record<string, unknown>;
506
+ children: import('preact').ComponentChildren | undefined;
507
+ ssr: boolean;
508
+ ssrOnly: boolean;
509
+ renderOptions: AnalyzerOptions;
510
+ logPrefix: string;
511
+ }): Promise<JSX.Element> {
512
+ const { src, condition, props, children, ssr, ssrOnly, renderOptions, logPrefix } = opts;
513
+ devLog(`🔍 [renderIsland] ${src} - Starting render (slow path)`, {
514
+ ssr,
515
+ ssrOnly,
516
+ hasChildren: !!children,
517
+ condition,
518
+ });
519
+
520
+ const shouldSkipHydration = await analyzeHydrationStrategy(src, ssrOnly, renderOptions, logPrefix);
521
+
522
+ if (shouldSkipHydration) {
523
+ return renderSSROnlyPath(src, condition, props, children, ssr, renderOptions, logPrefix);
524
+ }
525
+
526
+ // If SSR is disabled or we already have children, use basic Island
527
+ if (!ssr || children) {
528
+ return Island({ src, condition, props, children, ssr, renderOptions });
529
+ }
530
+
531
+ // Full SSR rendering with auto-detected framework
532
+ try {
533
+ return await renderSlowPathSSR(src, condition, props, ssrOnly, renderOptions, logPrefix);
534
+ } catch (error) {
535
+ const detectedFramework = await detectFrameworkForSrc(src);
536
+ devError(`${logPrefix} Framework rendering failed:`, error);
537
+ return Island({
538
+ src,
539
+ condition,
540
+ props,
541
+ ssr: false,
542
+ framework: detectedFramework as FrameworkId,
543
+ renderOptions,
544
+ });
545
+ }
546
+ }
547
+
548
+ /** Handle the SSR-only path when hydration should be skipped */
549
+ function renderSSROnlyPath(
550
+ src: string,
551
+ condition: HydrationCondition,
552
+ props: Record<string, unknown>,
553
+ children: import('preact').ComponentChildren | undefined,
554
+ ssr: boolean,
555
+ renderOptions: AnalyzerOptions,
556
+ logPrefix: string,
557
+ ): Promise<JSX.Element> | JSX.Element {
558
+ if (ssr && !children) {
559
+ return renderComponentSSROnly({ src, condition, props, renderOptions }).catch(error => {
560
+ devError(`${logPrefix} SSR failed for SSR-only component:`, error);
561
+ return Island({ src, condition, props, ssr: false, ssrOnly: true, renderOptions });
562
+ });
563
+ }
564
+ return Island({ src, condition, props, children, ssr, ssrOnly: true, renderOptions });
565
+ }