@useavalon/avalon 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +54 -54
  2. package/mod.ts +302 -302
  3. package/package.json +49 -26
  4. package/src/build/integration-bundler-plugin.ts +116 -116
  5. package/src/build/integration-config.ts +168 -168
  6. package/src/build/integration-detection-plugin.ts +117 -117
  7. package/src/build/integration-resolver-plugin.ts +90 -90
  8. package/src/build/island-manifest.ts +269 -269
  9. package/src/build/island-types-generator.ts +476 -476
  10. package/src/build/mdx-island-transform.ts +464 -464
  11. package/src/build/mdx-plugin.ts +98 -98
  12. package/src/build/page-island-transform.ts +598 -598
  13. package/src/build/prop-extractors/index.ts +21 -21
  14. package/src/build/prop-extractors/lit.ts +140 -140
  15. package/src/build/prop-extractors/qwik.ts +16 -16
  16. package/src/build/prop-extractors/solid.ts +125 -125
  17. package/src/build/prop-extractors/svelte.ts +194 -194
  18. package/src/build/prop-extractors/vue.ts +111 -111
  19. package/src/build/sidecar-file-manager.ts +104 -104
  20. package/src/build/sidecar-renderer.ts +30 -30
  21. package/src/client/adapters/index.ts +21 -13
  22. package/src/client/components.ts +35 -35
  23. package/src/client/css-hmr-handler.ts +344 -344
  24. package/src/client/framework-adapter.ts +462 -462
  25. package/src/client/hmr-coordinator.ts +396 -396
  26. package/src/client/hmr-error-overlay.js +533 -533
  27. package/src/client/main.js +824 -816
  28. package/src/client/types/framework-runtime.d.ts +68 -68
  29. package/src/client/types/vite-hmr.d.ts +46 -46
  30. package/src/client/types/vite-virtual-modules.d.ts +70 -60
  31. package/src/components/Image.tsx +123 -123
  32. package/src/components/IslandErrorBoundary.tsx +145 -145
  33. package/src/components/LayoutDataErrorBoundary.tsx +141 -141
  34. package/src/components/LayoutErrorBoundary.tsx +127 -127
  35. package/src/components/PersistentIsland.tsx +52 -52
  36. package/src/components/StreamingErrorBoundary.tsx +233 -233
  37. package/src/components/StreamingLayout.tsx +538 -538
  38. package/src/core/components/component-analyzer.ts +192 -192
  39. package/src/core/components/component-detection.ts +508 -508
  40. package/src/core/components/enhanced-framework-detector.ts +500 -500
  41. package/src/core/components/framework-registry.ts +563 -563
  42. package/src/core/content/mdx-processor.ts +46 -46
  43. package/src/core/integrations/index.ts +19 -19
  44. package/src/core/integrations/loader.ts +125 -125
  45. package/src/core/integrations/registry.ts +175 -175
  46. package/src/core/islands/island-persistence.ts +325 -325
  47. package/src/core/islands/island-state-serializer.ts +258 -258
  48. package/src/core/islands/persistent-island-context.tsx +80 -80
  49. package/src/core/islands/use-persistent-state.ts +68 -68
  50. package/src/core/layout/enhanced-layout-resolver.ts +322 -322
  51. package/src/core/layout/layout-cache-manager.ts +485 -485
  52. package/src/core/layout/layout-composer.ts +357 -357
  53. package/src/core/layout/layout-data-loader.ts +516 -516
  54. package/src/core/layout/layout-discovery.ts +243 -243
  55. package/src/core/layout/layout-matcher.ts +299 -299
  56. package/src/core/layout/layout-types.ts +110 -110
  57. package/src/core/modules/framework-module-resolver.ts +273 -273
  58. package/src/islands/component-analysis.ts +213 -213
  59. package/src/islands/css-utils.ts +565 -565
  60. package/src/islands/discovery/index.ts +80 -80
  61. package/src/islands/discovery/registry.ts +340 -340
  62. package/src/islands/discovery/resolver.ts +477 -477
  63. package/src/islands/discovery/scanner.ts +386 -386
  64. package/src/islands/discovery/types.ts +117 -117
  65. package/src/islands/discovery/validator.ts +544 -544
  66. package/src/islands/discovery/watcher.ts +368 -368
  67. package/src/islands/framework-detection.ts +428 -428
  68. package/src/islands/integration-loader.ts +490 -490
  69. package/src/islands/island.tsx +565 -565
  70. package/src/islands/render-cache.ts +550 -550
  71. package/src/islands/types.ts +80 -80
  72. package/src/islands/universal-css-collector.ts +157 -157
  73. package/src/islands/universal-head-collector.ts +137 -137
  74. package/src/layout-system.d.ts +592 -592
  75. package/src/layout-system.ts +218 -218
  76. package/src/middleware/discovery.ts +268 -268
  77. package/src/middleware/executor.ts +315 -315
  78. package/src/middleware/index.ts +76 -76
  79. package/src/middleware/types.ts +99 -99
  80. package/src/nitro/build-config.ts +575 -575
  81. package/src/nitro/config.ts +483 -483
  82. package/src/nitro/error-handler.ts +636 -636
  83. package/src/nitro/index.ts +173 -173
  84. package/src/nitro/island-manifest.ts +584 -584
  85. package/src/nitro/middleware-adapter.ts +260 -260
  86. package/src/nitro/renderer.ts +1471 -1471
  87. package/src/nitro/route-discovery.ts +439 -439
  88. package/src/nitro/types.ts +321 -321
  89. package/src/render/collect-css.ts +198 -198
  90. package/src/render/error-pages.ts +79 -79
  91. package/src/render/isolated-ssr-renderer.ts +654 -654
  92. package/src/render/ssr.ts +1030 -1030
  93. package/src/schemas/api.ts +30 -30
  94. package/src/schemas/core.ts +64 -64
  95. package/src/schemas/index.ts +212 -212
  96. package/src/schemas/layout.ts +279 -279
  97. package/src/schemas/routing/index.ts +38 -38
  98. package/src/schemas/routing.ts +376 -376
  99. package/src/types/as-island.ts +20 -20
  100. package/src/types/image.d.ts +106 -106
  101. package/src/types/index.d.ts +22 -22
  102. package/src/types/island-jsx.d.ts +33 -33
  103. package/src/types/island-prop.d.ts +20 -20
  104. package/src/types/layout.ts +285 -285
  105. package/src/types/mdx.d.ts +6 -6
  106. package/src/types/routing.ts +555 -555
  107. package/src/types/types.ts +5 -5
  108. package/src/types/urlpattern.d.ts +49 -49
  109. package/src/types/vite-env.d.ts +11 -11
  110. package/src/utils/dev-logger.ts +299 -299
  111. package/src/utils/fs.ts +151 -151
  112. package/src/vite-plugin/auto-discover.ts +551 -551
  113. package/src/vite-plugin/config.ts +266 -266
  114. package/src/vite-plugin/errors.ts +127 -127
  115. package/src/vite-plugin/image-optimization.ts +156 -156
  116. package/src/vite-plugin/integration-activator.ts +126 -126
  117. package/src/vite-plugin/island-sidecar-plugin.ts +176 -176
  118. package/src/vite-plugin/module-discovery.ts +189 -189
  119. package/src/vite-plugin/nitro-integration.ts +1354 -1354
  120. package/src/vite-plugin/plugin.ts +403 -409
  121. package/src/vite-plugin/types.ts +327 -327
  122. package/src/vite-plugin/validation.ts +228 -228
  123. package/src/client/adapters/index.js +0 -12
  124. package/src/client/adapters/lit-adapter.js +0 -467
  125. package/src/client/adapters/lit-adapter.ts +0 -654
  126. package/src/client/adapters/preact-adapter.js +0 -223
  127. package/src/client/adapters/preact-adapter.ts +0 -331
  128. package/src/client/adapters/qwik-adapter.js +0 -259
  129. package/src/client/adapters/qwik-adapter.ts +0 -345
  130. package/src/client/adapters/react-adapter.js +0 -220
  131. package/src/client/adapters/react-adapter.ts +0 -353
  132. package/src/client/adapters/solid-adapter.js +0 -295
  133. package/src/client/adapters/solid-adapter.ts +0 -451
  134. package/src/client/adapters/svelte-adapter.js +0 -368
  135. package/src/client/adapters/svelte-adapter.ts +0 -524
  136. package/src/client/adapters/vue-adapter.js +0 -278
  137. package/src/client/adapters/vue-adapter.ts +0 -467
  138. package/src/client/components.js +0 -23
  139. package/src/client/css-hmr-handler.js +0 -263
  140. package/src/client/framework-adapter.js +0 -283
  141. package/src/client/hmr-coordinator.js +0 -274
@@ -1,259 +0,0 @@
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();
@@ -1,345 +0,0 @@
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
-
18
- /// <reference lib="dom" />
19
-
20
- import { BaseFrameworkAdapter, type StateSnapshot } from '../framework-adapter.ts';
21
-
22
- /**
23
- * Qwik component type
24
- * Qwik components are created with component$() which returns a QRL-wrapped component
25
- */
26
- type QwikComponent<P = Record<string, unknown>> = {
27
- (props: P): unknown;
28
- __brand?: 'QwikComponent';
29
- };
30
-
31
- /**
32
- * Qwik module interface for SSR/client rendering
33
- */
34
- interface QwikModule {
35
- renderToString?: (opts: Record<string, unknown>) => Promise<string>;
36
- render?: (parent: Element, jsxNode: unknown) => Promise<void>;
37
- }
38
-
39
- /**
40
- * Qwik state snapshot extending base state
41
- */
42
- interface QwikStateSnapshot extends StateSnapshot {
43
- framework: 'qwik';
44
- data: {
45
- componentName: string;
46
- capturedProps: Record<string, unknown>;
47
- containerState: string | null;
48
- qContainerAttrs: Record<string, string>;
49
- };
50
- }
51
-
52
- /**
53
- * Qwik HMR Adapter
54
- *
55
- * Unlike other frameworks, Qwik doesn't hydrate — it resumes. This adapter
56
- * handles HMR by re-rendering the container rather than performing traditional
57
- * hydration-based hot updates.
58
- *
59
- * During development, Qwik's optimizer (via vite-plugin-qwik) handles most
60
- * of the HMR heavy lifting. This adapter provides the island-level integration
61
- * for Avalon's HMR coordinator.
62
- */
63
- export class QwikHMRAdapter extends BaseFrameworkAdapter {
64
- readonly name = 'qwik';
65
-
66
- /**
67
- * Track container elements for cleanup
68
- */
69
- private containers: WeakMap<HTMLElement, { cleanup?: () => void }> = new WeakMap();
70
-
71
- /**
72
- * Check if a component is a Qwik component
73
- *
74
- * Qwik components are created with component$() and have distinctive markers:
75
- * - __brand or __qrl property from the Qwik optimizer
76
- * - $ suffix convention in source
77
- * - QRL (Qwik Resource Locator) references
78
- */
79
- canHandle(component: unknown): boolean {
80
- if (!component) return false;
81
-
82
- // Check if it's a function (Qwik components are functions)
83
- if (typeof component === 'function') {
84
- const comp = component as unknown as Record<string, unknown>;
85
-
86
- // Check for Qwik-specific markers set by the optimizer
87
- if (comp.__brand === 'QwikComponent' || comp.__qrl) {
88
- return true;
89
- }
90
-
91
- // Check for QRL wrapper pattern
92
- if (comp.getSymbol || comp.getHash) {
93
- return true;
94
- }
95
-
96
- try {
97
- const funcStr = component.toString();
98
-
99
- // Look for Qwik-specific compiled patterns
100
- if (
101
- funcStr.includes('component$') ||
102
- funcStr.includes('qrl') ||
103
- funcStr.includes('useSignal') ||
104
- funcStr.includes('useStore') ||
105
- funcStr.includes('useTask$') ||
106
- funcStr.includes('useVisibleTask$') ||
107
- funcStr.includes('_qrl') ||
108
- funcStr.includes('qwik')
109
- ) {
110
- return true;
111
- }
112
- } catch {
113
- // Ignore errors from toString()
114
- }
115
- }
116
-
117
- // Check if it's a Qwik component object (wrapped or exported)
118
- if (typeof component === 'object' && component !== null) {
119
- const obj = component as Record<string, unknown>;
120
-
121
- // Check for default export pattern
122
- if (obj.default && typeof obj.default === 'function') {
123
- return this.canHandle(obj.default);
124
- }
125
-
126
- // Check for Qwik QRL markers
127
- if (obj.__qrl || obj.__brand === 'QwikComponent') {
128
- return true;
129
- }
130
- }
131
-
132
- return false;
133
- }
134
-
135
- /**
136
- * Preserve Qwik component state before HMR update
137
- *
138
- * Qwik serializes state into the DOM via q:container attributes and
139
- * inline <script type="qwik/json"> blocks. We capture this serialized
140
- * state so it can be used during re-rendering.
141
- */
142
- override preserveState(island: HTMLElement): QwikStateSnapshot | null {
143
- try {
144
- const baseSnapshot = super.preserveState(island);
145
- if (!baseSnapshot) return null;
146
-
147
- // Get props from the island
148
- const propsAttr = island.getAttribute('data-props');
149
- const capturedProps = propsAttr ? JSON.parse(propsAttr) : {};
150
-
151
- const src = island.getAttribute('data-src') || '';
152
- const componentName = this.extractComponentName(src);
153
-
154
- // Capture Qwik container state — serialized in the DOM
155
- const containerEl = island.closest('[q\\:container]') || island;
156
- const containerState = containerEl.querySelector('script[type="qwik/json"]')?.textContent || null;
157
-
158
- // Capture q: attributes that hold container metadata
159
- const qContainerAttrs: Record<string, string> = {};
160
- const attrs = containerEl.attributes;
161
- if (attrs) {
162
- for (let i = 0; i < attrs.length; i++) {
163
- const attr = attrs[i];
164
- if (attr.name.startsWith('q:')) {
165
- qContainerAttrs[attr.name] = attr.value;
166
- }
167
- }
168
- }
169
-
170
- return {
171
- ...baseSnapshot,
172
- framework: 'qwik',
173
- data: {
174
- componentName,
175
- capturedProps,
176
- containerState,
177
- qContainerAttrs,
178
- },
179
- };
180
- } catch (error) {
181
- console.warn('Failed to preserve Qwik state:', error);
182
- return null;
183
- }
184
- }
185
-
186
- /**
187
- * Update Qwik component with HMR
188
- *
189
- * Qwik's resumability model means we don't hydrate in the traditional sense.
190
- * Instead, during HMR:
191
- * 1. The Qwik optimizer (vite-plugin-qwik) invalidates the affected QRLs
192
- * 2. We re-render the component into the island container
193
- * 3. Qwikloader picks up the new event bindings automatically
194
- * 4. Serialized state is restored from the container
195
- */
196
- async update(
197
- island: HTMLElement,
198
- newComponent: unknown,
199
- props: Record<string, unknown>
200
- ): Promise<void> {
201
- if (!this.canHandle(newComponent)) {
202
- throw new Error('Component is not a valid Qwik component');
203
- }
204
-
205
- // Extract the actual component function
206
- let Component: QwikComponent;
207
- if (typeof newComponent === 'object' && newComponent !== null) {
208
- const obj = newComponent as Record<string, unknown>;
209
- if (obj.default && typeof obj.default === 'function') {
210
- Component = obj.default as QwikComponent;
211
- } else {
212
- throw new Error('Qwik component object must have a default export');
213
- }
214
- } else if (typeof newComponent === 'function') {
215
- Component = newComponent as QwikComponent;
216
- } else {
217
- throw new Error('Invalid Qwik component type');
218
- }
219
-
220
- try {
221
- // Clean up existing container tracking
222
- const existing = this.containers.get(island);
223
- if (existing?.cleanup) {
224
- try {
225
- existing.cleanup();
226
- } catch (error) {
227
- console.warn('Failed to clean up existing Qwik container:', error);
228
- }
229
- }
230
-
231
- // Dynamically import Qwik's client render API
232
- // In development, vite-plugin-qwik makes this available
233
- // Use a variable to prevent Vite's static import analysis from resolving this at build time
234
- const qwikId = '@builder.io/qwik';
235
- const qwikModule = await import(/* @vite-ignore */ qwikId) as QwikModule & Record<string, unknown>;
236
-
237
- if (qwikModule.render) {
238
- // Use Qwik's client-side render to mount the component
239
- // Qwik's render handles setting up the container and resumability
240
- const jsxNode = typeof qwikModule.jsx === 'function'
241
- ? qwikModule.jsx(Component, props)
242
- : Component(props);
243
-
244
- await qwikModule.render(island, jsxNode);
245
- } else {
246
- // Fallback: replace innerHTML and let Qwikloader resume
247
- // This works because Qwik's event listeners are declarative in the DOM
248
- console.warn('Qwik render API not available, using innerHTML fallback');
249
- const result = Component(props);
250
- if (typeof result === 'string') {
251
- island.innerHTML = result;
252
- }
253
- }
254
-
255
- // Track the container for future cleanup
256
- this.containers.set(island, {});
257
-
258
- // Mark as hydrated (resumed, in Qwik terms)
259
- island.setAttribute('data-hydrated', 'true');
260
- island.setAttribute('data-hydration-status', 'success');
261
-
262
- } catch (error) {
263
- console.error('Qwik HMR update failed:', error);
264
- island.setAttribute('data-hydration-status', 'error');
265
- throw error;
266
- }
267
- }
268
-
269
- /**
270
- * Restore Qwik component state after HMR update
271
- *
272
- * Qwik's serialized state lives in the DOM, so restoring the container
273
- * state script block and q: attributes is sufficient for the framework
274
- * to resume correctly.
275
- */
276
- override restoreState(island: HTMLElement, state: StateSnapshot): void {
277
- try {
278
- // Restore DOM state (scroll, focus, form values)
279
- super.restoreState(island, state);
280
-
281
- // Qwik's resumability handles reactive state automatically
282
- // through its serialized container state in the DOM
283
-
284
- } catch (error) {
285
- console.warn('Failed to restore Qwik state:', error);
286
- }
287
- }
288
-
289
- /**
290
- * Handle errors during Qwik HMR update
291
- */
292
- override handleError(island: HTMLElement, error: Error): void {
293
- console.error('Qwik HMR error:', error);
294
-
295
- super.handleError(island, error);
296
-
297
- const errorIndicator = island.querySelector('.hmr-error-indicator');
298
- if (errorIndicator) {
299
- const errorMessage = error.message;
300
-
301
- let hint = '';
302
- if (errorMessage.includes('component$') || errorMessage.includes('component\\$')) {
303
- hint = ' (Hint: Ensure component is wrapped with component$())';
304
- } else if (errorMessage.includes('useSignal') || errorMessage.includes('useStore')) {
305
- hint = ' (Hint: Qwik hooks must be called inside component$() body)';
306
- } else if (errorMessage.includes('QRL') || errorMessage.includes('qrl')) {
307
- hint = ' (Hint: Check that lazy-loaded boundaries use $ correctly)';
308
- } else if (errorMessage.includes('serialize') || errorMessage.includes('container')) {
309
- hint = ' (Hint: Ensure all state is serializable — Qwik serializes state to the DOM)';
310
- } else if (errorMessage.includes('resumable') || errorMessage.includes('resume')) {
311
- hint = ' (Hint: Server and client container state must match for resumability)';
312
- }
313
-
314
- errorIndicator.textContent = `Qwik HMR Error: ${errorMessage}${hint}`;
315
- }
316
- }
317
-
318
- /**
319
- * Extract component name from source path
320
- */
321
- private extractComponentName(src: string): string {
322
- const parts = src.split('/');
323
- const filename = parts[parts.length - 1];
324
- return filename.replace(/\.qwik\.(tsx?|jsx?)$/, '').replace(/\.(tsx?|jsx?)$/, '');
325
- }
326
-
327
- /**
328
- * Clean up Qwik component when island is removed
329
- */
330
- unmount(island: HTMLElement): void {
331
- const container = this.containers.get(island);
332
- if (container) {
333
- try {
334
- if (container.cleanup) {
335
- container.cleanup();
336
- }
337
- this.containers.delete(island);
338
- } catch (error) {
339
- console.warn('Failed to unmount Qwik component:', error);
340
- }
341
- }
342
- }
343
- }
344
-
345
- export const qwikAdapter = new QwikHMRAdapter();