Skip to content

Commit 81fe3da

Browse files
committed
Update arizona.svelte template with enhanced lifecycle monitoring
- Add LifecycleDemo.svelte component with interactive lifecycle controls - Enhance arizona-svelte-lifecycle.js with automatic monitoring features: * DOM MutationObserver for real-time component detection * Arizona WebSocket integration for HTML patch handling * Automatic mount/unmount based on DOM changes * Debounced operations to prevent excessive re-scanning - Update main.js with automatic lifecycle monitoring initialization - Improve logging with proper JSON display and auto-scroll functionality - Add persistent Demo Components section with enhanced UX - Update built assets (CSS, JS, source maps) with latest changes - Ensure all template variables use {{name}} for proper templating New features: - arizonaSvelte.startMonitoring() for automatic lifecycle management - Enhanced component architecture demo with real-time lifecycle tracking - Improved developer experience with comprehensive monitoring tools
1 parent 676ba8b commit 81fe3da

File tree

8 files changed

+1449
-29
lines changed

8 files changed

+1449
-29
lines changed

priv/templates/arizona.svelte.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
{template, "arizona.svelte/assets/js/arizona-svelte-registry.js", "{{name}}/assets/js/arizona-svelte-registry.js"}.
1515
{template, "arizona.svelte/assets/svelte/components/Counter.svelte", "{{name}}/assets/svelte/components/Counter.svelte"}.
1616
{template, "arizona.svelte/assets/svelte/components/HelloWorld.svelte", "{{name}}/assets/svelte/components/HelloWorld.svelte"}.
17+
{template, "arizona.svelte/assets/svelte/components/LifecycleDemo.svelte", "{{name}}/assets/svelte/components/LifecycleDemo.svelte"}.
1718
{template, "arizona.svelte/assets/tailwind.config.js", "{{name}}/assets/tailwind.config.js"}.
1819
{template, "arizona.svelte/assets/vite.config.js", "{{name}}/assets/vite.config.js"}.
1920

priv/templates/arizona.svelte/assets/js/arizona-svelte-lifecycle.js

Lines changed: 305 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,23 @@
66
import { mount, unmount } from 'svelte';
77

88
class ArizonaSvelteLifecycle {
9-
constructor(registry) {
9+
constructor(registry, options = {}) {
1010
if (!registry) {
1111
throw new Error('ArizonaSvelteLifecycle requires a registry instance');
1212
}
1313

1414
this.registry = registry;
1515
this.mountedComponents = new Map(); // target -> component instance
16+
this.observers = new Set(); // Set of active observers
17+
this.isMonitoring = false;
18+
this.options = {
19+
autoMount: options.autoMount !== false, // Default true
20+
autoUnmount: options.autoUnmount !== false, // Default true
21+
observeSubtree: options.observeSubtree !== false, // Default true
22+
debounceMs: options.debounceMs || 100, // Debounce DOM changes
23+
...options
24+
};
25+
this.debounceTimer = null;
1626
}
1727

1828
/**
@@ -38,15 +48,22 @@ class ArizonaSvelteLifecycle {
3848
const instance = mount(ComponentClass, { target, props });
3949
this.mountedComponents.set(target, instance);
4050
mountedCount++;
51+
console.log(`[Arizona Svelte] ✅ Mounted '${componentName}' component`, {
52+
target: target.id || target.className || 'unnamed',
53+
props,
54+
totalMounted: this.mountedComponents.size
55+
});
4156
} catch (error) {
42-
console.error(`[Arizona Svelte] Failed to mount component '${componentName}':`, error);
57+
console.error(`[Arizona Svelte] Failed to mount component '${componentName}':`, error);
4358
}
4459
} else {
4560
console.warn(`[Arizona Svelte] Component '${componentName}' not found in registry`);
4661
}
4762
});
4863

49-
console.log(`[Arizona Svelte] Mounted ${mountedCount} components`);
64+
if (mountedCount > 0) {
65+
console.log(`[Arizona Svelte] Mounted ${mountedCount} components`);
66+
}
5067
return mountedCount;
5168
}
5269

@@ -60,11 +77,16 @@ class ArizonaSvelteLifecycle {
6077

6178
if (instance) {
6279
try {
80+
const componentName = target.dataset.svelteComponent || 'unknown';
6381
unmount(instance);
6482
this.mountedComponents.delete(target);
83+
console.log(`[Arizona Svelte] 🗑️ Unmounted '${componentName}' component`, {
84+
target: target.id || target.className || 'unnamed',
85+
totalMounted: this.mountedComponents.size
86+
});
6587
return true;
6688
} catch (error) {
67-
console.error(`[Arizona Svelte] Failed to unmount component:`, error);
89+
console.error(`[Arizona Svelte] Failed to unmount component:`, error);
6890
return false;
6991
}
7092
}
@@ -123,6 +145,285 @@ class ArizonaSvelteLifecycle {
123145
isComponentMounted(target) {
124146
return this.mountedComponents.has(target);
125147
}
148+
149+
/**
150+
* Start automatic monitoring for component lifecycle
151+
* @returns {void}
152+
*/
153+
startMonitoring() {
154+
if (this.isMonitoring) {
155+
console.warn('[Arizona Svelte] Monitoring already started');
156+
return;
157+
}
158+
159+
this.isMonitoring = true;
160+
console.log('[Arizona Svelte] Starting automatic component monitoring');
161+
162+
// Initial mount
163+
if (this.options.autoMount) {
164+
this.mountComponents();
165+
}
166+
167+
// Set up DOM mutation observer
168+
this.setupDOMObserver();
169+
170+
// Set up Arizona WebSocket listener
171+
this.setupArizonaListener();
172+
173+
// Set up page visibility listener
174+
this.setupVisibilityListener();
175+
176+
// Set up cleanup on page unload
177+
this.setupUnloadListener();
178+
}
179+
180+
/**
181+
* Stop automatic monitoring
182+
* @returns {void}
183+
*/
184+
stopMonitoring() {
185+
if (!this.isMonitoring) {
186+
return;
187+
}
188+
189+
this.isMonitoring = false;
190+
console.log('[Arizona Svelte] Stopping automatic component monitoring');
191+
192+
// Clean up all observers
193+
this.observers.forEach(observer => {
194+
if (observer.disconnect) {
195+
observer.disconnect();
196+
} else if (typeof observer === 'function') {
197+
observer(); // Cleanup function
198+
}
199+
});
200+
this.observers.clear();
201+
202+
// Clear debounce timer
203+
if (this.debounceTimer) {
204+
clearTimeout(this.debounceTimer);
205+
this.debounceTimer = null;
206+
}
207+
}
208+
209+
/**
210+
* Setup DOM mutation observer to detect component additions/removals
211+
* @private
212+
*/
213+
setupDOMObserver() {
214+
const observer = new MutationObserver((mutations) => {
215+
this.debouncedHandleMutations(mutations);
216+
});
217+
218+
observer.observe(document.body, {
219+
childList: true,
220+
subtree: this.options.observeSubtree,
221+
attributes: true,
222+
attributeFilter: ['data-svelte-component', 'data-svelte-props']
223+
});
224+
225+
this.observers.add(observer);
226+
}
227+
228+
/**
229+
* Setup Arizona WebSocket event listener for patches
230+
* @private
231+
*/
232+
setupArizonaListener() {
233+
const handleArizonaEvent = (event) => {
234+
const { type, data } = event.detail;
235+
236+
if (type === 'html_patch') {
237+
// When Arizona applies HTML patches, we need to check for new components
238+
this.debouncedScanAndMount();
239+
}
240+
};
241+
242+
document.addEventListener('arizonaEvent', handleArizonaEvent);
243+
244+
// Return cleanup function
245+
const cleanup = () => {
246+
document.removeEventListener('arizonaEvent', handleArizonaEvent);
247+
};
248+
249+
this.observers.add(cleanup);
250+
}
251+
252+
/**
253+
* Setup page visibility listener to pause/resume components
254+
* @private
255+
*/
256+
setupVisibilityListener() {
257+
const handleVisibilityChange = () => {
258+
if (document.hidden) {
259+
console.log('[Arizona Svelte] Page hidden - components may pause updates');
260+
} else {
261+
console.log('[Arizona Svelte] Page visible - checking for component updates');
262+
this.debouncedScanAndMount();
263+
}
264+
};
265+
266+
document.addEventListener('visibilitychange', handleVisibilityChange);
267+
268+
const cleanup = () => {
269+
document.removeEventListener('visibilitychange', handleVisibilityChange);
270+
};
271+
272+
this.observers.add(cleanup);
273+
}
274+
275+
/**
276+
* Setup page unload listener for cleanup
277+
* @private
278+
*/
279+
setupUnloadListener() {
280+
const handleUnload = () => {
281+
console.log('[Arizona Svelte] Page unloading - cleaning up components');
282+
if (this.options.autoUnmount) {
283+
this.unmountAllComponents();
284+
}
285+
this.stopMonitoring();
286+
};
287+
288+
window.addEventListener('beforeunload', handleUnload);
289+
window.addEventListener('unload', handleUnload);
290+
291+
const cleanup = () => {
292+
window.removeEventListener('beforeunload', handleUnload);
293+
window.removeEventListener('unload', handleUnload);
294+
};
295+
296+
this.observers.add(cleanup);
297+
}
298+
299+
/**
300+
* Debounced mutation handler to avoid excessive re-scanning
301+
* @private
302+
*/
303+
debouncedHandleMutations(mutations) {
304+
if (this.debounceTimer) {
305+
clearTimeout(this.debounceTimer);
306+
}
307+
308+
this.debounceTimer = setTimeout(() => {
309+
this.handleMutations(mutations);
310+
}, this.options.debounceMs);
311+
}
312+
313+
/**
314+
* Debounced scan and mount to avoid excessive operations
315+
* @private
316+
*/
317+
debouncedScanAndMount() {
318+
if (this.debounceTimer) {
319+
clearTimeout(this.debounceTimer);
320+
}
321+
322+
this.debounceTimer = setTimeout(() => {
323+
this.scanAndMount();
324+
}, this.options.debounceMs);
325+
}
326+
327+
/**
328+
* Handle DOM mutations and update components accordingly
329+
* @private
330+
*/
331+
handleMutations(mutations) {
332+
let shouldScan = false;
333+
const removedNodes = new Set();
334+
335+
mutations.forEach(mutation => {
336+
// Handle removed nodes
337+
if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
338+
mutation.removedNodes.forEach(node => {
339+
if (node.nodeType === Node.ELEMENT_NODE) {
340+
removedNodes.add(node);
341+
// Check if removed node or its children had mounted components
342+
this.unmountRemovedComponents(node);
343+
}
344+
});
345+
}
346+
347+
// Handle added nodes or attribute changes
348+
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
349+
shouldScan = true;
350+
} else if (mutation.type === 'attributes' &&
351+
(mutation.attributeName === 'data-svelte-component' ||
352+
mutation.attributeName === 'data-svelte-props')) {
353+
shouldScan = true;
354+
}
355+
});
356+
357+
if (shouldScan && this.options.autoMount) {
358+
this.scanAndMount();
359+
}
360+
}
361+
362+
/**
363+
* Scan for new components and mount them
364+
* @private
365+
*/
366+
async scanAndMount() {
367+
try {
368+
const mounted = await this.mountComponents();
369+
if (mounted > 0) {
370+
console.log(`[Arizona Svelte] 🔄 Auto-mounted ${mounted} new components`);
371+
}
372+
} catch (error) {
373+
console.error('[Arizona Svelte] Error during auto-mount:', error);
374+
}
375+
}
376+
377+
/**
378+
* Unmount components that were removed from DOM
379+
* @private
380+
*/
381+
unmountRemovedComponents(removedNode) {
382+
if (!this.options.autoUnmount) {
383+
return;
384+
}
385+
386+
// Check if the removed node itself was a component target
387+
if (this.mountedComponents.has(removedNode)) {
388+
console.log('[Arizona Svelte] Auto-unmounting removed component');
389+
this.unmountComponent(removedNode);
390+
}
391+
392+
// Check children of removed node
393+
if (removedNode.querySelectorAll) {
394+
const childTargets = removedNode.querySelectorAll('[data-svelte-component]');
395+
childTargets.forEach(target => {
396+
if (this.mountedComponents.has(target)) {
397+
console.log('[Arizona Svelte] Auto-unmounting removed child component');
398+
this.unmountComponent(target);
399+
}
400+
});
401+
}
402+
}
403+
404+
/**
405+
* Get monitoring status
406+
* @returns {boolean}
407+
*/
408+
isMonitoringActive() {
409+
return this.isMonitoring;
410+
}
411+
412+
/**
413+
* Get current monitoring options
414+
* @returns {Object}
415+
*/
416+
getMonitoringOptions() {
417+
return { ...this.options };
418+
}
419+
420+
/**
421+
* Update monitoring options
422+
* @param {Object} newOptions - New options to merge
423+
*/
424+
updateMonitoringOptions(newOptions) {
425+
this.options = { ...this.options, ...newOptions };
426+
}
126427
}
127428

128429
export { ArizonaSvelteLifecycle };

priv/templates/arizona.svelte/assets/js/main.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ import ArizonaSvelte from './arizona-svelte.js';
55
globalThis.arizona = new Arizona({ logLevel: 'debug' });
66
arizona.connect({ wsPath: '/live' });
77

8-
// Initialize ArizonaSvelte
8+
// Initialize ArizonaSvelte with automatic monitoring
99
const arizonaSvelte = new ArizonaSvelte();
10-
arizonaSvelte.mountComponents();
10+
11+
// Start automatic monitoring - components will mount/unmount automatically
12+
arizonaSvelte.startMonitoring({
13+
autoMount: true, // Automatically mount new components
14+
autoUnmount: true, // Automatically unmount removed components
15+
observeSubtree: true, // Monitor the entire DOM tree
16+
debounceMs: 0 // Debounce DOM changes for 0ms
17+
});
18+
19+
// Make available globally for debugging
20+
globalThis.arizonaSvelte = arizonaSvelte;
21+
22+
// Add some helpful logging
23+
console.log('[Arizona Svelte] 🚀 Automatic component monitoring started');
24+
console.log('[Arizona Svelte] 🧪 LifecycleDemo component available in UI');
25+
console.log('[Arizona Svelte] 🔍 Global access: window.arizonaSvelte');

0 commit comments

Comments
 (0)