@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.
- package/README.md +54 -54
- package/mod.ts +302 -302
- package/package.json +49 -26
- package/src/build/integration-bundler-plugin.ts +116 -116
- package/src/build/integration-config.ts +168 -168
- package/src/build/integration-detection-plugin.ts +117 -117
- package/src/build/integration-resolver-plugin.ts +90 -90
- package/src/build/island-manifest.ts +269 -269
- package/src/build/island-types-generator.ts +476 -476
- package/src/build/mdx-island-transform.ts +464 -464
- package/src/build/mdx-plugin.ts +98 -98
- package/src/build/page-island-transform.ts +598 -598
- package/src/build/prop-extractors/index.ts +21 -21
- package/src/build/prop-extractors/lit.ts +140 -140
- package/src/build/prop-extractors/qwik.ts +16 -16
- package/src/build/prop-extractors/solid.ts +125 -125
- package/src/build/prop-extractors/svelte.ts +194 -194
- package/src/build/prop-extractors/vue.ts +111 -111
- package/src/build/sidecar-file-manager.ts +104 -104
- package/src/build/sidecar-renderer.ts +30 -30
- package/src/client/adapters/index.ts +21 -13
- package/src/client/components.ts +35 -35
- package/src/client/css-hmr-handler.ts +344 -344
- package/src/client/framework-adapter.ts +462 -462
- package/src/client/hmr-coordinator.ts +396 -396
- package/src/client/hmr-error-overlay.js +533 -533
- package/src/client/main.js +824 -816
- package/src/client/types/framework-runtime.d.ts +68 -68
- package/src/client/types/vite-hmr.d.ts +46 -46
- package/src/client/types/vite-virtual-modules.d.ts +70 -60
- package/src/components/Image.tsx +123 -123
- package/src/components/IslandErrorBoundary.tsx +145 -145
- package/src/components/LayoutDataErrorBoundary.tsx +141 -141
- package/src/components/LayoutErrorBoundary.tsx +127 -127
- package/src/components/PersistentIsland.tsx +52 -52
- package/src/components/StreamingErrorBoundary.tsx +233 -233
- package/src/components/StreamingLayout.tsx +538 -538
- package/src/core/components/component-analyzer.ts +192 -192
- package/src/core/components/component-detection.ts +508 -508
- package/src/core/components/enhanced-framework-detector.ts +500 -500
- package/src/core/components/framework-registry.ts +563 -563
- package/src/core/content/mdx-processor.ts +46 -46
- package/src/core/integrations/index.ts +19 -19
- package/src/core/integrations/loader.ts +125 -125
- package/src/core/integrations/registry.ts +175 -175
- package/src/core/islands/island-persistence.ts +325 -325
- package/src/core/islands/island-state-serializer.ts +258 -258
- package/src/core/islands/persistent-island-context.tsx +80 -80
- package/src/core/islands/use-persistent-state.ts +68 -68
- package/src/core/layout/enhanced-layout-resolver.ts +322 -322
- package/src/core/layout/layout-cache-manager.ts +485 -485
- package/src/core/layout/layout-composer.ts +357 -357
- package/src/core/layout/layout-data-loader.ts +516 -516
- package/src/core/layout/layout-discovery.ts +243 -243
- package/src/core/layout/layout-matcher.ts +299 -299
- package/src/core/layout/layout-types.ts +110 -110
- package/src/core/modules/framework-module-resolver.ts +273 -273
- package/src/islands/component-analysis.ts +213 -213
- package/src/islands/css-utils.ts +565 -565
- package/src/islands/discovery/index.ts +80 -80
- package/src/islands/discovery/registry.ts +340 -340
- package/src/islands/discovery/resolver.ts +477 -477
- package/src/islands/discovery/scanner.ts +386 -386
- package/src/islands/discovery/types.ts +117 -117
- package/src/islands/discovery/validator.ts +544 -544
- package/src/islands/discovery/watcher.ts +368 -368
- package/src/islands/framework-detection.ts +428 -428
- package/src/islands/integration-loader.ts +490 -490
- package/src/islands/island.tsx +565 -565
- package/src/islands/render-cache.ts +550 -550
- package/src/islands/types.ts +80 -80
- package/src/islands/universal-css-collector.ts +157 -157
- package/src/islands/universal-head-collector.ts +137 -137
- package/src/layout-system.d.ts +592 -592
- package/src/layout-system.ts +218 -218
- package/src/middleware/discovery.ts +268 -268
- package/src/middleware/executor.ts +315 -315
- package/src/middleware/index.ts +76 -76
- package/src/middleware/types.ts +99 -99
- package/src/nitro/build-config.ts +575 -575
- package/src/nitro/config.ts +483 -483
- package/src/nitro/error-handler.ts +636 -636
- package/src/nitro/index.ts +173 -173
- package/src/nitro/island-manifest.ts +584 -584
- package/src/nitro/middleware-adapter.ts +260 -260
- package/src/nitro/renderer.ts +1471 -1471
- package/src/nitro/route-discovery.ts +439 -439
- package/src/nitro/types.ts +321 -321
- package/src/render/collect-css.ts +198 -198
- package/src/render/error-pages.ts +79 -79
- package/src/render/isolated-ssr-renderer.ts +654 -654
- package/src/render/ssr.ts +1030 -1030
- package/src/schemas/api.ts +30 -30
- package/src/schemas/core.ts +64 -64
- package/src/schemas/index.ts +212 -212
- package/src/schemas/layout.ts +279 -279
- package/src/schemas/routing/index.ts +38 -38
- package/src/schemas/routing.ts +376 -376
- package/src/types/as-island.ts +20 -20
- package/src/types/image.d.ts +106 -106
- package/src/types/index.d.ts +22 -22
- package/src/types/island-jsx.d.ts +33 -33
- package/src/types/island-prop.d.ts +20 -20
- package/src/types/layout.ts +285 -285
- package/src/types/mdx.d.ts +6 -6
- package/src/types/routing.ts +555 -555
- package/src/types/types.ts +5 -5
- package/src/types/urlpattern.d.ts +49 -49
- package/src/types/vite-env.d.ts +11 -11
- package/src/utils/dev-logger.ts +299 -299
- package/src/utils/fs.ts +151 -151
- package/src/vite-plugin/auto-discover.ts +551 -551
- package/src/vite-plugin/config.ts +266 -266
- package/src/vite-plugin/errors.ts +127 -127
- package/src/vite-plugin/image-optimization.ts +156 -156
- package/src/vite-plugin/integration-activator.ts +126 -126
- package/src/vite-plugin/island-sidecar-plugin.ts +176 -176
- package/src/vite-plugin/module-discovery.ts +189 -189
- package/src/vite-plugin/nitro-integration.ts +1354 -1354
- package/src/vite-plugin/plugin.ts +403 -409
- package/src/vite-plugin/types.ts +327 -327
- package/src/vite-plugin/validation.ts +228 -228
- package/src/client/adapters/index.js +0 -12
- package/src/client/adapters/lit-adapter.js +0 -467
- package/src/client/adapters/lit-adapter.ts +0 -654
- package/src/client/adapters/preact-adapter.js +0 -223
- package/src/client/adapters/preact-adapter.ts +0 -331
- package/src/client/adapters/qwik-adapter.js +0 -259
- package/src/client/adapters/qwik-adapter.ts +0 -345
- package/src/client/adapters/react-adapter.js +0 -220
- package/src/client/adapters/react-adapter.ts +0 -353
- package/src/client/adapters/solid-adapter.js +0 -295
- package/src/client/adapters/solid-adapter.ts +0 -451
- package/src/client/adapters/svelte-adapter.js +0 -368
- package/src/client/adapters/svelte-adapter.ts +0 -524
- package/src/client/adapters/vue-adapter.js +0 -278
- package/src/client/adapters/vue-adapter.ts +0 -467
- package/src/client/components.js +0 -23
- package/src/client/css-hmr-handler.js +0 -263
- package/src/client/framework-adapter.js +0 -283
- package/src/client/hmr-coordinator.js +0 -274
|
@@ -1,223 +0,0 @@
|
|
|
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();
|
|
@@ -1,331 +0,0 @@
|
|
|
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
|
-
|
|
10
|
-
/// <reference lib="dom" />
|
|
11
|
-
|
|
12
|
-
import { BaseFrameworkAdapter, type StateSnapshot } from '../framework-adapter.ts';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Preact component type
|
|
16
|
-
* Can be a function component or class component
|
|
17
|
-
*/
|
|
18
|
-
type PreactComponent<P = Record<string, unknown>> =
|
|
19
|
-
| ((props: P) => PreactVNode | null)
|
|
20
|
-
| (new (props: P) => PreactClassComponent);
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Preact class component interface
|
|
24
|
-
*/
|
|
25
|
-
interface PreactClassComponent {
|
|
26
|
-
render(): PreactVNode | null;
|
|
27
|
-
isReactComponent?: boolean; // Preact uses same marker as React
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Preact VNode (Virtual Node) type
|
|
32
|
-
*/
|
|
33
|
-
interface PreactVNode {
|
|
34
|
-
type: string | PreactComponent;
|
|
35
|
-
props: Record<string, unknown>;
|
|
36
|
-
key: string | number | null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Preact module interface
|
|
41
|
-
*/
|
|
42
|
-
interface PreactModule {
|
|
43
|
-
h<P = Record<string, unknown>>(
|
|
44
|
-
component: PreactComponent<P> | string,
|
|
45
|
-
props: P | null,
|
|
46
|
-
...children: unknown[]
|
|
47
|
-
): PreactVNode;
|
|
48
|
-
render(vnode: PreactVNode, container: HTMLElement): void;
|
|
49
|
-
hydrate(vnode: PreactVNode, container: HTMLElement): void;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Preact-specific state snapshot
|
|
54
|
-
* Extends base snapshot with Preact-specific state like hooks
|
|
55
|
-
*/
|
|
56
|
-
interface PreactStateSnapshot extends StateSnapshot {
|
|
57
|
-
framework: 'preact';
|
|
58
|
-
data: {
|
|
59
|
-
/**
|
|
60
|
-
* Preact component tree data (if accessible)
|
|
61
|
-
* Used by Preact HMR to preserve hooks state
|
|
62
|
-
*/
|
|
63
|
-
componentData?: unknown;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Component display name for debugging
|
|
67
|
-
*/
|
|
68
|
-
componentName?: string;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Props at time of state capture
|
|
72
|
-
*/
|
|
73
|
-
capturedProps?: Record<string, unknown>;
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Preact HMR Adapter
|
|
79
|
-
*
|
|
80
|
-
* Leverages Preact's HMR capabilities (provided by @preact/preset-vite) to:
|
|
81
|
-
* - Preserve hooks state across updates
|
|
82
|
-
* - Maintain component tree structure
|
|
83
|
-
* - Handle component updates without full remount
|
|
84
|
-
*
|
|
85
|
-
* Preact HMR works similarly to React Fast Refresh:
|
|
86
|
-
* 1. Detecting when a component module is updated
|
|
87
|
-
* 2. Preserving the component tree (internal state representation)
|
|
88
|
-
* 3. Re-rendering with the new component definition
|
|
89
|
-
* 4. Restoring hooks state from the preserved tree
|
|
90
|
-
*/
|
|
91
|
-
export class PreactHMRAdapter extends BaseFrameworkAdapter {
|
|
92
|
-
readonly name = 'preact';
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Store Preact component instances for each island to enable proper updates
|
|
96
|
-
*/
|
|
97
|
-
private instances: WeakMap<HTMLElement, unknown> = new WeakMap();
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Check if a component is a Preact component
|
|
101
|
-
*
|
|
102
|
-
* Preact components can be:
|
|
103
|
-
* - Function components (including hooks)
|
|
104
|
-
* - Class components (extending Preact Component)
|
|
105
|
-
* - Forward refs
|
|
106
|
-
* - Memoized components
|
|
107
|
-
*
|
|
108
|
-
* Preact is API-compatible with React, so detection is similar
|
|
109
|
-
*/
|
|
110
|
-
canHandle(component: unknown): boolean {
|
|
111
|
-
if (!component) return false;
|
|
112
|
-
|
|
113
|
-
// Check if it's a function (function component or class)
|
|
114
|
-
if (typeof component === 'function') {
|
|
115
|
-
// Check for Preact/React-specific properties on the function
|
|
116
|
-
const comp = component as unknown as Record<string, unknown>;
|
|
117
|
-
|
|
118
|
-
// Class components have isReactComponent on prototype
|
|
119
|
-
// (Preact uses the same marker for compatibility)
|
|
120
|
-
const proto = (component as { prototype?: Record<string, unknown> }).prototype;
|
|
121
|
-
if (proto && proto.isReactComponent) {
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Check for Preact element symbol (for wrapped components)
|
|
126
|
-
if (comp.$typeof) {
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Assume any function could be a Preact component
|
|
131
|
-
// Preact HMR will handle validation
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Check for Preact VNode types
|
|
136
|
-
if (typeof component !== 'object') {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const obj = component as Record<string, unknown>;
|
|
141
|
-
|
|
142
|
-
// Check for Preact VNode symbol
|
|
143
|
-
if (obj.$typeof) {
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Check for wrapped components (HOCs, memo, forwardRef)
|
|
148
|
-
if (obj.type && typeof obj.type === 'function') {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Preserve Preact component state before HMR update
|
|
157
|
-
*
|
|
158
|
-
* Preact HMR handles most state preservation automatically through
|
|
159
|
-
* the component tree. We capture additional DOM state and props for fallback.
|
|
160
|
-
*/
|
|
161
|
-
override preserveState(island: HTMLElement): PreactStateSnapshot | null {
|
|
162
|
-
try {
|
|
163
|
-
// Get base DOM state
|
|
164
|
-
const baseSnapshot = super.preserveState(island);
|
|
165
|
-
if (!baseSnapshot) return null;
|
|
166
|
-
|
|
167
|
-
// Get Preact-specific data
|
|
168
|
-
const propsAttr = island.getAttribute('data-props');
|
|
169
|
-
const capturedProps = propsAttr ? JSON.parse(propsAttr) : {};
|
|
170
|
-
|
|
171
|
-
// Try to get component name from the island
|
|
172
|
-
const src = island.getAttribute('data-src') || '';
|
|
173
|
-
const componentName = this.extractComponentName(src);
|
|
174
|
-
|
|
175
|
-
const preactSnapshot: PreactStateSnapshot = {
|
|
176
|
-
...baseSnapshot,
|
|
177
|
-
framework: 'preact',
|
|
178
|
-
data: {
|
|
179
|
-
componentName,
|
|
180
|
-
capturedProps,
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
return preactSnapshot;
|
|
185
|
-
} catch (error) {
|
|
186
|
-
console.warn('Failed to preserve Preact state:', error);
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Update Preact component with HMR
|
|
193
|
-
*
|
|
194
|
-
* This method integrates with Preact HMR:
|
|
195
|
-
* 1. Preact HMR is automatically enabled by @preact/preset-vite
|
|
196
|
-
* 2. When a module updates, Vite sends an HMR event
|
|
197
|
-
* 3. We re-hydrate the component with the new definition
|
|
198
|
-
* 4. Preact HMR preserves hooks state automatically
|
|
199
|
-
* 5. The component re-renders with preserved state
|
|
200
|
-
*/
|
|
201
|
-
async update(
|
|
202
|
-
island: HTMLElement,
|
|
203
|
-
newComponent: unknown,
|
|
204
|
-
props: Record<string, unknown>
|
|
205
|
-
): Promise<void> {
|
|
206
|
-
if (!this.canHandle(newComponent)) {
|
|
207
|
-
throw new Error('Component is not a valid Preact component');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const Component = newComponent as PreactComponent;
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
// Dynamically import Preact at runtime
|
|
214
|
-
// This is resolved by Vite in the browser
|
|
215
|
-
const preactModule = await import('preact') as PreactModule;
|
|
216
|
-
const { h, hydrate } = preactModule;
|
|
217
|
-
|
|
218
|
-
// Check if we have an existing instance
|
|
219
|
-
const existingInstance = this.instances.get(island);
|
|
220
|
-
|
|
221
|
-
if (existingInstance) {
|
|
222
|
-
// Update existing component
|
|
223
|
-
// Preact HMR will preserve hooks state automatically
|
|
224
|
-
const vnode = h(Component, props);
|
|
225
|
-
hydrate(vnode, island);
|
|
226
|
-
} else {
|
|
227
|
-
// Create new instance and hydrate
|
|
228
|
-
// This happens on first HMR update or if instance was lost
|
|
229
|
-
const vnode = h(Component, props);
|
|
230
|
-
hydrate(vnode, island);
|
|
231
|
-
|
|
232
|
-
// Store instance for future updates
|
|
233
|
-
this.instances.set(island, Component);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Mark as hydrated
|
|
237
|
-
island.setAttribute('data-hydrated', 'true');
|
|
238
|
-
island.setAttribute('data-hydration-status', 'success');
|
|
239
|
-
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error('Preact HMR update failed:', error);
|
|
242
|
-
island.setAttribute('data-hydration-status', 'error');
|
|
243
|
-
throw error;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Restore Preact component state after HMR update
|
|
249
|
-
*
|
|
250
|
-
* Preact HMR handles most state restoration automatically.
|
|
251
|
-
* We restore DOM state (scroll, focus, form values) as a supplement.
|
|
252
|
-
*/
|
|
253
|
-
override restoreState(island: HTMLElement, state: StateSnapshot): void {
|
|
254
|
-
try {
|
|
255
|
-
// Restore DOM state (scroll, focus, form values)
|
|
256
|
-
super.restoreState(island, state);
|
|
257
|
-
|
|
258
|
-
// Preact HMR handles hooks state restoration automatically
|
|
259
|
-
// through the component tree, so we don't need to do anything special here
|
|
260
|
-
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.warn('Failed to restore Preact state:', error);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Handle errors during Preact HMR update
|
|
268
|
-
*
|
|
269
|
-
* Provides Preact-specific error handling with helpful messages
|
|
270
|
-
*/
|
|
271
|
-
override handleError(island: HTMLElement, error: Error): void {
|
|
272
|
-
console.error('Preact HMR error:', error);
|
|
273
|
-
|
|
274
|
-
// Use base error handling
|
|
275
|
-
super.handleError(island, error);
|
|
276
|
-
|
|
277
|
-
// Add Preact-specific error information
|
|
278
|
-
const errorIndicator = island.querySelector('.hmr-error-indicator');
|
|
279
|
-
if (errorIndicator) {
|
|
280
|
-
const errorMessage = error.message;
|
|
281
|
-
|
|
282
|
-
// Provide helpful hints for common Preact errors
|
|
283
|
-
let hint = '';
|
|
284
|
-
if (errorMessage.includes('hooks')) {
|
|
285
|
-
hint = ' (Hint: Check hooks usage - hooks must be called in the same order)';
|
|
286
|
-
} else if (errorMessage.includes('render')) {
|
|
287
|
-
hint = ' (Hint: Check component render method for errors)';
|
|
288
|
-
} else if (errorMessage.includes('hydration') || errorMessage.includes('hydrate')) {
|
|
289
|
-
hint = ' (Hint: Server and client render must match)';
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
errorIndicator.textContent = `Preact HMR Error: ${errorMessage}${hint}`;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Extract component name from source path
|
|
298
|
-
* Used for debugging and error messages
|
|
299
|
-
*/
|
|
300
|
-
private extractComponentName(src: string): string {
|
|
301
|
-
const parts = src.split('/');
|
|
302
|
-
const filename = parts[parts.length - 1];
|
|
303
|
-
return filename.replace(/\.(tsx?|jsx?)$/, '');
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Clean up Preact component when island is removed
|
|
308
|
-
* This should be called when an island is unmounted
|
|
309
|
-
*/
|
|
310
|
-
unmount(island: HTMLElement): void {
|
|
311
|
-
const instance = this.instances.get(island);
|
|
312
|
-
if (instance) {
|
|
313
|
-
try {
|
|
314
|
-
// Preact doesn't have an explicit unmount API like React
|
|
315
|
-
// We just clear the instance reference
|
|
316
|
-
this.instances.delete(island);
|
|
317
|
-
|
|
318
|
-
// Clear the island content to trigger cleanup
|
|
319
|
-
// Preact will handle component lifecycle cleanup
|
|
320
|
-
island.innerHTML = '';
|
|
321
|
-
} catch (error) {
|
|
322
|
-
console.warn('Failed to unmount Preact component:', error);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Create and export a singleton instance of the Preact HMR adapter
|
|
330
|
-
*/
|
|
331
|
-
export const preactAdapter = new PreactHMRAdapter();
|