@useavalon/avalon 0.1.12 → 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.
- package/mod.ts +302 -0
- package/package.json +9 -17
- package/src/build/integration-bundler-plugin.ts +116 -0
- package/src/build/integration-config.ts +168 -0
- package/src/build/integration-detection-plugin.ts +117 -0
- package/src/build/integration-resolver-plugin.ts +90 -0
- package/src/build/island-manifest.ts +269 -0
- package/src/build/island-types-generator.ts +476 -0
- package/src/build/mdx-island-transform.ts +464 -0
- package/src/build/mdx-plugin.ts +98 -0
- package/src/build/page-island-transform.ts +598 -0
- package/src/build/prop-extractors/index.ts +21 -0
- package/src/build/prop-extractors/lit.ts +140 -0
- package/src/build/prop-extractors/qwik.ts +16 -0
- package/src/build/prop-extractors/solid.ts +125 -0
- package/src/build/prop-extractors/svelte.ts +194 -0
- package/src/build/prop-extractors/vue.ts +111 -0
- package/src/build/sidecar-file-manager.ts +104 -0
- package/src/build/sidecar-renderer.ts +30 -0
- package/src/client/adapters/index.ts +21 -0
- package/src/client/components.ts +35 -0
- package/src/client/css-hmr-handler.ts +344 -0
- package/src/client/framework-adapter.ts +462 -0
- package/src/client/hmr-coordinator.ts +396 -0
- package/src/client/hmr-error-overlay.js +533 -0
- package/src/client/main.js +824 -0
- package/src/components/Image.tsx +123 -0
- package/src/components/IslandErrorBoundary.tsx +145 -0
- package/src/components/LayoutDataErrorBoundary.tsx +141 -0
- package/src/components/LayoutErrorBoundary.tsx +127 -0
- package/src/components/PersistentIsland.tsx +52 -0
- package/src/components/StreamingErrorBoundary.tsx +233 -0
- package/src/components/StreamingLayout.tsx +538 -0
- package/src/core/components/component-analyzer.ts +192 -0
- package/src/core/components/component-detection.ts +508 -0
- package/src/core/components/enhanced-framework-detector.ts +500 -0
- package/src/core/components/framework-registry.ts +563 -0
- package/src/core/content/mdx-processor.ts +46 -0
- package/src/core/integrations/index.ts +19 -0
- package/src/core/integrations/loader.ts +125 -0
- package/src/core/integrations/registry.ts +175 -0
- package/src/core/islands/island-persistence.ts +325 -0
- package/src/core/islands/island-state-serializer.ts +258 -0
- package/src/core/islands/persistent-island-context.tsx +80 -0
- package/src/core/islands/use-persistent-state.ts +68 -0
- package/src/core/layout/enhanced-layout-resolver.ts +322 -0
- package/src/core/layout/layout-cache-manager.ts +485 -0
- package/src/core/layout/layout-composer.ts +357 -0
- package/src/core/layout/layout-data-loader.ts +516 -0
- package/src/core/layout/layout-discovery.ts +243 -0
- package/src/core/layout/layout-matcher.ts +299 -0
- package/src/core/layout/layout-types.ts +110 -0
- package/src/core/modules/framework-module-resolver.ts +273 -0
- package/src/islands/component-analysis.ts +213 -0
- package/src/islands/css-utils.ts +565 -0
- package/src/islands/discovery/index.ts +80 -0
- package/src/islands/discovery/registry.ts +340 -0
- package/src/islands/discovery/resolver.ts +477 -0
- package/src/islands/discovery/scanner.ts +386 -0
- package/src/islands/discovery/types.ts +117 -0
- package/src/islands/discovery/validator.ts +544 -0
- package/src/islands/discovery/watcher.ts +368 -0
- package/src/islands/framework-detection.ts +428 -0
- package/src/islands/integration-loader.ts +490 -0
- package/src/islands/island.tsx +565 -0
- package/src/islands/render-cache.ts +550 -0
- package/src/islands/types.ts +80 -0
- package/src/islands/universal-css-collector.ts +157 -0
- package/src/islands/universal-head-collector.ts +137 -0
- package/src/layout-system.ts +218 -0
- package/src/middleware/discovery.ts +268 -0
- package/src/middleware/executor.ts +315 -0
- package/src/middleware/index.ts +76 -0
- package/src/middleware/types.ts +99 -0
- package/src/nitro/build-config.ts +576 -0
- package/src/nitro/config.ts +483 -0
- package/src/nitro/error-handler.ts +636 -0
- package/src/nitro/index.ts +173 -0
- package/src/nitro/island-manifest.ts +584 -0
- package/src/nitro/middleware-adapter.ts +260 -0
- package/src/nitro/renderer.ts +1471 -0
- package/src/nitro/route-discovery.ts +439 -0
- package/src/nitro/types.ts +321 -0
- package/src/render/collect-css.ts +198 -0
- package/src/render/error-pages.ts +79 -0
- package/src/render/isolated-ssr-renderer.ts +654 -0
- package/src/render/ssr.ts +1030 -0
- package/src/schemas/api.ts +30 -0
- package/src/schemas/core.ts +64 -0
- package/src/schemas/index.ts +212 -0
- package/src/schemas/layout.ts +279 -0
- package/src/schemas/routing/index.ts +38 -0
- package/src/schemas/routing.ts +376 -0
- package/src/types/as-island.ts +20 -0
- package/src/types/layout.ts +285 -0
- package/src/types/routing.ts +555 -0
- package/src/types/types.ts +5 -0
- package/src/utils/dev-logger.ts +299 -0
- package/src/utils/fs.ts +151 -0
- package/src/vite-plugin/auto-discover.ts +551 -0
- package/src/vite-plugin/config.ts +266 -0
- package/src/vite-plugin/errors.ts +127 -0
- package/src/vite-plugin/image-optimization.ts +156 -0
- package/src/vite-plugin/integration-activator.ts +126 -0
- package/src/vite-plugin/island-sidecar-plugin.ts +176 -0
- package/src/vite-plugin/module-discovery.ts +189 -0
- package/src/vite-plugin/nitro-integration.ts +1354 -0
- package/src/vite-plugin/plugin.ts +403 -0
- package/src/vite-plugin/types.ts +327 -0
- package/src/vite-plugin/validation.ts +228 -0
- package/dist/mod.js +0 -1
- package/dist/src/build/integration-bundler-plugin.js +0 -1
- package/dist/src/build/integration-config.js +0 -1
- package/dist/src/build/integration-detection-plugin.js +0 -1
- package/dist/src/build/integration-resolver-plugin.js +0 -1
- package/dist/src/build/island-manifest.js +0 -1
- package/dist/src/build/island-types-generator.js +0 -5
- package/dist/src/build/mdx-island-transform.js +0 -2
- package/dist/src/build/mdx-plugin.js +0 -1
- package/dist/src/build/page-island-transform.js +0 -3
- package/dist/src/build/prop-extractors/index.js +0 -1
- package/dist/src/build/prop-extractors/lit.js +0 -1
- package/dist/src/build/prop-extractors/qwik.js +0 -1
- package/dist/src/build/prop-extractors/solid.js +0 -1
- package/dist/src/build/prop-extractors/svelte.js +0 -1
- package/dist/src/build/prop-extractors/vue.js +0 -1
- package/dist/src/build/sidecar-file-manager.js +0 -1
- package/dist/src/build/sidecar-renderer.js +0 -6
- package/dist/src/client/adapters/index.js +0 -1
- package/dist/src/client/components.js +0 -1
- package/dist/src/client/css-hmr-handler.js +0 -1
- package/dist/src/client/framework-adapter.js +0 -13
- package/dist/src/client/hmr-coordinator.js +0 -1
- package/dist/src/client/hmr-error-overlay.js +0 -214
- package/dist/src/client/main.js +0 -39
- package/dist/src/components/Image.js +0 -1
- package/dist/src/components/IslandErrorBoundary.js +0 -1
- package/dist/src/components/LayoutDataErrorBoundary.js +0 -1
- package/dist/src/components/LayoutErrorBoundary.js +0 -1
- package/dist/src/components/PersistentIsland.js +0 -1
- package/dist/src/components/StreamingErrorBoundary.js +0 -1
- package/dist/src/components/StreamingLayout.js +0 -29
- package/dist/src/core/components/component-analyzer.js +0 -1
- package/dist/src/core/components/component-detection.js +0 -5
- package/dist/src/core/components/enhanced-framework-detector.js +0 -1
- package/dist/src/core/components/framework-registry.js +0 -1
- package/dist/src/core/content/mdx-processor.js +0 -1
- package/dist/src/core/integrations/index.js +0 -1
- package/dist/src/core/integrations/loader.js +0 -1
- package/dist/src/core/integrations/registry.js +0 -1
- package/dist/src/core/islands/island-persistence.js +0 -1
- package/dist/src/core/islands/island-state-serializer.js +0 -1
- package/dist/src/core/islands/persistent-island-context.js +0 -1
- package/dist/src/core/islands/use-persistent-state.js +0 -1
- package/dist/src/core/layout/enhanced-layout-resolver.js +0 -1
- package/dist/src/core/layout/layout-cache-manager.js +0 -1
- package/dist/src/core/layout/layout-composer.js +0 -1
- package/dist/src/core/layout/layout-data-loader.js +0 -1
- package/dist/src/core/layout/layout-discovery.js +0 -1
- package/dist/src/core/layout/layout-matcher.js +0 -1
- package/dist/src/core/layout/layout-types.js +0 -1
- package/dist/src/core/modules/framework-module-resolver.js +0 -1
- package/dist/src/islands/component-analysis.js +0 -1
- package/dist/src/islands/css-utils.js +0 -17
- package/dist/src/islands/discovery/index.js +0 -1
- package/dist/src/islands/discovery/registry.js +0 -1
- package/dist/src/islands/discovery/resolver.js +0 -2
- package/dist/src/islands/discovery/scanner.js +0 -1
- package/dist/src/islands/discovery/types.js +0 -1
- package/dist/src/islands/discovery/validator.js +0 -18
- package/dist/src/islands/discovery/watcher.js +0 -1
- package/dist/src/islands/framework-detection.js +0 -1
- package/dist/src/islands/integration-loader.js +0 -1
- package/dist/src/islands/island.js +0 -1
- package/dist/src/islands/render-cache.js +0 -1
- package/dist/src/islands/types.js +0 -1
- package/dist/src/islands/universal-css-collector.js +0 -5
- package/dist/src/islands/universal-head-collector.js +0 -2
- package/dist/src/layout-system.js +0 -1
- package/dist/src/middleware/discovery.js +0 -1
- package/dist/src/middleware/executor.js +0 -1
- package/dist/src/middleware/index.js +0 -1
- package/dist/src/middleware/types.js +0 -1
- package/dist/src/nitro/build-config.js +0 -1
- package/dist/src/nitro/config.js +0 -1
- package/dist/src/nitro/error-handler.js +0 -198
- package/dist/src/nitro/index.js +0 -1
- package/dist/src/nitro/island-manifest.js +0 -2
- package/dist/src/nitro/middleware-adapter.js +0 -1
- package/dist/src/nitro/renderer.js +0 -183
- package/dist/src/nitro/route-discovery.js +0 -1
- package/dist/src/nitro/types.js +0 -1
- package/dist/src/render/collect-css.js +0 -3
- package/dist/src/render/error-pages.js +0 -48
- package/dist/src/render/isolated-ssr-renderer.js +0 -1
- package/dist/src/render/ssr.js +0 -90
- package/dist/src/schemas/api.js +0 -1
- package/dist/src/schemas/core.js +0 -1
- package/dist/src/schemas/index.js +0 -1
- package/dist/src/schemas/layout.js +0 -1
- package/dist/src/schemas/routing/index.js +0 -1
- package/dist/src/schemas/routing.js +0 -1
- package/dist/src/types/as-island.js +0 -1
- package/dist/src/types/layout.js +0 -1
- package/dist/src/types/routing.js +0 -1
- package/dist/src/types/types.js +0 -1
- package/dist/src/utils/dev-logger.js +0 -12
- package/dist/src/utils/fs.js +0 -1
- package/dist/src/vite-plugin/auto-discover.js +0 -1
- package/dist/src/vite-plugin/config.js +0 -1
- package/dist/src/vite-plugin/errors.js +0 -1
- package/dist/src/vite-plugin/image-optimization.js +0 -45
- package/dist/src/vite-plugin/integration-activator.js +0 -1
- package/dist/src/vite-plugin/island-sidecar-plugin.js +0 -1
- package/dist/src/vite-plugin/module-discovery.js +0 -1
- package/dist/src/vite-plugin/nitro-integration.js +0 -42
- package/dist/src/vite-plugin/plugin.js +0 -1
- package/dist/src/vite-plugin/types.js +0 -1
- package/dist/src/vite-plugin/validation.js +0 -2
- /package/{dist/src → src}/client/types/framework-runtime.d.ts +0 -0
- /package/{dist/src → src}/client/types/vite-hmr.d.ts +0 -0
- /package/{dist/src → src}/client/types/vite-virtual-modules.d.ts +0 -0
- /package/{dist/src → src}/layout-system.d.ts +0 -0
- /package/{dist/src → src}/types/image.d.ts +0 -0
- /package/{dist/src → src}/types/index.d.ts +0 -0
- /package/{dist/src → src}/types/island-jsx.d.ts +0 -0
- /package/{dist/src → src}/types/island-prop.d.ts +0 -0
- /package/{dist/src → src}/types/mdx.d.ts +0 -0
- /package/{dist/src → src}/types/urlpattern.d.ts +0 -0
- /package/{dist/src → src}/types/vite-env.d.ts +0 -0
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
// Main client entry point for Vite
|
|
2
|
+
// Integration-based island hydration system
|
|
3
|
+
//
|
|
4
|
+
// NOTE: This file contains imports to Vite virtual modules (/@useavalon/*/client)
|
|
5
|
+
// that will show as errors in the IDE. These are resolved by Vite at runtime
|
|
6
|
+
// and work correctly in the browser. The errors can be safely ignored.
|
|
7
|
+
|
|
8
|
+
if (document.readyState === 'loading') {
|
|
9
|
+
document.addEventListener('DOMContentLoaded', initializeHydration);
|
|
10
|
+
} else {
|
|
11
|
+
initializeHydration();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize hydration for all islands on the page
|
|
16
|
+
* Discovers islands by data-framework attribute and routes to appropriate integration
|
|
17
|
+
*/
|
|
18
|
+
function initializeHydration() {
|
|
19
|
+
const islands = document.querySelectorAll('[data-framework]');
|
|
20
|
+
|
|
21
|
+
if (islands.length === 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
islands.forEach(island => {
|
|
26
|
+
try {
|
|
27
|
+
const framework = island.dataset.framework;
|
|
28
|
+
const condition = island.dataset.condition || 'on:client';
|
|
29
|
+
const renderStrategy = island.dataset.renderStrategy;
|
|
30
|
+
|
|
31
|
+
if (renderStrategy === 'ssr-only') {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!shouldHydrate(island, condition)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (condition === 'on:client') {
|
|
40
|
+
hydrateIsland(island, framework);
|
|
41
|
+
} else if (condition === 'on:visible') {
|
|
42
|
+
setupIntersectionObserver(island, framework);
|
|
43
|
+
} else if (condition === 'on:interaction') {
|
|
44
|
+
setupInteractionObserver(island, framework);
|
|
45
|
+
} else if (condition === 'on:idle') {
|
|
46
|
+
setupIdleCallback(island, framework);
|
|
47
|
+
} else if (condition.startsWith('media:')) {
|
|
48
|
+
const mediaQuery = condition.slice(6);
|
|
49
|
+
setupMediaQuery(island, framework, mediaQuery);
|
|
50
|
+
} else {
|
|
51
|
+
hydrateIsland(island, framework);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Error processing island:', error);
|
|
55
|
+
handleHydrationError(island, island.dataset.framework || 'unknown', island.dataset.src || 'unknown', error);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Determine if an island should hydrate based on its condition
|
|
62
|
+
*
|
|
63
|
+
* @param {HTMLElement} island - The island element
|
|
64
|
+
* @param {string} condition - The hydration condition
|
|
65
|
+
* @returns {boolean} Whether the island should hydrate
|
|
66
|
+
*/
|
|
67
|
+
function shouldHydrate(island, condition) {
|
|
68
|
+
if (!condition || condition === 'on:client') {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (condition.startsWith('media:')) {
|
|
73
|
+
const mediaQuery = condition.slice(6);
|
|
74
|
+
try {
|
|
75
|
+
return globalThis.matchMedia(mediaQuery).matches;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Invalid media query:', mediaQuery, error);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (condition === 'on:visible' || condition === 'on:interaction' || condition === 'on:idle') {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.warn('Unknown hydration condition:', condition);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Setup Intersection Observer for "on:visible" hydration
|
|
92
|
+
*
|
|
93
|
+
* @param {HTMLElement} island - The island element
|
|
94
|
+
* @param {string} framework - The framework name
|
|
95
|
+
*/
|
|
96
|
+
function setupIntersectionObserver(island, framework) {
|
|
97
|
+
try {
|
|
98
|
+
const observer = new IntersectionObserver(
|
|
99
|
+
entries => {
|
|
100
|
+
const entry = entries[0];
|
|
101
|
+
if (entry.isIntersecting) {
|
|
102
|
+
hydrateIsland(island, framework);
|
|
103
|
+
observer.disconnect();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
rootMargin: '50px',
|
|
108
|
+
threshold: 0,
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
observer.observe(island);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('Failed to setup intersection observer:', error);
|
|
115
|
+
hydrateIsland(island, framework);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Setup interaction observer for "on:interaction" hydration
|
|
121
|
+
*
|
|
122
|
+
* @param {HTMLElement} island - The island element
|
|
123
|
+
* @param {string} framework - The framework name
|
|
124
|
+
*/
|
|
125
|
+
function setupInteractionObserver(island, framework) {
|
|
126
|
+
const events = ['click', 'touchstart', 'mouseenter', 'focusin'];
|
|
127
|
+
let hydrated = false;
|
|
128
|
+
|
|
129
|
+
const handleInteraction = () => {
|
|
130
|
+
if (hydrated) return;
|
|
131
|
+
hydrated = true;
|
|
132
|
+
|
|
133
|
+
events.forEach(eventType => {
|
|
134
|
+
island.removeEventListener(eventType, handleInteraction);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
hydrateIsland(island, framework);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
events.forEach(eventType => {
|
|
142
|
+
island.addEventListener(eventType, handleInteraction, { once: true, passive: true });
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('Failed to setup interaction observer:', error);
|
|
146
|
+
hydrateIsland(island, framework);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Setup idle callback for "on:idle" hydration
|
|
152
|
+
*
|
|
153
|
+
* @param {HTMLElement} island - The island element
|
|
154
|
+
* @param {string} framework - The framework name
|
|
155
|
+
*/
|
|
156
|
+
function setupIdleCallback(island, framework) {
|
|
157
|
+
try {
|
|
158
|
+
if ('requestIdleCallback' in globalThis) {
|
|
159
|
+
globalThis.requestIdleCallback(
|
|
160
|
+
() => {
|
|
161
|
+
hydrateIsland(island, framework);
|
|
162
|
+
},
|
|
163
|
+
{ timeout: 5000 },
|
|
164
|
+
);
|
|
165
|
+
} else if (document.readyState === 'complete') {
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
hydrateIsland(island, framework);
|
|
168
|
+
}, 200);
|
|
169
|
+
} else {
|
|
170
|
+
globalThis.addEventListener(
|
|
171
|
+
'load',
|
|
172
|
+
() => {
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
hydrateIsland(island, framework);
|
|
175
|
+
}, 200);
|
|
176
|
+
},
|
|
177
|
+
{ once: true },
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error('Failed to setup idle callback:', error);
|
|
182
|
+
hydrateIsland(island, framework);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Setup media query listener for "media:" hydration
|
|
188
|
+
*
|
|
189
|
+
* @param {HTMLElement} island - The island element
|
|
190
|
+
* @param {string} framework - The framework name
|
|
191
|
+
* @param {string} mediaQuery - The media query string
|
|
192
|
+
*/
|
|
193
|
+
function setupMediaQuery(island, framework, mediaQuery) {
|
|
194
|
+
try {
|
|
195
|
+
const mql = globalThis.matchMedia(mediaQuery);
|
|
196
|
+
|
|
197
|
+
if (mql.matches) {
|
|
198
|
+
hydrateIsland(island, framework);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const handleChange = event => {
|
|
203
|
+
if (event.matches) {
|
|
204
|
+
hydrateIsland(island, framework);
|
|
205
|
+
mql.removeEventListener('change', handleChange);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
mql.addEventListener('change', handleChange);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Failed to setup media query:', mediaQuery, error);
|
|
212
|
+
hydrateIsland(island, framework);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Load the integration module for a given framework
|
|
218
|
+
*
|
|
219
|
+
* @param {string} framework - The framework name
|
|
220
|
+
* @returns {Promise<object>} The integration module
|
|
221
|
+
*/
|
|
222
|
+
async function loadIntegrationModule(framework) {
|
|
223
|
+
switch (framework) {
|
|
224
|
+
case 'preact':
|
|
225
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
226
|
+
return import('/@useavalon/preact/client');
|
|
227
|
+
case 'react':
|
|
228
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
229
|
+
return import('/@useavalon/react/client');
|
|
230
|
+
case 'vue':
|
|
231
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
232
|
+
return import('/@useavalon/vue/client');
|
|
233
|
+
case 'svelte':
|
|
234
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
235
|
+
return import('/@useavalon/svelte/client');
|
|
236
|
+
case 'solid':
|
|
237
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
238
|
+
return import('/@useavalon/solid/client');
|
|
239
|
+
case 'lit':
|
|
240
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
241
|
+
return import('/@useavalon/lit/client');
|
|
242
|
+
case 'qwik':
|
|
243
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
244
|
+
return import('/@useavalon/qwik/client');
|
|
245
|
+
default:
|
|
246
|
+
throw new Error(`Unknown framework: ${framework}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Resolve the component from a module, trying default export then named exports
|
|
252
|
+
*
|
|
253
|
+
* @param {object} componentModule - The imported module
|
|
254
|
+
* @param {string} src - The component source path (for error messages)
|
|
255
|
+
* @returns {object} The resolved component
|
|
256
|
+
*/
|
|
257
|
+
function resolveComponent(componentModule, src) {
|
|
258
|
+
let Component = componentModule.default;
|
|
259
|
+
|
|
260
|
+
if (!Component) {
|
|
261
|
+
const exports = Object.keys(componentModule).filter(key => key !== 'default');
|
|
262
|
+
for (const exportName of exports) {
|
|
263
|
+
const exportValue = componentModule[exportName];
|
|
264
|
+
if (typeof exportValue === 'function' && exportValue.prototype) {
|
|
265
|
+
Component = exportValue;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!Component) {
|
|
271
|
+
Component = componentModule;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!Component) {
|
|
276
|
+
throw new Error(`Component ${src} has no default export`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return Component;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Hydrate an island using the integration system
|
|
284
|
+
*
|
|
285
|
+
* @param {HTMLElement} island - The island element
|
|
286
|
+
* @param {string} framework - The framework name
|
|
287
|
+
*/
|
|
288
|
+
async function hydrateIsland(island, framework) {
|
|
289
|
+
if (island.dataset.hydrated) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const src = island.dataset.src;
|
|
294
|
+
const propsAttr = island.dataset.props;
|
|
295
|
+
|
|
296
|
+
if (!src) {
|
|
297
|
+
console.warn('Island missing data-src attribute');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const props = propsAttr ? JSON.parse(propsAttr) : {};
|
|
303
|
+
|
|
304
|
+
// CRITICAL: For Lit components, load hydration support BEFORE importing the component
|
|
305
|
+
// This ensures our patch is applied before @customElement decorator runs
|
|
306
|
+
if (framework === 'lit') {
|
|
307
|
+
// @ts-ignore - Vite resolves this virtual module at runtime
|
|
308
|
+
await import('/@useavalon/lit/client');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const componentModule = await import(/* @vite-ignore */ src);
|
|
312
|
+
const Component = resolveComponent(componentModule, src);
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const integrationModule = await loadIntegrationModule(framework);
|
|
316
|
+
|
|
317
|
+
if (!integrationModule.hydrate || typeof integrationModule.hydrate !== 'function') {
|
|
318
|
+
throw new Error(`Integration ${framework} does not export a hydrate function`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
integrationModule.hydrate(island, Component, props);
|
|
322
|
+
island.dataset.hydrated = 'true';
|
|
323
|
+
} catch (integrationError) {
|
|
324
|
+
if (import.meta.env?.DEV) {
|
|
325
|
+
console.error(`Integration hydration failed for ${framework}: ${src}`, integrationError);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
island.dataset.hydrationStatus = 'failed';
|
|
329
|
+
island.dataset.hydrationError = integrationError.message;
|
|
330
|
+
|
|
331
|
+
island.dispatchEvent(
|
|
332
|
+
new CustomEvent('hydration-error', {
|
|
333
|
+
detail: {
|
|
334
|
+
framework,
|
|
335
|
+
src,
|
|
336
|
+
error: integrationError.message,
|
|
337
|
+
timestamp: Date.now(),
|
|
338
|
+
hydrationType: 'integration-level',
|
|
339
|
+
},
|
|
340
|
+
bubbles: true,
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error(`❌ Critical error hydrating ${framework} island ${src}:`, error);
|
|
346
|
+
handleHydrationError(island, framework, src, error);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Handle hydration errors with graceful degradation
|
|
352
|
+
*
|
|
353
|
+
* @param {HTMLElement} island - The island element
|
|
354
|
+
* @param {string} framework - The framework name
|
|
355
|
+
* @param {string} src - The component source path
|
|
356
|
+
* @param {Error} error - The error that occurred
|
|
357
|
+
*/
|
|
358
|
+
function handleHydrationError(island, framework, src, error) {
|
|
359
|
+
console.error(`Hydration error for ${framework} island:`, {
|
|
360
|
+
src,
|
|
361
|
+
error: error.message,
|
|
362
|
+
stack: error.stack,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
island.dataset.hydrationStatus = 'failed';
|
|
366
|
+
island.dataset.renderStrategy = 'ssr-only';
|
|
367
|
+
island.classList.add('hydration-failed');
|
|
368
|
+
|
|
369
|
+
island.dispatchEvent(
|
|
370
|
+
new CustomEvent('hydration-error', {
|
|
371
|
+
detail: {
|
|
372
|
+
framework,
|
|
373
|
+
src,
|
|
374
|
+
error: error.message,
|
|
375
|
+
timestamp: Date.now(),
|
|
376
|
+
},
|
|
377
|
+
bubbles: true,
|
|
378
|
+
}),
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
if (isDevelopment()) {
|
|
382
|
+
addErrorIndicator(island, framework, src, error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Add visual error indicator in development mode
|
|
388
|
+
*
|
|
389
|
+
* @param {HTMLElement} island - The island element
|
|
390
|
+
* @param {string} framework - The framework name
|
|
391
|
+
* @param {string} src - The component source path
|
|
392
|
+
* @param {Error} error - The error that occurred
|
|
393
|
+
*/
|
|
394
|
+
function addErrorIndicator(island, framework, src, error) {
|
|
395
|
+
const indicator = document.createElement('div');
|
|
396
|
+
indicator.className = 'hydration-error-indicator';
|
|
397
|
+
indicator.style.cssText = `
|
|
398
|
+
position: absolute;
|
|
399
|
+
top: 0;
|
|
400
|
+
right: 0;
|
|
401
|
+
background: #ff4444;
|
|
402
|
+
color: white;
|
|
403
|
+
padding: 4px 8px;
|
|
404
|
+
font-size: 11px;
|
|
405
|
+
font-family: monospace;
|
|
406
|
+
border-radius: 0 0 0 4px;
|
|
407
|
+
z-index: 9999;
|
|
408
|
+
cursor: pointer;
|
|
409
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
410
|
+
`;
|
|
411
|
+
indicator.textContent = `❌ ${framework}`;
|
|
412
|
+
indicator.title = `Hydration failed: ${src}\n${error.message}\nClick for details`;
|
|
413
|
+
|
|
414
|
+
indicator.addEventListener('click', () => {
|
|
415
|
+
alert(
|
|
416
|
+
`Hydration Error\n\nFramework: ${framework}\nComponent: ${src}\n\nError: ${error.message}\n\nStack:\n${error.stack}`,
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const computedStyle = globalThis.getComputedStyle(island);
|
|
421
|
+
if (computedStyle.position === 'static') {
|
|
422
|
+
island.style.position = 'relative';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
island.appendChild(indicator);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Check if running in development mode
|
|
430
|
+
*
|
|
431
|
+
* @returns {boolean} True if in development
|
|
432
|
+
*/
|
|
433
|
+
function isDevelopment() {
|
|
434
|
+
return (
|
|
435
|
+
import.meta.env?.DEV ||
|
|
436
|
+
import.meta.env?.MODE === 'development' ||
|
|
437
|
+
globalThis.location?.hostname === 'localhost' ||
|
|
438
|
+
globalThis.location?.hostname === '127.0.0.1'
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Store island state before HMR update
|
|
444
|
+
* @param {HTMLElement} island - The island element
|
|
445
|
+
* @returns {object|null} The preserved state
|
|
446
|
+
*/
|
|
447
|
+
function preserveIslandState(island) {
|
|
448
|
+
const framework = island.dataset.framework;
|
|
449
|
+
const src = island.dataset.src;
|
|
450
|
+
|
|
451
|
+
if (!src) return null;
|
|
452
|
+
|
|
453
|
+
const state = {
|
|
454
|
+
framework,
|
|
455
|
+
src,
|
|
456
|
+
props: island.dataset.props,
|
|
457
|
+
scrollPosition: {
|
|
458
|
+
x: globalThis.scrollX,
|
|
459
|
+
y: globalThis.scrollY,
|
|
460
|
+
},
|
|
461
|
+
focusedElement: document.activeElement?.id || null,
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
if (framework === 'vue' && island.__vue__) {
|
|
466
|
+
state.vueData = structuredClone(island.__vue__.$data || {});
|
|
467
|
+
} else if (framework === 'svelte' && island.__svelte__) {
|
|
468
|
+
state.svelteState = island.__svelte__;
|
|
469
|
+
} else if (framework === 'lit' && island.tagName?.includes('-')) {
|
|
470
|
+
const litElement = island.querySelector('[data-lit-element]') || island;
|
|
471
|
+
if (litElement._$litElement$) {
|
|
472
|
+
state.litProperties = {};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.warn('Failed to preserve island state:', error);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return state;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Restore island state after HMR update
|
|
484
|
+
* @param {HTMLElement} island - The island element
|
|
485
|
+
* @param {object} state - The preserved state
|
|
486
|
+
*/
|
|
487
|
+
function restoreIslandState(island, state) {
|
|
488
|
+
if (!state) return;
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
if (state.scrollPosition) {
|
|
492
|
+
globalThis.scrollTo(state.scrollPosition.x, state.scrollPosition.y);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (state.focusedElement) {
|
|
496
|
+
const element = document.getElementById(state.focusedElement);
|
|
497
|
+
if (element) {
|
|
498
|
+
element.focus();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (island.dataset.framework === 'vue' && state.vueData && island.__vue__) {
|
|
503
|
+
Object.assign(island.__vue__.$data, state.vueData);
|
|
504
|
+
}
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.warn('Failed to restore island state:', error);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Hydrate an island with a fresh module (cache-busted)
|
|
512
|
+
* @param {HTMLElement} island - The island element
|
|
513
|
+
* @param {string} framework - The framework name
|
|
514
|
+
* @param {string} freshSrc - The cache-busted source path
|
|
515
|
+
* @param {string} originalSrc - The original source path
|
|
516
|
+
*/
|
|
517
|
+
async function hydrateIslandWithFreshModule(island, framework, freshSrc, originalSrc) {
|
|
518
|
+
const propsAttr = island.dataset.props;
|
|
519
|
+
const props = propsAttr ? JSON.parse(propsAttr) : {};
|
|
520
|
+
|
|
521
|
+
if (framework === 'lit') {
|
|
522
|
+
// @ts-ignore - Vite resolves this at runtime
|
|
523
|
+
await import('/@useavalon/lit/client');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const componentModule = await import(/* @vite-ignore */ freshSrc);
|
|
527
|
+
const Component = resolveComponent(componentModule, originalSrc);
|
|
528
|
+
|
|
529
|
+
const integrationModule = await loadIntegrationModule(framework);
|
|
530
|
+
|
|
531
|
+
if (!integrationModule.hydrate || typeof integrationModule.hydrate !== 'function') {
|
|
532
|
+
throw new Error(`Integration ${framework} does not export a hydrate function`);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
integrationModule.hydrate(island, Component, props);
|
|
536
|
+
island.dataset.hydrated = 'true';
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Show inline HMR error indicator
|
|
541
|
+
* @param {HTMLElement} island - The island element
|
|
542
|
+
* @param {string} framework - The framework name
|
|
543
|
+
* @param {string} src - The component source path
|
|
544
|
+
* @param {Error} error - The error that occurred
|
|
545
|
+
*/
|
|
546
|
+
function showInlineHMRError(island, framework, src, error) {
|
|
547
|
+
const existing = island.querySelector('.hmr-error-indicator');
|
|
548
|
+
if (existing) {
|
|
549
|
+
existing.remove();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const indicator = document.createElement('div');
|
|
553
|
+
indicator.className = 'hmr-error-indicator';
|
|
554
|
+
indicator.style.cssText = `
|
|
555
|
+
position: absolute;
|
|
556
|
+
top: 0;
|
|
557
|
+
left: 0;
|
|
558
|
+
right: 0;
|
|
559
|
+
background: linear-gradient(135deg, #ff6b6b, #ee5a5a);
|
|
560
|
+
color: white;
|
|
561
|
+
padding: 8px 12px;
|
|
562
|
+
font-size: 12px;
|
|
563
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
|
|
564
|
+
z-index: 10000;
|
|
565
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
566
|
+
display: flex;
|
|
567
|
+
align-items: center;
|
|
568
|
+
gap: 8px;
|
|
569
|
+
`;
|
|
570
|
+
|
|
571
|
+
const icon = document.createElement('span');
|
|
572
|
+
icon.textContent = '⚠️';
|
|
573
|
+
icon.style.fontSize = '14px';
|
|
574
|
+
|
|
575
|
+
const message = document.createElement('span');
|
|
576
|
+
message.style.flex = '1';
|
|
577
|
+
message.innerHTML = `<strong>HMR Failed:</strong> ${error.message.slice(0, 100)}${error.message.length > 100 ? '...' : ''}`;
|
|
578
|
+
|
|
579
|
+
const dismissBtn = document.createElement('button');
|
|
580
|
+
dismissBtn.textContent = '×';
|
|
581
|
+
dismissBtn.style.cssText = `
|
|
582
|
+
background: rgba(255,255,255,0.2);
|
|
583
|
+
border: none;
|
|
584
|
+
color: white;
|
|
585
|
+
width: 20px;
|
|
586
|
+
height: 20px;
|
|
587
|
+
border-radius: 50%;
|
|
588
|
+
cursor: pointer;
|
|
589
|
+
font-size: 14px;
|
|
590
|
+
line-height: 1;
|
|
591
|
+
`;
|
|
592
|
+
dismissBtn.onclick = () => indicator.remove();
|
|
593
|
+
|
|
594
|
+
indicator.appendChild(icon);
|
|
595
|
+
indicator.appendChild(message);
|
|
596
|
+
indicator.appendChild(dismissBtn);
|
|
597
|
+
|
|
598
|
+
const computedStyle = globalThis.getComputedStyle(island);
|
|
599
|
+
if (computedStyle.position === 'static') {
|
|
600
|
+
island.style.position = 'relative';
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
island.insertBefore(indicator, island.firstChild);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// HMR support for development
|
|
607
|
+
if (import.meta.hot) {
|
|
608
|
+
import.meta.hot.accept();
|
|
609
|
+
|
|
610
|
+
// Lazy HMR adapter registration - only load adapters for frameworks used on the page
|
|
611
|
+
import('./hmr-coordinator.js')
|
|
612
|
+
.then(async ({ initializeHMR, getHMRCoordinator }) => {
|
|
613
|
+
initializeHMR();
|
|
614
|
+
|
|
615
|
+
const coordinator = getHMRCoordinator();
|
|
616
|
+
|
|
617
|
+
// Discover which frameworks are actually used on this page
|
|
618
|
+
const usedFrameworks = new Set();
|
|
619
|
+
document.querySelectorAll('[data-framework]').forEach(island => {
|
|
620
|
+
const framework = island.dataset.framework;
|
|
621
|
+
if (framework) usedFrameworks.add(framework);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Only register adapters for frameworks that are used
|
|
625
|
+
// Adapters are loaded from their respective integration packages
|
|
626
|
+
const adapterLoaders = {
|
|
627
|
+
// @ts-ignore - Vite resolves these virtual modules at runtime
|
|
628
|
+
react: () => import('/@useavalon/react/client/hmr').then(m => m.reactAdapter),
|
|
629
|
+
// @ts-ignore - Vite resolves these virtual modules at runtime
|
|
630
|
+
preact: () => import('/@useavalon/preact/client/hmr').then(m => m.preactAdapter),
|
|
631
|
+
// @ts-ignore - Vite resolves these virtual modules at runtime
|
|
632
|
+
vue: () => import('/@useavalon/vue/client/hmr').then(m => m.vueAdapter),
|
|
633
|
+
// @ts-ignore - Vite resolves these virtual modules at runtime
|
|
634
|
+
svelte: () => import('/@useavalon/svelte/client/hmr').then(m => m.svelteAdapter),
|
|
635
|
+
// @ts-ignore - Vite resolves these virtual modules at runtime
|
|
636
|
+
solid: () => import('/@useavalon/solid/client/hmr').then(m => m.solidAdapter),
|
|
637
|
+
// @ts-ignore - Vite resolves these virtual modules at runtime
|
|
638
|
+
lit: () => import('/@useavalon/lit/client/hmr').then(m => m.litAdapter),
|
|
639
|
+
// @ts-ignore - Vite resolves these virtual modules at runtime
|
|
640
|
+
qwik: () => import('/@useavalon/qwik/client/hmr').then(m => m.qwikAdapter),
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
for (const framework of usedFrameworks) {
|
|
644
|
+
const loader = adapterLoaders[framework];
|
|
645
|
+
if (loader) {
|
|
646
|
+
try {
|
|
647
|
+
const adapter = await loader();
|
|
648
|
+
coordinator.registerAdapter(framework, adapter);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
console.warn(`[HMR] Failed to load adapter for ${framework}:`, error);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
})
|
|
655
|
+
.catch(error => {
|
|
656
|
+
console.error('[HMR] Failed to initialize:', error);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// Enhanced HMR support for nested islands
|
|
660
|
+
setupNestedIslandHMR();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Show HMR error feedback on the island
|
|
665
|
+
* @param {HTMLElement} island - The island element
|
|
666
|
+
* @param {string} hmrFramework - The framework name
|
|
667
|
+
* @param {string} hmrSrc - The component source path
|
|
668
|
+
* @param {Error} error - The error that occurred
|
|
669
|
+
*/
|
|
670
|
+
async function showHMRError(island, hmrFramework, hmrSrc, error) {
|
|
671
|
+
try {
|
|
672
|
+
const { showHMRErrorOverlay } = await import('./hmr-error-overlay.js');
|
|
673
|
+
showHMRErrorOverlay({
|
|
674
|
+
framework: hmrFramework,
|
|
675
|
+
src: hmrSrc,
|
|
676
|
+
error,
|
|
677
|
+
filePath: hmrSrc,
|
|
678
|
+
});
|
|
679
|
+
} catch {
|
|
680
|
+
showInlineHMRError(island, hmrFramework, hmrSrc, error);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Setup HMR support for nested island directories.
|
|
686
|
+
* Handles hot module replacement for islands in any discovered directory,
|
|
687
|
+
* including nested paths like /src/modules/[module]/islands/.
|
|
688
|
+
*/
|
|
689
|
+
function setupNestedIslandHMR() {
|
|
690
|
+
if (!import.meta.hot) return;
|
|
691
|
+
|
|
692
|
+
const hydratedIslands = new Map();
|
|
693
|
+
|
|
694
|
+
async function handleIslandHMR(modulePath) {
|
|
695
|
+
const normalizedPath = modulePath.replaceAll('\\', '/');
|
|
696
|
+
|
|
697
|
+
const islands = document.querySelectorAll(`[data-src*="${normalizedPath}"], [data-src$="${normalizedPath}"]`);
|
|
698
|
+
|
|
699
|
+
if (islands.length === 0) {
|
|
700
|
+
const allIslands = document.querySelectorAll('[data-src]');
|
|
701
|
+
for (const island of allIslands) {
|
|
702
|
+
const src = island.dataset.src;
|
|
703
|
+
if (src && (src.includes(normalizedPath) || normalizedPath.includes(src.replace(/^\//, '')))) {
|
|
704
|
+
await rehydrateIsland(island);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
for (const island of islands) {
|
|
711
|
+
await rehydrateIsland(island);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async function rehydrateIsland(island) {
|
|
716
|
+
const framework = island.dataset.framework;
|
|
717
|
+
const src = island.dataset.src;
|
|
718
|
+
|
|
719
|
+
if (!src || !framework) return;
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
const state = preserveIslandState(island);
|
|
723
|
+
hydratedIslands.set(src, state);
|
|
724
|
+
|
|
725
|
+
delete island.dataset.hydrated;
|
|
726
|
+
delete island.dataset.hydrationStatus;
|
|
727
|
+
|
|
728
|
+
const errorIndicator = island.querySelector('.hydration-error-indicator');
|
|
729
|
+
if (errorIndicator) {
|
|
730
|
+
errorIndicator.remove();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const timestamp = Date.now();
|
|
734
|
+
const freshSrc = src.includes('?') ? `${src}&t=${timestamp}` : `${src}?t=${timestamp}`;
|
|
735
|
+
|
|
736
|
+
await hydrateIslandWithFreshModule(island, framework, freshSrc, src);
|
|
737
|
+
|
|
738
|
+
const preservedState = hydratedIslands.get(src);
|
|
739
|
+
if (preservedState) {
|
|
740
|
+
restoreIslandState(island, preservedState);
|
|
741
|
+
hydratedIslands.delete(src);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
island.dispatchEvent(
|
|
745
|
+
new CustomEvent('hmr-update', {
|
|
746
|
+
detail: {
|
|
747
|
+
framework,
|
|
748
|
+
src,
|
|
749
|
+
timestamp: Date.now(),
|
|
750
|
+
success: true,
|
|
751
|
+
},
|
|
752
|
+
bubbles: true,
|
|
753
|
+
}),
|
|
754
|
+
);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.error(`[HMR] Failed for ${framework} island ${src}:`, error);
|
|
757
|
+
|
|
758
|
+
island.dispatchEvent(
|
|
759
|
+
new CustomEvent('hmr-error', {
|
|
760
|
+
detail: {
|
|
761
|
+
framework,
|
|
762
|
+
src,
|
|
763
|
+
error: error.message,
|
|
764
|
+
timestamp: Date.now(),
|
|
765
|
+
},
|
|
766
|
+
bubbles: true,
|
|
767
|
+
}),
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
if (isDevelopment()) {
|
|
771
|
+
showHMRError(island, framework, src, error);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Listen for Vite HMR events
|
|
777
|
+
import.meta.hot.on('vite:beforeUpdate', payload => {
|
|
778
|
+
for (const update of payload.updates || []) {
|
|
779
|
+
const path = update.path || update.acceptedPath;
|
|
780
|
+
if (path && (path.includes('/islands/') || path.includes('\\islands\\'))) {
|
|
781
|
+
handleIslandHMR(path);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// Handle full page reloads for islands
|
|
787
|
+
import.meta.hot.on('vite:beforeFullReload', () => {
|
|
788
|
+
const islands = document.querySelectorAll('[data-hydrated="true"]');
|
|
789
|
+
const states = {};
|
|
790
|
+
|
|
791
|
+
for (const island of islands) {
|
|
792
|
+
const src = island.dataset.src;
|
|
793
|
+
if (src) {
|
|
794
|
+
states[src] = preserveIslandState(island);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
try {
|
|
799
|
+
sessionStorage.setItem('__avalon_hmr_states__', JSON.stringify(states));
|
|
800
|
+
} catch {
|
|
801
|
+
// sessionStorage might not be available
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Restore states after page load (for full reloads)
|
|
806
|
+
try {
|
|
807
|
+
const savedStates = sessionStorage.getItem('__avalon_hmr_states__');
|
|
808
|
+
if (savedStates) {
|
|
809
|
+
const states = JSON.parse(savedStates);
|
|
810
|
+
sessionStorage.removeItem('__avalon_hmr_states__');
|
|
811
|
+
|
|
812
|
+
setTimeout(() => {
|
|
813
|
+
for (const [src, state] of Object.entries(states)) {
|
|
814
|
+
const island = document.querySelector(`[data-src="${src}"]`);
|
|
815
|
+
if (island && state) {
|
|
816
|
+
restoreIslandState(island, state);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}, 100);
|
|
820
|
+
}
|
|
821
|
+
} catch {
|
|
822
|
+
// Ignore errors - state restoration is best-effort
|
|
823
|
+
}
|
|
824
|
+
}
|