@useavalon/avalon 0.1.9 → 0.1.11
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/package.json +5 -1
- package/src/client/adapters/index.js +12 -0
- package/src/client/adapters/lit-adapter.js +467 -0
- package/src/client/adapters/preact-adapter.js +223 -0
- package/src/client/adapters/qwik-adapter.js +259 -0
- package/src/client/adapters/react-adapter.js +220 -0
- package/src/client/adapters/solid-adapter.js +295 -0
- package/src/client/adapters/svelte-adapter.js +368 -0
- package/src/client/adapters/vue-adapter.js +278 -0
- package/src/client/components.js +23 -0
- package/src/client/css-hmr-handler.js +263 -0
- package/src/client/framework-adapter.js +283 -0
- package/src/client/hmr-coordinator.js +274 -0
- package/src/client/main.js +8 -8
- package/src/vite-plugin/plugin.ts +25 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/avalon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Multi-framework islands architecture for the modern web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://useavalon.dev",
|
|
13
13
|
"keywords": ["avalon", "islands", "ssr", "vite", "preact", "multi-framework", "hydration"],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build:client": "bun run scripts/build-client.ts",
|
|
16
|
+
"prepublishOnly": "bun run build:client"
|
|
17
|
+
},
|
|
14
18
|
"exports": {
|
|
15
19
|
".": "./mod.ts",
|
|
16
20
|
"./client": "./src/client/components.ts",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework HMR Adapters
|
|
3
|
+
*
|
|
4
|
+
* Exports all framework-specific HMR adapters for easy registration
|
|
5
|
+
*/
|
|
6
|
+
export { ReactHMRAdapter, reactAdapter } from "./react-adapter.js";
|
|
7
|
+
export { PreactHMRAdapter, preactAdapter } from "./preact-adapter.js";
|
|
8
|
+
export { VueHMRAdapter, vueAdapter } from "./vue-adapter.js";
|
|
9
|
+
export { SvelteHMRAdapter, svelteAdapter } from "./svelte-adapter.js";
|
|
10
|
+
export { SolidHMRAdapter, solidAdapter } from "./solid-adapter.js";
|
|
11
|
+
export { LitHMRAdapter, litAdapter } from "./lit-adapter.js";
|
|
12
|
+
export { QwikHMRAdapter, qwikAdapter } from "./qwik-adapter.js";
|
|
@@ -0,0 +1,467 @@
|
|
|
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
|
+
/// <reference lib="dom" />
|
|
11
|
+
import { BaseFrameworkAdapter } from "../framework-adapter.js";
|
|
12
|
+
/**
|
|
13
|
+
* Lit HMR Adapter
|
|
14
|
+
*
|
|
15
|
+
* Handles HMR for Lit components by:
|
|
16
|
+
* 1. Re-registering the custom element with a new definition
|
|
17
|
+
* 2. Updating all existing instances of the element
|
|
18
|
+
* 3. Preserving element properties and attributes
|
|
19
|
+
*
|
|
20
|
+
* Lit components are Web Components (custom elements), so HMR requires:
|
|
21
|
+
* - Undefining the old custom element (if possible)
|
|
22
|
+
* - Defining the new custom element
|
|
23
|
+
* - Updating all instances in the DOM
|
|
24
|
+
* - Preserving reactive properties
|
|
25
|
+
*/
|
|
26
|
+
export class LitHMRAdapter extends BaseFrameworkAdapter {
|
|
27
|
+
name = "lit";
|
|
28
|
+
/**
|
|
29
|
+
* Store element constructors for each island
|
|
30
|
+
*/
|
|
31
|
+
elementConstructors = new WeakMap();
|
|
32
|
+
/**
|
|
33
|
+
* Store tag names for each island
|
|
34
|
+
*/
|
|
35
|
+
tagNames = new WeakMap();
|
|
36
|
+
/**
|
|
37
|
+
* Check if a component is a Lit component
|
|
38
|
+
*
|
|
39
|
+
* Lit components are:
|
|
40
|
+
* - Classes that extend LitElement
|
|
41
|
+
* - Typically decorated with @customElement
|
|
42
|
+
* - Have .lit.ts or .lit.js extension (but not always)
|
|
43
|
+
*/
|
|
44
|
+
canHandle(component) {
|
|
45
|
+
if (!component) return false;
|
|
46
|
+
// Check if it's a class (Lit components are classes)
|
|
47
|
+
if (typeof component === "function") {
|
|
48
|
+
const comp = component;
|
|
49
|
+
// Check for Lit-specific markers
|
|
50
|
+
// Lit elements may have __litElement marker
|
|
51
|
+
if (comp.__litElement) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// Check if it extends LitElement by looking at the prototype chain
|
|
55
|
+
try {
|
|
56
|
+
const proto = component.prototype;
|
|
57
|
+
if (proto) {
|
|
58
|
+
// Check for Lit-specific methods on prototype
|
|
59
|
+
if ("render" in proto && "requestUpdate" in proto && "updateComplete" in proto) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
// Check for LitElement in prototype chain
|
|
63
|
+
let currentProto = proto;
|
|
64
|
+
while (currentProto && currentProto !== Object.prototype) {
|
|
65
|
+
const constructor = currentProto.constructor;
|
|
66
|
+
if (constructor && constructor.name === "LitElement") {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
currentProto = Object.getPrototypeOf(currentProto);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch {}
|
|
73
|
+
// Check for @customElement decorator metadata
|
|
74
|
+
// The decorator adds metadata to the class
|
|
75
|
+
if (comp.elementName || comp.tagName) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
// Check function signature - Lit components typically have specific patterns
|
|
79
|
+
try {
|
|
80
|
+
const funcStr = component.toString();
|
|
81
|
+
// Look for Lit-specific patterns
|
|
82
|
+
if (funcStr.includes("LitElement") || funcStr.includes("customElement") || funcStr.includes("html`") || funcStr.includes("css`") || funcStr.includes("render()") || funcStr.includes("requestUpdate")) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
// Check if it's a Lit component object (wrapped or exported)
|
|
88
|
+
if (typeof component !== "object") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const obj = component;
|
|
92
|
+
// Check for default export pattern
|
|
93
|
+
if (obj.default && typeof obj.default === "function") {
|
|
94
|
+
return this.canHandle(obj.default);
|
|
95
|
+
}
|
|
96
|
+
// Check for Lit component markers
|
|
97
|
+
if (obj.__litElement) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Preserve Lit element state before HMR update
|
|
104
|
+
*
|
|
105
|
+
* Captures:
|
|
106
|
+
* - Element properties (reactive properties defined with @property)
|
|
107
|
+
* - Element attributes
|
|
108
|
+
* - DOM state (scroll, focus, form values)
|
|
109
|
+
*/
|
|
110
|
+
preserveState(island) {
|
|
111
|
+
try {
|
|
112
|
+
// Get base DOM state
|
|
113
|
+
const baseSnapshot = super.preserveState(island);
|
|
114
|
+
if (!baseSnapshot) return null;
|
|
115
|
+
// Get Lit-specific data
|
|
116
|
+
const propsAttr = island.getAttribute("data-props");
|
|
117
|
+
const capturedProps = propsAttr ? JSON.parse(propsAttr) : {};
|
|
118
|
+
// Try to get component name from the island
|
|
119
|
+
const src = island.getAttribute("data-src") || "";
|
|
120
|
+
const componentName = this.extractComponentName(src);
|
|
121
|
+
// Get tag name
|
|
122
|
+
const tagName = island.getAttribute("data-tag-name") || this.tagNames.get(island);
|
|
123
|
+
// Find the Lit element inside the island
|
|
124
|
+
const litElement = tagName ? island.querySelector(tagName) : island.querySelector("[data-lit-element]");
|
|
125
|
+
// Capture element properties and attributes
|
|
126
|
+
const elementProperties = {};
|
|
127
|
+
const elementAttributes = {};
|
|
128
|
+
if (litElement) {
|
|
129
|
+
// Capture all properties
|
|
130
|
+
// Lit stores reactive properties on the element instance
|
|
131
|
+
for (const key in litElement) {
|
|
132
|
+
if (litElement.hasOwnProperty(key) && !key.startsWith("_")) {
|
|
133
|
+
try {
|
|
134
|
+
const value = litElement[key];
|
|
135
|
+
// Only capture serializable values
|
|
136
|
+
if (value !== undefined && value !== null && typeof value !== "function" && typeof value !== "symbol") {
|
|
137
|
+
elementProperties[key] = value;
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Capture all attributes
|
|
143
|
+
for (let i = 0; i < litElement.attributes.length; i++) {
|
|
144
|
+
const attr = litElement.attributes[i];
|
|
145
|
+
elementAttributes[attr.name] = attr.value;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const litSnapshot = {
|
|
149
|
+
...baseSnapshot,
|
|
150
|
+
framework: "lit",
|
|
151
|
+
data: {
|
|
152
|
+
componentName,
|
|
153
|
+
tagName: tagName || undefined,
|
|
154
|
+
capturedProps,
|
|
155
|
+
elementProperties,
|
|
156
|
+
elementAttributes
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
return litSnapshot;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.warn("Failed to preserve Lit state:", error);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Update Lit component with HMR
|
|
167
|
+
*
|
|
168
|
+
* This method handles custom element re-registration:
|
|
169
|
+
* 1. Extract the tag name from the component or island
|
|
170
|
+
* 2. Find all instances of the element in the DOM
|
|
171
|
+
* 3. Preserve their properties and attributes
|
|
172
|
+
* 4. Undefine the old custom element (if possible)
|
|
173
|
+
* 5. Define the new custom element
|
|
174
|
+
* 6. Update all instances with the new definition
|
|
175
|
+
* 7. Restore properties and attributes
|
|
176
|
+
*/
|
|
177
|
+
async update(island, newComponent, props) {
|
|
178
|
+
if (!this.canHandle(newComponent)) {
|
|
179
|
+
throw new Error("Component is not a valid Lit component");
|
|
180
|
+
}
|
|
181
|
+
// Extract the actual component class
|
|
182
|
+
let ElementClass;
|
|
183
|
+
if (typeof newComponent === "object" && newComponent !== null) {
|
|
184
|
+
const obj = newComponent;
|
|
185
|
+
if (obj.default && typeof obj.default === "function") {
|
|
186
|
+
ElementClass = obj.default;
|
|
187
|
+
} else {
|
|
188
|
+
throw new Error("Lit component object must have a default export");
|
|
189
|
+
}
|
|
190
|
+
} else if (typeof newComponent === "function") {
|
|
191
|
+
ElementClass = newComponent;
|
|
192
|
+
} else {
|
|
193
|
+
throw new Error("Invalid Lit component type");
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
// Determine the tag name
|
|
197
|
+
// Priority: data-tag-name attribute > elementName property > derive from class name
|
|
198
|
+
let tagName = island.getAttribute("data-tag-name");
|
|
199
|
+
if (!tagName) {
|
|
200
|
+
// Try to get from the class
|
|
201
|
+
tagName = ElementClass.elementName;
|
|
202
|
+
}
|
|
203
|
+
if (!tagName) {
|
|
204
|
+
// Derive from class name (convert PascalCase to kebab-case)
|
|
205
|
+
tagName = ElementClass.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
if (!tagName || !tagName.includes("-")) {
|
|
208
|
+
throw new Error("Invalid custom element tag name: " + tagName);
|
|
209
|
+
}
|
|
210
|
+
// Store tag name for future updates
|
|
211
|
+
this.tagNames.set(island, tagName);
|
|
212
|
+
island.setAttribute("data-tag-name", tagName);
|
|
213
|
+
// Find all instances of this element in the island
|
|
214
|
+
const elements = Array.from(island.querySelectorAll(tagName));
|
|
215
|
+
// If no elements exist, create one
|
|
216
|
+
if (elements.length === 0) {
|
|
217
|
+
// Check if the custom element is already defined
|
|
218
|
+
const existingDefinition = customElements.get(tagName);
|
|
219
|
+
if (!existingDefinition) {
|
|
220
|
+
// Define the custom element
|
|
221
|
+
customElements.define(tagName, ElementClass);
|
|
222
|
+
} else if (existingDefinition !== ElementClass) {
|
|
223
|
+
// Element is already defined with a different class
|
|
224
|
+
// We need to re-register it
|
|
225
|
+
await this.reregisterCustomElement(tagName, ElementClass);
|
|
226
|
+
}
|
|
227
|
+
// Create a new element
|
|
228
|
+
const newElement = document.createElement(tagName);
|
|
229
|
+
// Set properties from props
|
|
230
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
231
|
+
try {
|
|
232
|
+
newElement[key] = value;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.warn(`Failed to set property ${key} on Lit element:`, error);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
// Append to island
|
|
238
|
+
island.appendChild(newElement);
|
|
239
|
+
// Store constructor
|
|
240
|
+
this.elementConstructors.set(island, ElementClass);
|
|
241
|
+
// Mark as hydrated
|
|
242
|
+
island.setAttribute("data-hydrated", "true");
|
|
243
|
+
island.setAttribute("data-hydration-status", "success");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Update existing elements
|
|
247
|
+
// First, check if we need to re-register the custom element
|
|
248
|
+
const existingDefinition = customElements.get(tagName);
|
|
249
|
+
if (existingDefinition && existingDefinition !== ElementClass) {
|
|
250
|
+
// Re-register the custom element with the new definition
|
|
251
|
+
await this.reregisterCustomElement(tagName, ElementClass);
|
|
252
|
+
// After re-registration, we need to replace all existing elements
|
|
253
|
+
// because they're instances of the old class
|
|
254
|
+
for (const oldElement of elements) {
|
|
255
|
+
// Preserve state
|
|
256
|
+
const properties = {};
|
|
257
|
+
const attributes = {};
|
|
258
|
+
// Capture properties
|
|
259
|
+
for (const key in oldElement) {
|
|
260
|
+
if (oldElement.hasOwnProperty(key) && !key.startsWith("_")) {
|
|
261
|
+
try {
|
|
262
|
+
const value = oldElement[key];
|
|
263
|
+
if (value !== undefined && value !== null && typeof value !== "function" && typeof value !== "symbol") {
|
|
264
|
+
properties[key] = value;
|
|
265
|
+
}
|
|
266
|
+
} catch {}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Capture attributes
|
|
270
|
+
for (let i = 0; i < oldElement.attributes.length; i++) {
|
|
271
|
+
const attr = oldElement.attributes[i];
|
|
272
|
+
attributes[attr.name] = attr.value;
|
|
273
|
+
}
|
|
274
|
+
// Create new element
|
|
275
|
+
const newElement = document.createElement(tagName);
|
|
276
|
+
// Restore attributes
|
|
277
|
+
Object.entries(attributes).forEach(([name, value]) => {
|
|
278
|
+
newElement.setAttribute(name, value);
|
|
279
|
+
});
|
|
280
|
+
// Restore properties
|
|
281
|
+
Object.entries(properties).forEach(([key, value]) => {
|
|
282
|
+
try {
|
|
283
|
+
newElement[key] = value;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.warn(`Failed to restore property ${key}:`, error);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
// Replace old element with new element
|
|
289
|
+
oldElement.parentNode?.replaceChild(newElement, oldElement);
|
|
290
|
+
}
|
|
291
|
+
} else if (!existingDefinition) {
|
|
292
|
+
// Define the custom element for the first time
|
|
293
|
+
customElements.define(tagName, ElementClass);
|
|
294
|
+
// Trigger update on all elements
|
|
295
|
+
for (const element of elements) {
|
|
296
|
+
if (element.requestUpdate) {
|
|
297
|
+
element.requestUpdate();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
// Same class, just trigger updates
|
|
302
|
+
for (const element of elements) {
|
|
303
|
+
// Update properties from props
|
|
304
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
305
|
+
try {
|
|
306
|
+
element[key] = value;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.warn(`Failed to update property ${key}:`, error);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
// Request update
|
|
312
|
+
if (element.requestUpdate) {
|
|
313
|
+
element.requestUpdate();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Store constructor
|
|
318
|
+
this.elementConstructors.set(island, ElementClass);
|
|
319
|
+
// Mark as hydrated
|
|
320
|
+
island.setAttribute("data-hydrated", "true");
|
|
321
|
+
island.setAttribute("data-hydration-status", "success");
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error("Lit HMR update failed:", error);
|
|
324
|
+
island.setAttribute("data-hydration-status", "error");
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Re-register a custom element with a new definition
|
|
330
|
+
*
|
|
331
|
+
* Custom elements cannot be undefined once defined, so we need to:
|
|
332
|
+
* 1. Create a new tag name (with a version suffix)
|
|
333
|
+
* 2. Define the new element with the new tag name
|
|
334
|
+
* 3. Update all references to use the new tag name
|
|
335
|
+
*
|
|
336
|
+
* Alternative approach (used here):
|
|
337
|
+
* 1. Use a wrapper element that delegates to the actual element
|
|
338
|
+
* 2. Update the wrapper to use the new element class
|
|
339
|
+
*/
|
|
340
|
+
async reregisterCustomElement(tagName, ElementClass) {
|
|
341
|
+
// Unfortunately, custom elements cannot be truly undefined once defined
|
|
342
|
+
// The best we can do is define a new version with a suffix
|
|
343
|
+
// However, for HMR purposes, we can use a different approach:
|
|
344
|
+
// We'll just replace all instances of the old element with new instances
|
|
345
|
+
// This is handled in the update() method above
|
|
346
|
+
// For now, we'll just log a warning
|
|
347
|
+
console.warn(`Custom element ${tagName} is already defined. ` + `Replacing all instances with new definition.`);
|
|
348
|
+
// Note: In a production HMR system, you might want to:
|
|
349
|
+
// 1. Use a versioned tag name (e.g., my-element-v2)
|
|
350
|
+
// 2. Use a proxy element that delegates to the actual element
|
|
351
|
+
// 3. Use a custom element registry that supports re-registration
|
|
352
|
+
// For this implementation, we rely on replacing element instances
|
|
353
|
+
// which is handled in the update() method
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Restore Lit element state after HMR update
|
|
357
|
+
*
|
|
358
|
+
* Restores:
|
|
359
|
+
* - Element properties
|
|
360
|
+
* - Element attributes
|
|
361
|
+
* - DOM state (scroll, focus, form values)
|
|
362
|
+
*/
|
|
363
|
+
restoreState(island, state) {
|
|
364
|
+
try {
|
|
365
|
+
// Restore DOM state (scroll, focus, form values)
|
|
366
|
+
super.restoreState(island, state);
|
|
367
|
+
// Restore Lit-specific state
|
|
368
|
+
const litState = state;
|
|
369
|
+
const tagName = litState.data.tagName;
|
|
370
|
+
if (tagName) {
|
|
371
|
+
const litElement = island.querySelector(tagName);
|
|
372
|
+
if (litElement) {
|
|
373
|
+
// Restore element properties
|
|
374
|
+
if (litState.data.elementProperties) {
|
|
375
|
+
Object.entries(litState.data.elementProperties).forEach(([key, value]) => {
|
|
376
|
+
try {
|
|
377
|
+
litElement[key] = value;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.warn(`Failed to restore property ${key}:`, error);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
// Restore element attributes
|
|
384
|
+
if (litState.data.elementAttributes) {
|
|
385
|
+
Object.entries(litState.data.elementAttributes).forEach(([name, value]) => {
|
|
386
|
+
try {
|
|
387
|
+
litElement.setAttribute(name, value);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.warn(`Failed to restore attribute ${name}:`, error);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
// Request update to apply changes
|
|
394
|
+
if (litElement.requestUpdate) {
|
|
395
|
+
litElement.requestUpdate();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.warn("Failed to restore Lit state:", error);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Handle errors during Lit HMR update
|
|
405
|
+
*
|
|
406
|
+
* Provides Lit-specific error handling with helpful messages
|
|
407
|
+
*/
|
|
408
|
+
handleError(island, error) {
|
|
409
|
+
console.error("Lit HMR error:", error);
|
|
410
|
+
// Use base error handling
|
|
411
|
+
super.handleError(island, error);
|
|
412
|
+
// Add Lit-specific error information
|
|
413
|
+
const errorIndicator = island.querySelector(".hmr-error-indicator");
|
|
414
|
+
if (errorIndicator) {
|
|
415
|
+
const errorMessage = error.message;
|
|
416
|
+
// Provide helpful hints for common Lit errors
|
|
417
|
+
let hint = "";
|
|
418
|
+
if (errorMessage.includes("custom element") || errorMessage.includes("define")) {
|
|
419
|
+
hint = " (Hint: Check that your element has a valid tag name with a hyphen)";
|
|
420
|
+
} else if (errorMessage.includes("tag name")) {
|
|
421
|
+
hint = " (Hint: Custom element tag names must contain a hyphen)";
|
|
422
|
+
} else if (errorMessage.includes("property") || errorMessage.includes("attribute")) {
|
|
423
|
+
hint = " (Hint: Check @property decorators and attribute names)";
|
|
424
|
+
} else if (errorMessage.includes("render")) {
|
|
425
|
+
hint = " (Hint: Check the render() method for errors)";
|
|
426
|
+
} else if (errorMessage.includes("shadow")) {
|
|
427
|
+
hint = " (Hint: Check Shadow DOM usage and styles)";
|
|
428
|
+
}
|
|
429
|
+
errorIndicator.textContent = `Lit HMR Error: ${errorMessage}${hint}`;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Extract component name from source path
|
|
434
|
+
* Used for debugging and error messages
|
|
435
|
+
*/
|
|
436
|
+
extractComponentName(src) {
|
|
437
|
+
const parts = src.split("/");
|
|
438
|
+
const filename = parts[parts.length - 1];
|
|
439
|
+
return filename.replace(/\.lit\.(ts|js)$/, "").replace(/\.(ts|js)$/, "");
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Clean up Lit element when island is removed
|
|
443
|
+
* This should be called when an island is unmounted
|
|
444
|
+
*/
|
|
445
|
+
unmount(island) {
|
|
446
|
+
try {
|
|
447
|
+
const tagName = this.tagNames.get(island);
|
|
448
|
+
if (tagName) {
|
|
449
|
+
// Find all elements and disconnect them
|
|
450
|
+
const elements = island.querySelectorAll(tagName);
|
|
451
|
+
elements.forEach((element) => {
|
|
452
|
+
// Lit elements clean up automatically when disconnected
|
|
453
|
+
// but we can help by removing them from the DOM
|
|
454
|
+
element.remove();
|
|
455
|
+
});
|
|
456
|
+
this.tagNames.delete(island);
|
|
457
|
+
}
|
|
458
|
+
this.elementConstructors.delete(island);
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.warn("Failed to unmount Lit element:", error);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Create and export a singleton instance of the Lit HMR adapter
|
|
466
|
+
*/
|
|
467
|
+
export const litAdapter = new LitHMRAdapter();
|