@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,654 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lit HMR Adapter
|
|
3
|
-
*
|
|
4
|
-
* Provides Hot Module Replacement support for Lit components.
|
|
5
|
-
* Handles custom element re-registration and updates all element instances.
|
|
6
|
-
* Preserves element properties across updates.
|
|
7
|
-
*
|
|
8
|
-
* Requirements: 2.6
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/// <reference lib="dom" />
|
|
12
|
-
|
|
13
|
-
import { BaseFrameworkAdapter, type StateSnapshot } from '../framework-adapter.ts';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Lit element type
|
|
17
|
-
* Lit components are classes that extend LitElement
|
|
18
|
-
*/
|
|
19
|
-
interface LitElementConstructor {
|
|
20
|
-
new (): LitElementInstance;
|
|
21
|
-
elementName?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Lit element instance interface
|
|
26
|
-
*/
|
|
27
|
-
interface LitElementInstance extends HTMLElement {
|
|
28
|
-
/**
|
|
29
|
-
* Lit-specific properties
|
|
30
|
-
*/
|
|
31
|
-
_$litElement$?: unknown;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Request update method
|
|
35
|
-
*/
|
|
36
|
-
requestUpdate?(): void;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Update complete promise
|
|
40
|
-
*/
|
|
41
|
-
updateComplete?: Promise<boolean>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Lit-specific state snapshot
|
|
46
|
-
* Extends base snapshot with Lit-specific state like element properties
|
|
47
|
-
*/
|
|
48
|
-
interface LitStateSnapshot extends StateSnapshot {
|
|
49
|
-
framework: 'lit';
|
|
50
|
-
data: {
|
|
51
|
-
/**
|
|
52
|
-
* Element properties captured from the Lit element
|
|
53
|
-
*/
|
|
54
|
-
elementProperties?: Record<string, unknown>;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Element attributes
|
|
58
|
-
*/
|
|
59
|
-
elementAttributes?: Record<string, string>;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Component display name for debugging
|
|
63
|
-
*/
|
|
64
|
-
componentName?: string;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Tag name of the custom element
|
|
68
|
-
*/
|
|
69
|
-
tagName?: string;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Props at time of state capture
|
|
73
|
-
*/
|
|
74
|
-
capturedProps?: Record<string, unknown>;
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Lit HMR Adapter
|
|
80
|
-
*
|
|
81
|
-
* Handles HMR for Lit components by:
|
|
82
|
-
* 1. Re-registering the custom element with a new definition
|
|
83
|
-
* 2. Updating all existing instances of the element
|
|
84
|
-
* 3. Preserving element properties and attributes
|
|
85
|
-
*
|
|
86
|
-
* Lit components are Web Components (custom elements), so HMR requires:
|
|
87
|
-
* - Undefining the old custom element (if possible)
|
|
88
|
-
* - Defining the new custom element
|
|
89
|
-
* - Updating all instances in the DOM
|
|
90
|
-
* - Preserving reactive properties
|
|
91
|
-
*/
|
|
92
|
-
export class LitHMRAdapter extends BaseFrameworkAdapter {
|
|
93
|
-
readonly name = 'lit';
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Store element constructors for each island
|
|
97
|
-
*/
|
|
98
|
-
private elementConstructors: WeakMap<HTMLElement, LitElementConstructor> = new WeakMap();
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Store tag names for each island
|
|
102
|
-
*/
|
|
103
|
-
private tagNames: WeakMap<HTMLElement, string> = new WeakMap();
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Check if a component is a Lit component
|
|
107
|
-
*
|
|
108
|
-
* Lit components are:
|
|
109
|
-
* - Classes that extend LitElement
|
|
110
|
-
* - Typically decorated with @customElement
|
|
111
|
-
* - Have .lit.ts or .lit.js extension (but not always)
|
|
112
|
-
*/
|
|
113
|
-
canHandle(component: unknown): boolean {
|
|
114
|
-
if (!component) return false;
|
|
115
|
-
|
|
116
|
-
// Check if it's a class (Lit components are classes)
|
|
117
|
-
if (typeof component === 'function') {
|
|
118
|
-
const comp = component as unknown as Record<string, unknown>;
|
|
119
|
-
|
|
120
|
-
// Check for Lit-specific markers
|
|
121
|
-
// Lit elements may have __litElement marker
|
|
122
|
-
if (comp.__litElement) {
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check if it extends LitElement by looking at the prototype chain
|
|
127
|
-
try {
|
|
128
|
-
const proto = (component as { prototype?: Record<string, unknown> }).prototype;
|
|
129
|
-
if (proto) {
|
|
130
|
-
// Check for Lit-specific methods on prototype
|
|
131
|
-
if (
|
|
132
|
-
'render' in proto &&
|
|
133
|
-
'requestUpdate' in proto &&
|
|
134
|
-
'updateComplete' in proto
|
|
135
|
-
) {
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Check for LitElement in prototype chain
|
|
140
|
-
let currentProto = proto;
|
|
141
|
-
while (currentProto && currentProto !== Object.prototype) {
|
|
142
|
-
const constructor = currentProto.constructor as { name?: string };
|
|
143
|
-
if (constructor && constructor.name === 'LitElement') {
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
currentProto = Object.getPrototypeOf(currentProto);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
} catch {
|
|
150
|
-
// Ignore errors from prototype inspection
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Check for @customElement decorator metadata
|
|
154
|
-
// The decorator adds metadata to the class
|
|
155
|
-
if (comp.elementName || comp.tagName) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Check function signature - Lit components typically have specific patterns
|
|
160
|
-
try {
|
|
161
|
-
const funcStr = component.toString();
|
|
162
|
-
|
|
163
|
-
// Look for Lit-specific patterns
|
|
164
|
-
if (
|
|
165
|
-
funcStr.includes('LitElement') ||
|
|
166
|
-
funcStr.includes('customElement') ||
|
|
167
|
-
funcStr.includes('html`') ||
|
|
168
|
-
funcStr.includes('css`') ||
|
|
169
|
-
funcStr.includes('render()') ||
|
|
170
|
-
funcStr.includes('requestUpdate')
|
|
171
|
-
) {
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
} catch {
|
|
175
|
-
// Ignore errors from toString()
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Check if it's a Lit component object (wrapped or exported)
|
|
180
|
-
if (typeof component !== 'object') {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const obj = component as Record<string, unknown>;
|
|
185
|
-
|
|
186
|
-
// Check for default export pattern
|
|
187
|
-
if (obj.default && typeof obj.default === 'function') {
|
|
188
|
-
return this.canHandle(obj.default);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Check for Lit component markers
|
|
192
|
-
if (obj.__litElement) {
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Preserve Lit element state before HMR update
|
|
201
|
-
*
|
|
202
|
-
* Captures:
|
|
203
|
-
* - Element properties (reactive properties defined with @property)
|
|
204
|
-
* - Element attributes
|
|
205
|
-
* - DOM state (scroll, focus, form values)
|
|
206
|
-
*/
|
|
207
|
-
override preserveState(island: HTMLElement): LitStateSnapshot | null {
|
|
208
|
-
try {
|
|
209
|
-
// Get base DOM state
|
|
210
|
-
const baseSnapshot = super.preserveState(island);
|
|
211
|
-
if (!baseSnapshot) return null;
|
|
212
|
-
|
|
213
|
-
// Get Lit-specific data
|
|
214
|
-
const propsAttr = island.getAttribute('data-props');
|
|
215
|
-
const capturedProps = propsAttr ? JSON.parse(propsAttr) : {};
|
|
216
|
-
|
|
217
|
-
// Try to get component name from the island
|
|
218
|
-
const src = island.getAttribute('data-src') || '';
|
|
219
|
-
const componentName = this.extractComponentName(src);
|
|
220
|
-
|
|
221
|
-
// Get tag name
|
|
222
|
-
const tagName = island.getAttribute('data-tag-name') || this.tagNames.get(island);
|
|
223
|
-
|
|
224
|
-
// Find the Lit element inside the island
|
|
225
|
-
const litElement = tagName
|
|
226
|
-
? island.querySelector(tagName) as LitElementInstance
|
|
227
|
-
: island.querySelector('[data-lit-element]') as LitElementInstance;
|
|
228
|
-
|
|
229
|
-
// Capture element properties and attributes
|
|
230
|
-
const elementProperties: Record<string, unknown> = {};
|
|
231
|
-
const elementAttributes: Record<string, string> = {};
|
|
232
|
-
|
|
233
|
-
if (litElement) {
|
|
234
|
-
// Capture all properties
|
|
235
|
-
// Lit stores reactive properties on the element instance
|
|
236
|
-
for (const key in litElement) {
|
|
237
|
-
if (litElement.hasOwnProperty(key) && !key.startsWith('_')) {
|
|
238
|
-
try {
|
|
239
|
-
const value = litElement[key as keyof LitElementInstance];
|
|
240
|
-
// Only capture serializable values
|
|
241
|
-
if (
|
|
242
|
-
value !== undefined &&
|
|
243
|
-
value !== null &&
|
|
244
|
-
typeof value !== 'function' &&
|
|
245
|
-
typeof value !== 'symbol'
|
|
246
|
-
) {
|
|
247
|
-
elementProperties[key] = value;
|
|
248
|
-
}
|
|
249
|
-
} catch {
|
|
250
|
-
// Skip properties that throw on access
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Capture all attributes
|
|
256
|
-
for (let i = 0; i < litElement.attributes.length; i++) {
|
|
257
|
-
const attr = litElement.attributes[i];
|
|
258
|
-
elementAttributes[attr.name] = attr.value;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const litSnapshot: LitStateSnapshot = {
|
|
263
|
-
...baseSnapshot,
|
|
264
|
-
framework: 'lit',
|
|
265
|
-
data: {
|
|
266
|
-
componentName,
|
|
267
|
-
tagName: tagName || undefined,
|
|
268
|
-
capturedProps,
|
|
269
|
-
elementProperties,
|
|
270
|
-
elementAttributes,
|
|
271
|
-
},
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
return litSnapshot;
|
|
275
|
-
} catch (error) {
|
|
276
|
-
console.warn('Failed to preserve Lit state:', error);
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Update Lit component with HMR
|
|
283
|
-
*
|
|
284
|
-
* This method handles custom element re-registration:
|
|
285
|
-
* 1. Extract the tag name from the component or island
|
|
286
|
-
* 2. Find all instances of the element in the DOM
|
|
287
|
-
* 3. Preserve their properties and attributes
|
|
288
|
-
* 4. Undefine the old custom element (if possible)
|
|
289
|
-
* 5. Define the new custom element
|
|
290
|
-
* 6. Update all instances with the new definition
|
|
291
|
-
* 7. Restore properties and attributes
|
|
292
|
-
*/
|
|
293
|
-
async update(
|
|
294
|
-
island: HTMLElement,
|
|
295
|
-
newComponent: unknown,
|
|
296
|
-
props: Record<string, unknown>
|
|
297
|
-
): Promise<void> {
|
|
298
|
-
if (!this.canHandle(newComponent)) {
|
|
299
|
-
throw new Error('Component is not a valid Lit component');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Extract the actual component class
|
|
303
|
-
let ElementClass: LitElementConstructor;
|
|
304
|
-
if (typeof newComponent === 'object' && newComponent !== null) {
|
|
305
|
-
const obj = newComponent as Record<string, unknown>;
|
|
306
|
-
if (obj.default && typeof obj.default === 'function') {
|
|
307
|
-
ElementClass = obj.default as LitElementConstructor;
|
|
308
|
-
} else {
|
|
309
|
-
throw new Error('Lit component object must have a default export');
|
|
310
|
-
}
|
|
311
|
-
} else if (typeof newComponent === 'function') {
|
|
312
|
-
ElementClass = newComponent as LitElementConstructor;
|
|
313
|
-
} else {
|
|
314
|
-
throw new Error('Invalid Lit component type');
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
// Determine the tag name
|
|
319
|
-
// Priority: data-tag-name attribute > elementName property > derive from class name
|
|
320
|
-
let tagName = island.getAttribute('data-tag-name');
|
|
321
|
-
|
|
322
|
-
if (!tagName) {
|
|
323
|
-
// Try to get from the class
|
|
324
|
-
tagName = (ElementClass as unknown as Record<string, unknown>).elementName as string;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (!tagName) {
|
|
328
|
-
// Derive from class name (convert PascalCase to kebab-case)
|
|
329
|
-
tagName = ElementClass.name
|
|
330
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
331
|
-
.toLowerCase();
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (!tagName || !tagName.includes('-')) {
|
|
335
|
-
throw new Error('Invalid custom element tag name: ' + tagName);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Store tag name for future updates
|
|
339
|
-
this.tagNames.set(island, tagName);
|
|
340
|
-
island.setAttribute('data-tag-name', tagName);
|
|
341
|
-
|
|
342
|
-
// Find all instances of this element in the island
|
|
343
|
-
const elements = Array.from(island.querySelectorAll(tagName)) as LitElementInstance[];
|
|
344
|
-
|
|
345
|
-
// If no elements exist, create one
|
|
346
|
-
if (elements.length === 0) {
|
|
347
|
-
// Check if the custom element is already defined
|
|
348
|
-
const existingDefinition = customElements.get(tagName);
|
|
349
|
-
|
|
350
|
-
if (!existingDefinition) {
|
|
351
|
-
// Define the custom element
|
|
352
|
-
customElements.define(tagName, ElementClass as CustomElementConstructor);
|
|
353
|
-
} else if (existingDefinition !== ElementClass) {
|
|
354
|
-
// Element is already defined with a different class
|
|
355
|
-
// We need to re-register it
|
|
356
|
-
await this.reregisterCustomElement(tagName, ElementClass);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Create a new element
|
|
360
|
-
const newElement = document.createElement(tagName) as LitElementInstance;
|
|
361
|
-
|
|
362
|
-
// Set properties from props
|
|
363
|
-
Object.entries(props).forEach(([key, value]) => {
|
|
364
|
-
try {
|
|
365
|
-
(newElement as unknown as Record<string, unknown>)[key] = value;
|
|
366
|
-
} catch (error) {
|
|
367
|
-
console.warn(`Failed to set property ${key} on Lit element:`, error);
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// Append to island
|
|
372
|
-
island.appendChild(newElement);
|
|
373
|
-
|
|
374
|
-
// Store constructor
|
|
375
|
-
this.elementConstructors.set(island, ElementClass);
|
|
376
|
-
|
|
377
|
-
// Mark as hydrated
|
|
378
|
-
island.setAttribute('data-hydrated', 'true');
|
|
379
|
-
island.setAttribute('data-hydration-status', 'success');
|
|
380
|
-
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Update existing elements
|
|
385
|
-
// First, check if we need to re-register the custom element
|
|
386
|
-
const existingDefinition = customElements.get(tagName);
|
|
387
|
-
|
|
388
|
-
if (existingDefinition && existingDefinition !== ElementClass) {
|
|
389
|
-
// Re-register the custom element with the new definition
|
|
390
|
-
await this.reregisterCustomElement(tagName, ElementClass);
|
|
391
|
-
|
|
392
|
-
// After re-registration, we need to replace all existing elements
|
|
393
|
-
// because they're instances of the old class
|
|
394
|
-
for (const oldElement of elements) {
|
|
395
|
-
// Preserve state
|
|
396
|
-
const properties: Record<string, unknown> = {};
|
|
397
|
-
const attributes: Record<string, string> = {};
|
|
398
|
-
|
|
399
|
-
// Capture properties
|
|
400
|
-
for (const key in oldElement) {
|
|
401
|
-
if (oldElement.hasOwnProperty(key) && !key.startsWith('_')) {
|
|
402
|
-
try {
|
|
403
|
-
const value = oldElement[key as keyof LitElementInstance];
|
|
404
|
-
if (
|
|
405
|
-
value !== undefined &&
|
|
406
|
-
value !== null &&
|
|
407
|
-
typeof value !== 'function' &&
|
|
408
|
-
typeof value !== 'symbol'
|
|
409
|
-
) {
|
|
410
|
-
properties[key] = value;
|
|
411
|
-
}
|
|
412
|
-
} catch {
|
|
413
|
-
// Skip properties that throw on access
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Capture attributes
|
|
419
|
-
for (let i = 0; i < oldElement.attributes.length; i++) {
|
|
420
|
-
const attr = oldElement.attributes[i];
|
|
421
|
-
attributes[attr.name] = attr.value;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Create new element
|
|
425
|
-
const newElement = document.createElement(tagName) as LitElementInstance;
|
|
426
|
-
|
|
427
|
-
// Restore attributes
|
|
428
|
-
Object.entries(attributes).forEach(([name, value]) => {
|
|
429
|
-
newElement.setAttribute(name, value);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Restore properties
|
|
433
|
-
Object.entries(properties).forEach(([key, value]) => {
|
|
434
|
-
try {
|
|
435
|
-
(newElement as unknown as Record<string, unknown>)[key] = value;
|
|
436
|
-
} catch (error) {
|
|
437
|
-
console.warn(`Failed to restore property ${key}:`, error);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// Replace old element with new element
|
|
442
|
-
oldElement.parentNode?.replaceChild(newElement, oldElement);
|
|
443
|
-
}
|
|
444
|
-
} else if (!existingDefinition) {
|
|
445
|
-
// Define the custom element for the first time
|
|
446
|
-
customElements.define(tagName, ElementClass as CustomElementConstructor);
|
|
447
|
-
|
|
448
|
-
// Trigger update on all elements
|
|
449
|
-
for (const element of elements) {
|
|
450
|
-
if (element.requestUpdate) {
|
|
451
|
-
element.requestUpdate();
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
// Same class, just trigger updates
|
|
456
|
-
for (const element of elements) {
|
|
457
|
-
// Update properties from props
|
|
458
|
-
Object.entries(props).forEach(([key, value]) => {
|
|
459
|
-
try {
|
|
460
|
-
(element as unknown as Record<string, unknown>)[key] = value;
|
|
461
|
-
} catch (error) {
|
|
462
|
-
console.warn(`Failed to update property ${key}:`, error);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
// Request update
|
|
467
|
-
if (element.requestUpdate) {
|
|
468
|
-
element.requestUpdate();
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Store constructor
|
|
474
|
-
this.elementConstructors.set(island, ElementClass);
|
|
475
|
-
|
|
476
|
-
// Mark as hydrated
|
|
477
|
-
island.setAttribute('data-hydrated', 'true');
|
|
478
|
-
island.setAttribute('data-hydration-status', 'success');
|
|
479
|
-
|
|
480
|
-
} catch (error) {
|
|
481
|
-
console.error('Lit HMR update failed:', error);
|
|
482
|
-
island.setAttribute('data-hydration-status', 'error');
|
|
483
|
-
throw error;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Re-register a custom element with a new definition
|
|
489
|
-
*
|
|
490
|
-
* Custom elements cannot be undefined once defined, so we need to:
|
|
491
|
-
* 1. Create a new tag name (with a version suffix)
|
|
492
|
-
* 2. Define the new element with the new tag name
|
|
493
|
-
* 3. Update all references to use the new tag name
|
|
494
|
-
*
|
|
495
|
-
* Alternative approach (used here):
|
|
496
|
-
* 1. Use a wrapper element that delegates to the actual element
|
|
497
|
-
* 2. Update the wrapper to use the new element class
|
|
498
|
-
*/
|
|
499
|
-
private async reregisterCustomElement(
|
|
500
|
-
tagName: string,
|
|
501
|
-
ElementClass: LitElementConstructor
|
|
502
|
-
): Promise<void> {
|
|
503
|
-
// Unfortunately, custom elements cannot be truly undefined once defined
|
|
504
|
-
// The best we can do is define a new version with a suffix
|
|
505
|
-
|
|
506
|
-
// However, for HMR purposes, we can use a different approach:
|
|
507
|
-
// We'll just replace all instances of the old element with new instances
|
|
508
|
-
// This is handled in the update() method above
|
|
509
|
-
|
|
510
|
-
// For now, we'll just log a warning
|
|
511
|
-
console.warn(
|
|
512
|
-
`Custom element ${tagName} is already defined. ` +
|
|
513
|
-
`Replacing all instances with new definition.`
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
// Note: In a production HMR system, you might want to:
|
|
517
|
-
// 1. Use a versioned tag name (e.g., my-element-v2)
|
|
518
|
-
// 2. Use a proxy element that delegates to the actual element
|
|
519
|
-
// 3. Use a custom element registry that supports re-registration
|
|
520
|
-
|
|
521
|
-
// For this implementation, we rely on replacing element instances
|
|
522
|
-
// which is handled in the update() method
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* Restore Lit element state after HMR update
|
|
527
|
-
*
|
|
528
|
-
* Restores:
|
|
529
|
-
* - Element properties
|
|
530
|
-
* - Element attributes
|
|
531
|
-
* - DOM state (scroll, focus, form values)
|
|
532
|
-
*/
|
|
533
|
-
override restoreState(island: HTMLElement, state: StateSnapshot): void {
|
|
534
|
-
try {
|
|
535
|
-
// Restore DOM state (scroll, focus, form values)
|
|
536
|
-
super.restoreState(island, state);
|
|
537
|
-
|
|
538
|
-
// Restore Lit-specific state
|
|
539
|
-
const litState = state as LitStateSnapshot;
|
|
540
|
-
const tagName = litState.data.tagName;
|
|
541
|
-
|
|
542
|
-
if (tagName) {
|
|
543
|
-
const litElement = island.querySelector(tagName) as LitElementInstance;
|
|
544
|
-
|
|
545
|
-
if (litElement) {
|
|
546
|
-
// Restore element properties
|
|
547
|
-
if (litState.data.elementProperties) {
|
|
548
|
-
Object.entries(litState.data.elementProperties).forEach(([key, value]) => {
|
|
549
|
-
try {
|
|
550
|
-
(litElement as unknown as Record<string, unknown>)[key] = value;
|
|
551
|
-
} catch (error) {
|
|
552
|
-
console.warn(`Failed to restore property ${key}:`, error);
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Restore element attributes
|
|
558
|
-
if (litState.data.elementAttributes) {
|
|
559
|
-
Object.entries(litState.data.elementAttributes).forEach(([name, value]) => {
|
|
560
|
-
try {
|
|
561
|
-
litElement.setAttribute(name, value);
|
|
562
|
-
} catch (error) {
|
|
563
|
-
console.warn(`Failed to restore attribute ${name}:`, error);
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Request update to apply changes
|
|
569
|
-
if (litElement.requestUpdate) {
|
|
570
|
-
litElement.requestUpdate();
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
} catch (error) {
|
|
576
|
-
console.warn('Failed to restore Lit state:', error);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Handle errors during Lit HMR update
|
|
582
|
-
*
|
|
583
|
-
* Provides Lit-specific error handling with helpful messages
|
|
584
|
-
*/
|
|
585
|
-
override handleError(island: HTMLElement, error: Error): void {
|
|
586
|
-
console.error('Lit HMR error:', error);
|
|
587
|
-
|
|
588
|
-
// Use base error handling
|
|
589
|
-
super.handleError(island, error);
|
|
590
|
-
|
|
591
|
-
// Add Lit-specific error information
|
|
592
|
-
const errorIndicator = island.querySelector('.hmr-error-indicator');
|
|
593
|
-
if (errorIndicator) {
|
|
594
|
-
const errorMessage = error.message;
|
|
595
|
-
|
|
596
|
-
// Provide helpful hints for common Lit errors
|
|
597
|
-
let hint = '';
|
|
598
|
-
if (errorMessage.includes('custom element') || errorMessage.includes('define')) {
|
|
599
|
-
hint = ' (Hint: Check that your element has a valid tag name with a hyphen)';
|
|
600
|
-
} else if (errorMessage.includes('tag name')) {
|
|
601
|
-
hint = ' (Hint: Custom element tag names must contain a hyphen)';
|
|
602
|
-
} else if (errorMessage.includes('property') || errorMessage.includes('attribute')) {
|
|
603
|
-
hint = ' (Hint: Check @property decorators and attribute names)';
|
|
604
|
-
} else if (errorMessage.includes('render')) {
|
|
605
|
-
hint = ' (Hint: Check the render() method for errors)';
|
|
606
|
-
} else if (errorMessage.includes('shadow')) {
|
|
607
|
-
hint = ' (Hint: Check Shadow DOM usage and styles)';
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
errorIndicator.textContent = `Lit HMR Error: ${errorMessage}${hint}`;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Extract component name from source path
|
|
616
|
-
* Used for debugging and error messages
|
|
617
|
-
*/
|
|
618
|
-
private extractComponentName(src: string): string {
|
|
619
|
-
const parts = src.split('/');
|
|
620
|
-
const filename = parts[parts.length - 1];
|
|
621
|
-
return filename.replace(/\.lit\.(ts|js)$/, '').replace(/\.(ts|js)$/, '');
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* Clean up Lit element when island is removed
|
|
626
|
-
* This should be called when an island is unmounted
|
|
627
|
-
*/
|
|
628
|
-
unmount(island: HTMLElement): void {
|
|
629
|
-
try {
|
|
630
|
-
const tagName = this.tagNames.get(island);
|
|
631
|
-
|
|
632
|
-
if (tagName) {
|
|
633
|
-
// Find all elements and disconnect them
|
|
634
|
-
const elements = island.querySelectorAll(tagName) as NodeListOf<LitElementInstance>;
|
|
635
|
-
elements.forEach(element => {
|
|
636
|
-
// Lit elements clean up automatically when disconnected
|
|
637
|
-
// but we can help by removing them from the DOM
|
|
638
|
-
element.remove();
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
this.tagNames.delete(island);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
this.elementConstructors.delete(island);
|
|
645
|
-
} catch (error) {
|
|
646
|
-
console.warn('Failed to unmount Lit element:', error);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* Create and export a singleton instance of the Lit HMR adapter
|
|
653
|
-
*/
|
|
654
|
-
export const litAdapter = new LitHMRAdapter();
|