@useavalon/avalon 0.1.9 → 0.1.10

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.
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Preact HMR Adapter
3
+ *
4
+ * Provides Hot Module Replacement support for Preact components.
5
+ * Integrates with @preact/preset-vite to preserve hooks state and component tree during updates.
6
+ *
7
+ * Requirements: 2.2
8
+ */
9
+ /// <reference lib="dom" />
10
+ import { BaseFrameworkAdapter } from "../framework-adapter.js";
11
+ /**
12
+ * Preact HMR Adapter
13
+ *
14
+ * Leverages Preact's HMR capabilities (provided by @preact/preset-vite) to:
15
+ * - Preserve hooks state across updates
16
+ * - Maintain component tree structure
17
+ * - Handle component updates without full remount
18
+ *
19
+ * Preact HMR works similarly to React Fast Refresh:
20
+ * 1. Detecting when a component module is updated
21
+ * 2. Preserving the component tree (internal state representation)
22
+ * 3. Re-rendering with the new component definition
23
+ * 4. Restoring hooks state from the preserved tree
24
+ */
25
+ export class PreactHMRAdapter extends BaseFrameworkAdapter {
26
+ name = "preact";
27
+ /**
28
+ * Store Preact component instances for each island to enable proper updates
29
+ */
30
+ instances = new WeakMap();
31
+ /**
32
+ * Check if a component is a Preact component
33
+ *
34
+ * Preact components can be:
35
+ * - Function components (including hooks)
36
+ * - Class components (extending Preact Component)
37
+ * - Forward refs
38
+ * - Memoized components
39
+ *
40
+ * Preact is API-compatible with React, so detection is similar
41
+ */
42
+ canHandle(component) {
43
+ if (!component) return false;
44
+ // Check if it's a function (function component or class)
45
+ if (typeof component === "function") {
46
+ // Check for Preact/React-specific properties on the function
47
+ const comp = component;
48
+ // Class components have isReactComponent on prototype
49
+ // (Preact uses the same marker for compatibility)
50
+ const proto = component.prototype;
51
+ if (proto && proto.isReactComponent) {
52
+ return true;
53
+ }
54
+ // Check for Preact element symbol (for wrapped components)
55
+ if (comp.$typeof) {
56
+ return true;
57
+ }
58
+ // Assume any function could be a Preact component
59
+ // Preact HMR will handle validation
60
+ return true;
61
+ }
62
+ // Check for Preact VNode types
63
+ if (typeof component !== "object") {
64
+ return false;
65
+ }
66
+ const obj = component;
67
+ // Check for Preact VNode symbol
68
+ if (obj.$typeof) {
69
+ return true;
70
+ }
71
+ // Check for wrapped components (HOCs, memo, forwardRef)
72
+ if (obj.type && typeof obj.type === "function") {
73
+ return true;
74
+ }
75
+ return false;
76
+ }
77
+ /**
78
+ * Preserve Preact component state before HMR update
79
+ *
80
+ * Preact HMR handles most state preservation automatically through
81
+ * the component tree. We capture additional DOM state and props for fallback.
82
+ */
83
+ preserveState(island) {
84
+ try {
85
+ // Get base DOM state
86
+ const baseSnapshot = super.preserveState(island);
87
+ if (!baseSnapshot) return null;
88
+ // Get Preact-specific data
89
+ const propsAttr = island.getAttribute("data-props");
90
+ const capturedProps = propsAttr ? JSON.parse(propsAttr) : {};
91
+ // Try to get component name from the island
92
+ const src = island.getAttribute("data-src") || "";
93
+ const componentName = this.extractComponentName(src);
94
+ const preactSnapshot = {
95
+ ...baseSnapshot,
96
+ framework: "preact",
97
+ data: {
98
+ componentName,
99
+ capturedProps
100
+ }
101
+ };
102
+ return preactSnapshot;
103
+ } catch (error) {
104
+ console.warn("Failed to preserve Preact state:", error);
105
+ return null;
106
+ }
107
+ }
108
+ /**
109
+ * Update Preact component with HMR
110
+ *
111
+ * This method integrates with Preact HMR:
112
+ * 1. Preact HMR is automatically enabled by @preact/preset-vite
113
+ * 2. When a module updates, Vite sends an HMR event
114
+ * 3. We re-hydrate the component with the new definition
115
+ * 4. Preact HMR preserves hooks state automatically
116
+ * 5. The component re-renders with preserved state
117
+ */
118
+ async update(island, newComponent, props) {
119
+ if (!this.canHandle(newComponent)) {
120
+ throw new Error("Component is not a valid Preact component");
121
+ }
122
+ const Component = newComponent;
123
+ try {
124
+ // Dynamically import Preact at runtime
125
+ // This is resolved by Vite in the browser
126
+ const preactModule = await import("preact");
127
+ const { h, hydrate } = preactModule;
128
+ // Check if we have an existing instance
129
+ const existingInstance = this.instances.get(island);
130
+ if (existingInstance) {
131
+ // Update existing component
132
+ // Preact HMR will preserve hooks state automatically
133
+ const vnode = h(Component, props);
134
+ hydrate(vnode, island);
135
+ } else {
136
+ // Create new instance and hydrate
137
+ // This happens on first HMR update or if instance was lost
138
+ const vnode = h(Component, props);
139
+ hydrate(vnode, island);
140
+ // Store instance for future updates
141
+ this.instances.set(island, Component);
142
+ }
143
+ // Mark as hydrated
144
+ island.setAttribute("data-hydrated", "true");
145
+ island.setAttribute("data-hydration-status", "success");
146
+ } catch (error) {
147
+ console.error("Preact HMR update failed:", error);
148
+ island.setAttribute("data-hydration-status", "error");
149
+ throw error;
150
+ }
151
+ }
152
+ /**
153
+ * Restore Preact component state after HMR update
154
+ *
155
+ * Preact HMR handles most state restoration automatically.
156
+ * We restore DOM state (scroll, focus, form values) as a supplement.
157
+ */
158
+ restoreState(island, state) {
159
+ try {
160
+ // Restore DOM state (scroll, focus, form values)
161
+ super.restoreState(island, state);
162
+ } catch (error) {
163
+ console.warn("Failed to restore Preact state:", error);
164
+ }
165
+ }
166
+ /**
167
+ * Handle errors during Preact HMR update
168
+ *
169
+ * Provides Preact-specific error handling with helpful messages
170
+ */
171
+ handleError(island, error) {
172
+ console.error("Preact HMR error:", error);
173
+ // Use base error handling
174
+ super.handleError(island, error);
175
+ // Add Preact-specific error information
176
+ const errorIndicator = island.querySelector(".hmr-error-indicator");
177
+ if (errorIndicator) {
178
+ const errorMessage = error.message;
179
+ // Provide helpful hints for common Preact errors
180
+ let hint = "";
181
+ if (errorMessage.includes("hooks")) {
182
+ hint = " (Hint: Check hooks usage - hooks must be called in the same order)";
183
+ } else if (errorMessage.includes("render")) {
184
+ hint = " (Hint: Check component render method for errors)";
185
+ } else if (errorMessage.includes("hydration") || errorMessage.includes("hydrate")) {
186
+ hint = " (Hint: Server and client render must match)";
187
+ }
188
+ errorIndicator.textContent = `Preact HMR Error: ${errorMessage}${hint}`;
189
+ }
190
+ }
191
+ /**
192
+ * Extract component name from source path
193
+ * Used for debugging and error messages
194
+ */
195
+ extractComponentName(src) {
196
+ const parts = src.split("/");
197
+ const filename = parts[parts.length - 1];
198
+ return filename.replace(/\.(tsx?|jsx?)$/, "");
199
+ }
200
+ /**
201
+ * Clean up Preact component when island is removed
202
+ * This should be called when an island is unmounted
203
+ */
204
+ unmount(island) {
205
+ const instance = this.instances.get(island);
206
+ if (instance) {
207
+ try {
208
+ // Preact doesn't have an explicit unmount API like React
209
+ // We just clear the instance reference
210
+ this.instances.delete(island);
211
+ // Clear the island content to trigger cleanup
212
+ // Preact will handle component lifecycle cleanup
213
+ island.innerHTML = "";
214
+ } catch (error) {
215
+ console.warn("Failed to unmount Preact component:", error);
216
+ }
217
+ }
218
+ }
219
+ }
220
+ /**
221
+ * Create and export a singleton instance of the Preact HMR adapter
222
+ */
223
+ export const preactAdapter = new PreactHMRAdapter();
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Qwik HMR Adapter
3
+ *
4
+ * Provides Hot Module Replacement support for Qwik components.
5
+ * Qwik is fundamentally different from other frameworks — it uses resumability
6
+ * instead of hydration. Components are serialized on the server and resumed
7
+ * on the client without replaying application logic.
8
+ *
9
+ * Key Qwik concepts:
10
+ * - Resumability: No hydration step — the app resumes from serialized state
11
+ * - Lazy loading via $: Code is split at $ boundaries and loaded on demand
12
+ * - Containers: Qwik apps run inside container elements with q:container attribute
13
+ * - Qwikloader: A tiny (~1KB) script that sets up global event listeners
14
+ *
15
+ * Requirements: 2.1
16
+ */
17
+ /// <reference lib="dom" />
18
+ import { BaseFrameworkAdapter } from "../framework-adapter.js";
19
+ /**
20
+ * Qwik HMR Adapter
21
+ *
22
+ * Unlike other frameworks, Qwik doesn't hydrate — it resumes. This adapter
23
+ * handles HMR by re-rendering the container rather than performing traditional
24
+ * hydration-based hot updates.
25
+ *
26
+ * During development, Qwik's optimizer (via vite-plugin-qwik) handles most
27
+ * of the HMR heavy lifting. This adapter provides the island-level integration
28
+ * for Avalon's HMR coordinator.
29
+ */
30
+ export class QwikHMRAdapter extends BaseFrameworkAdapter {
31
+ name = "qwik";
32
+ /**
33
+ * Track container elements for cleanup
34
+ */
35
+ containers = new WeakMap();
36
+ /**
37
+ * Check if a component is a Qwik component
38
+ *
39
+ * Qwik components are created with component$() and have distinctive markers:
40
+ * - __brand or __qrl property from the Qwik optimizer
41
+ * - $ suffix convention in source
42
+ * - QRL (Qwik Resource Locator) references
43
+ */
44
+ canHandle(component) {
45
+ if (!component) return false;
46
+ // Check if it's a function (Qwik components are functions)
47
+ if (typeof component === "function") {
48
+ const comp = component;
49
+ // Check for Qwik-specific markers set by the optimizer
50
+ if (comp.__brand === "QwikComponent" || comp.__qrl) {
51
+ return true;
52
+ }
53
+ // Check for QRL wrapper pattern
54
+ if (comp.getSymbol || comp.getHash) {
55
+ return true;
56
+ }
57
+ try {
58
+ const funcStr = component.toString();
59
+ // Look for Qwik-specific compiled patterns
60
+ if (funcStr.includes("component$") || funcStr.includes("qrl") || funcStr.includes("useSignal") || funcStr.includes("useStore") || funcStr.includes("useTask$") || funcStr.includes("useVisibleTask$") || funcStr.includes("_qrl") || funcStr.includes("qwik")) {
61
+ return true;
62
+ }
63
+ } catch {}
64
+ }
65
+ // Check if it's a Qwik component object (wrapped or exported)
66
+ if (typeof component === "object" && component !== null) {
67
+ const obj = component;
68
+ // Check for default export pattern
69
+ if (obj.default && typeof obj.default === "function") {
70
+ return this.canHandle(obj.default);
71
+ }
72
+ // Check for Qwik QRL markers
73
+ if (obj.__qrl || obj.__brand === "QwikComponent") {
74
+ return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+ /**
80
+ * Preserve Qwik component state before HMR update
81
+ *
82
+ * Qwik serializes state into the DOM via q:container attributes and
83
+ * inline <script type="qwik/json"> blocks. We capture this serialized
84
+ * state so it can be used during re-rendering.
85
+ */
86
+ preserveState(island) {
87
+ try {
88
+ const baseSnapshot = super.preserveState(island);
89
+ if (!baseSnapshot) return null;
90
+ // Get props from the island
91
+ const propsAttr = island.getAttribute("data-props");
92
+ const capturedProps = propsAttr ? JSON.parse(propsAttr) : {};
93
+ const src = island.getAttribute("data-src") || "";
94
+ const componentName = this.extractComponentName(src);
95
+ // Capture Qwik container state — serialized in the DOM
96
+ const containerEl = island.closest("[q\\:container]") || island;
97
+ const containerState = containerEl.querySelector("script[type=\"qwik/json\"]")?.textContent || null;
98
+ // Capture q: attributes that hold container metadata
99
+ const qContainerAttrs = {};
100
+ const attrs = containerEl.attributes;
101
+ if (attrs) {
102
+ for (let i = 0; i < attrs.length; i++) {
103
+ const attr = attrs[i];
104
+ if (attr.name.startsWith("q:")) {
105
+ qContainerAttrs[attr.name] = attr.value;
106
+ }
107
+ }
108
+ }
109
+ return {
110
+ ...baseSnapshot,
111
+ framework: "qwik",
112
+ data: {
113
+ componentName,
114
+ capturedProps,
115
+ containerState,
116
+ qContainerAttrs
117
+ }
118
+ };
119
+ } catch (error) {
120
+ console.warn("Failed to preserve Qwik state:", error);
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Update Qwik component with HMR
126
+ *
127
+ * Qwik's resumability model means we don't hydrate in the traditional sense.
128
+ * Instead, during HMR:
129
+ * 1. The Qwik optimizer (vite-plugin-qwik) invalidates the affected QRLs
130
+ * 2. We re-render the component into the island container
131
+ * 3. Qwikloader picks up the new event bindings automatically
132
+ * 4. Serialized state is restored from the container
133
+ */
134
+ async update(island, newComponent, props) {
135
+ if (!this.canHandle(newComponent)) {
136
+ throw new Error("Component is not a valid Qwik component");
137
+ }
138
+ // Extract the actual component function
139
+ let Component;
140
+ if (typeof newComponent === "object" && newComponent !== null) {
141
+ const obj = newComponent;
142
+ if (obj.default && typeof obj.default === "function") {
143
+ Component = obj.default;
144
+ } else {
145
+ throw new Error("Qwik component object must have a default export");
146
+ }
147
+ } else if (typeof newComponent === "function") {
148
+ Component = newComponent;
149
+ } else {
150
+ throw new Error("Invalid Qwik component type");
151
+ }
152
+ try {
153
+ // Clean up existing container tracking
154
+ const existing = this.containers.get(island);
155
+ if (existing?.cleanup) {
156
+ try {
157
+ existing.cleanup();
158
+ } catch (error) {
159
+ console.warn("Failed to clean up existing Qwik container:", error);
160
+ }
161
+ }
162
+ // Dynamically import Qwik's client render API
163
+ // In development, vite-plugin-qwik makes this available
164
+ // Use a variable to prevent Vite's static import analysis from resolving this at build time
165
+ const qwikId = "@builder.io/qwik";
166
+ const qwikModule = await import(
167
+ /* @vite-ignore */
168
+ qwikId
169
+ );
170
+ if (qwikModule.render) {
171
+ // Use Qwik's client-side render to mount the component
172
+ // Qwik's render handles setting up the container and resumability
173
+ const jsxNode = typeof qwikModule.jsx === "function" ? qwikModule.jsx(Component, props) : Component(props);
174
+ await qwikModule.render(island, jsxNode);
175
+ } else {
176
+ // Fallback: replace innerHTML and let Qwikloader resume
177
+ // This works because Qwik's event listeners are declarative in the DOM
178
+ console.warn("Qwik render API not available, using innerHTML fallback");
179
+ const result = Component(props);
180
+ if (typeof result === "string") {
181
+ island.innerHTML = result;
182
+ }
183
+ }
184
+ // Track the container for future cleanup
185
+ this.containers.set(island, {});
186
+ // Mark as hydrated (resumed, in Qwik terms)
187
+ island.setAttribute("data-hydrated", "true");
188
+ island.setAttribute("data-hydration-status", "success");
189
+ } catch (error) {
190
+ console.error("Qwik HMR update failed:", error);
191
+ island.setAttribute("data-hydration-status", "error");
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * Restore Qwik component state after HMR update
197
+ *
198
+ * Qwik's serialized state lives in the DOM, so restoring the container
199
+ * state script block and q: attributes is sufficient for the framework
200
+ * to resume correctly.
201
+ */
202
+ restoreState(island, state) {
203
+ try {
204
+ // Restore DOM state (scroll, focus, form values)
205
+ super.restoreState(island, state);
206
+ } catch (error) {
207
+ console.warn("Failed to restore Qwik state:", error);
208
+ }
209
+ }
210
+ /**
211
+ * Handle errors during Qwik HMR update
212
+ */
213
+ handleError(island, error) {
214
+ console.error("Qwik HMR error:", error);
215
+ super.handleError(island, error);
216
+ const errorIndicator = island.querySelector(".hmr-error-indicator");
217
+ if (errorIndicator) {
218
+ const errorMessage = error.message;
219
+ let hint = "";
220
+ if (errorMessage.includes("component$") || errorMessage.includes("component\\$")) {
221
+ hint = " (Hint: Ensure component is wrapped with component$())";
222
+ } else if (errorMessage.includes("useSignal") || errorMessage.includes("useStore")) {
223
+ hint = " (Hint: Qwik hooks must be called inside component$() body)";
224
+ } else if (errorMessage.includes("QRL") || errorMessage.includes("qrl")) {
225
+ hint = " (Hint: Check that lazy-loaded boundaries use $ correctly)";
226
+ } else if (errorMessage.includes("serialize") || errorMessage.includes("container")) {
227
+ hint = " (Hint: Ensure all state is serializable — Qwik serializes state to the DOM)";
228
+ } else if (errorMessage.includes("resumable") || errorMessage.includes("resume")) {
229
+ hint = " (Hint: Server and client container state must match for resumability)";
230
+ }
231
+ errorIndicator.textContent = `Qwik HMR Error: ${errorMessage}${hint}`;
232
+ }
233
+ }
234
+ /**
235
+ * Extract component name from source path
236
+ */
237
+ extractComponentName(src) {
238
+ const parts = src.split("/");
239
+ const filename = parts[parts.length - 1];
240
+ return filename.replace(/\.qwik\.(tsx?|jsx?)$/, "").replace(/\.(tsx?|jsx?)$/, "");
241
+ }
242
+ /**
243
+ * Clean up Qwik component when island is removed
244
+ */
245
+ unmount(island) {
246
+ const container = this.containers.get(island);
247
+ if (container) {
248
+ try {
249
+ if (container.cleanup) {
250
+ container.cleanup();
251
+ }
252
+ this.containers.delete(island);
253
+ } catch (error) {
254
+ console.warn("Failed to unmount Qwik component:", error);
255
+ }
256
+ }
257
+ }
258
+ }
259
+ export const qwikAdapter = new QwikHMRAdapter();
@@ -0,0 +1,220 @@
1
+ /**
2
+ * React HMR Adapter
3
+ *
4
+ * Provides Hot Module Replacement support for React components using React Fast Refresh.
5
+ * Integrates with @vitejs/plugin-react to preserve hooks state and component tree during updates.
6
+ *
7
+ * Requirements: 2.1
8
+ */
9
+ /// <reference lib="dom" />
10
+ import { BaseFrameworkAdapter } from "../framework-adapter.js";
11
+ /**
12
+ * React HMR Adapter
13
+ *
14
+ * Leverages React Fast Refresh (provided by @vitejs/plugin-react) to:
15
+ * - Preserve hooks state across updates
16
+ * - Maintain component tree structure
17
+ * - Handle component updates without full remount
18
+ *
19
+ * React Fast Refresh works by:
20
+ * 1. Detecting when a component module is updated
21
+ * 2. Preserving the React Fiber tree (internal state representation)
22
+ * 3. Re-rendering with the new component definition
23
+ * 4. Restoring hooks state from the preserved Fiber tree
24
+ */
25
+ export class ReactHMRAdapter extends BaseFrameworkAdapter {
26
+ name = "react";
27
+ /**
28
+ * Store React roots for each island to enable proper updates
29
+ */
30
+ roots = new WeakMap();
31
+ /**
32
+ * Check if a component is a React component
33
+ *
34
+ * React components can be:
35
+ * - Function components (including hooks)
36
+ * - Class components (extending React.Component)
37
+ * - Forward refs
38
+ * - Memoized components
39
+ */
40
+ canHandle(component) {
41
+ if (!component) return false;
42
+ // Check if it's a function (function component or class)
43
+ if (typeof component === "function") {
44
+ // Check for React-specific properties on the function
45
+ // Use unknown first to avoid type errors
46
+ const comp = component;
47
+ // Class components have isReactComponent on prototype
48
+ const proto = component.prototype;
49
+ if (proto && proto.isReactComponent) {
50
+ return true;
51
+ }
52
+ // Check for React element symbol (for wrapped components)
53
+ if (comp.$$typeof) {
54
+ return true;
55
+ }
56
+ // Assume any function could be a React component
57
+ // React Fast Refresh will handle validation
58
+ return true;
59
+ }
60
+ // Check for React element types
61
+ if (typeof component !== "object") {
62
+ return false;
63
+ }
64
+ const obj = component;
65
+ // Check for React element symbol
66
+ if (obj.$$typeof) {
67
+ return true;
68
+ }
69
+ // Check for wrapped components (HOCs, memo, forwardRef)
70
+ if (obj.type && typeof obj.type === "function") {
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+ /**
76
+ * Preserve React component state before HMR update
77
+ *
78
+ * React Fast Refresh handles most state preservation automatically through
79
+ * the React Fiber tree. We capture additional DOM state and props for fallback.
80
+ */
81
+ preserveState(island) {
82
+ try {
83
+ // Get base DOM state
84
+ const baseSnapshot = super.preserveState(island);
85
+ if (!baseSnapshot) return null;
86
+ // Get React-specific data
87
+ const propsAttr = island.getAttribute("data-props");
88
+ const capturedProps = propsAttr ? JSON.parse(propsAttr) : {};
89
+ // Try to get component name from the island
90
+ const src = island.getAttribute("data-src") || "";
91
+ const componentName = this.extractComponentName(src);
92
+ const reactSnapshot = {
93
+ ...baseSnapshot,
94
+ framework: "react",
95
+ data: {
96
+ componentName,
97
+ capturedProps
98
+ }
99
+ };
100
+ return reactSnapshot;
101
+ } catch (error) {
102
+ console.warn("Failed to preserve React state:", error);
103
+ return null;
104
+ }
105
+ }
106
+ /**
107
+ * Update React component with HMR
108
+ *
109
+ * This method integrates with React Fast Refresh:
110
+ * 1. React Fast Refresh is automatically enabled by @vitejs/plugin-react
111
+ * 2. When a module updates, Vite sends an HMR event
112
+ * 3. We re-hydrate the component with the new definition
113
+ * 4. React Fast Refresh preserves hooks state automatically
114
+ * 5. The component re-renders with preserved state
115
+ */
116
+ async update(island, newComponent, props) {
117
+ if (!this.canHandle(newComponent)) {
118
+ throw new Error("Component is not a valid React component");
119
+ }
120
+ const Component = newComponent;
121
+ try {
122
+ // Dynamically import React and ReactDOM at runtime
123
+ // These are resolved by Vite in the browser
124
+ const [reactModule, reactDOMModule] = await Promise.all([import("react"), import("react-dom/client")]);
125
+ const { createElement } = reactModule;
126
+ const { hydrateRoot } = reactDOMModule;
127
+ // Check if we have an existing root
128
+ const existingRoot = this.roots.get(island);
129
+ if (existingRoot) {
130
+ // Update existing root with new component
131
+ // React Fast Refresh will preserve hooks state automatically
132
+ const element = createElement(Component, props);
133
+ existingRoot.render(element);
134
+ } else {
135
+ // Create new root and hydrate
136
+ // This happens on first HMR update or if root was lost
137
+ const element = createElement(Component, props);
138
+ const newRoot = hydrateRoot(island, element, { onRecoverableError: (error) => {
139
+ console.warn("React hydration recoverable error during HMR:", error);
140
+ } });
141
+ // Store root for future updates
142
+ this.roots.set(island, newRoot);
143
+ }
144
+ // Mark as hydrated
145
+ island.setAttribute("data-hydrated", "true");
146
+ island.setAttribute("data-hydration-status", "success");
147
+ } catch (error) {
148
+ console.error("React HMR update failed:", error);
149
+ island.setAttribute("data-hydration-status", "error");
150
+ throw error;
151
+ }
152
+ }
153
+ /**
154
+ * Restore React component state after HMR update
155
+ *
156
+ * React Fast Refresh handles most state restoration automatically.
157
+ * We restore DOM state (scroll, focus, form values) as a supplement.
158
+ */
159
+ restoreState(island, state) {
160
+ try {
161
+ // Restore DOM state (scroll, focus, form values)
162
+ super.restoreState(island, state);
163
+ } catch (error) {
164
+ console.warn("Failed to restore React state:", error);
165
+ }
166
+ }
167
+ /**
168
+ * Handle errors during React HMR update
169
+ *
170
+ * Provides React-specific error handling with helpful messages
171
+ */
172
+ handleError(island, error) {
173
+ console.error("React HMR error:", error);
174
+ // Use base error handling
175
+ super.handleError(island, error);
176
+ // Add React-specific error information
177
+ const errorIndicator = island.querySelector(".hmr-error-indicator");
178
+ if (errorIndicator) {
179
+ const errorMessage = error.message;
180
+ // Provide helpful hints for common React errors
181
+ let hint = "";
182
+ if (errorMessage.includes("hooks")) {
183
+ hint = " (Hint: Check hooks usage - hooks must be called in the same order)";
184
+ } else if (errorMessage.includes("render")) {
185
+ hint = " (Hint: Check component render method for errors)";
186
+ } else if (errorMessage.includes("hydration")) {
187
+ hint = " (Hint: Server and client render must match)";
188
+ }
189
+ errorIndicator.textContent = `React HMR Error: ${errorMessage}${hint}`;
190
+ }
191
+ }
192
+ /**
193
+ * Extract component name from source path
194
+ * Used for debugging and error messages
195
+ */
196
+ extractComponentName(src) {
197
+ const parts = src.split("/");
198
+ const filename = parts[parts.length - 1];
199
+ return filename.replace(/\.(tsx?|jsx?)$/, "");
200
+ }
201
+ /**
202
+ * Clean up React root when island is removed
203
+ * This should be called when an island is unmounted
204
+ */
205
+ unmount(island) {
206
+ const root = this.roots.get(island);
207
+ if (root) {
208
+ try {
209
+ root.unmount();
210
+ this.roots.delete(island);
211
+ } catch (error) {
212
+ console.warn("Failed to unmount React root:", error);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ /**
218
+ * Create and export a singleton instance of the React HMR adapter
219
+ */
220
+ export const reactAdapter = new ReactHMRAdapter();