UNPKG

muspe-cli

Version:

MusPE Advanced Framework v2.1.3 - Mobile User-friendly Simple Progressive Engine with Enhanced CLI Tools, Specialized E-Commerce Templates, Material Design 3, Progressive Enhancement, Mobile Optimizations, Performance Analysis, and Enterprise-Grade Develo

515 lines (434 loc) 15.1 kB
// MusPE Development Tools - Advanced debugging and development utilities class MusPEDevTools { constructor() { this.enabled = false; this.componentTree = new Map(); this.performanceMetrics = new Map(); this.eventLog = []; this.maxEventLog = 1000; this.inspectedComponent = null; this.devPanel = null; this.init(); } init() { // Only enable in development if (this.isDevelopment()) { this.enabled = true; this.setupDevPanel(); this.setupGlobalHooks(); this.setupPerformanceMonitoring(); console.log('🛠️ MusPE DevTools enabled'); } } isDevelopment() { return location.hostname === 'localhost' || location.hostname === '127.0.0.1' || location.search.includes('muspe-debug=true'); } setupDevPanel() { // Create floating dev panel this.devPanel = document.createElement('div'); this.devPanel.id = 'muspe-devtools'; this.devPanel.style.cssText = ` position: fixed; top: 10px; right: 10px; width: 300px; min-height: 200px; background: rgba(0, 0, 0, 0.9); color: #fff; border-radius: 8px; padding: 16px; font-family: monospace; font-size: 12px; z-index: 10000; max-height: 80vh; overflow-y: auto; transition: all 0.3s ease; transform: translateX(320px); `; this.devPanel.innerHTML = this.getDevPanelHTML(); document.body.appendChild(this.devPanel); // Toggle button const toggleBtn = document.createElement('button'); toggleBtn.innerHTML = '🛠️'; toggleBtn.style.cssText = ` position: fixed; top: 10px; right: 10px; width: 40px; height: 40px; border: none; border-radius: 50%; background: #007acc; color: white; font-size: 16px; cursor: pointer; z-index: 10001; `; toggleBtn.onclick = () => this.togglePanel(); document.body.appendChild(toggleBtn); this.setupPanelEvents(); } getDevPanelHTML() { return ` <div class="devtools-header"> <h3>MusPE DevTools</h3> <button id="close-devtools" style="float: right; background: none; border: none; color: white; cursor: pointer;">×</button> </div> <div class="devtools-tabs"> <button class="tab-btn active" data-tab="components">Components</button> <button class="tab-btn" data-tab="performance">Performance</button> <button class="tab-btn" data-tab="events">Events</button> <button class="tab-btn" data-tab="store">Store</button> </div> <div class="devtools-content"> <div id="components-tab" class="tab-content active"> <div id="component-tree"></div> <div id="component-inspector" style="display: none;"> <h4>Component Inspector</h4> <div id="component-details"></div> </div> </div> <div id="performance-tab" class="tab-content"> <div id="performance-metrics"></div> </div> <div id="events-tab" class="tab-content"> <button id="clear-events">Clear Events</button> <div id="event-log"></div> </div> <div id="store-tab" class="tab-content"> <div id="store-inspector"></div> </div> </div> `; } setupPanelEvents() { // Tab switching this.devPanel.querySelectorAll('.tab-btn').forEach(btn => { btn.onclick = () => { const tab = btn.dataset.tab; this.switchTab(tab); }; }); // Close button this.devPanel.querySelector('#close-devtools').onclick = () => { this.hidePanel(); }; // Clear events const clearEventsBtn = this.devPanel.querySelector('#clear-events'); if (clearEventsBtn) { clearEventsBtn.onclick = () => { this.eventLog = []; this.updateEventLog(); }; } } togglePanel() { if (this.devPanel.style.transform === 'translateX(0px)') { this.hidePanel(); } else { this.showPanel(); } } showPanel() { this.devPanel.style.transform = 'translateX(0px)'; this.updateComponentTree(); this.updatePerformanceMetrics(); this.updateEventLog(); this.updateStoreInspector(); } hidePanel() { this.devPanel.style.transform = 'translateX(320px)'; } switchTab(tab) { // Update tab buttons this.devPanel.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tab); }); // Update tab content this.devPanel.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === `${tab}-tab`); }); // Update content based on tab switch (tab) { case 'components': this.updateComponentTree(); break; case 'performance': this.updatePerformanceMetrics(); break; case 'events': this.updateEventLog(); break; case 'store': this.updateStoreInspector(); break; } } setupGlobalHooks() { // Hook into MusPE events if (window.MusPE) { MusPE.on('component:mounted', (event) => { this.trackComponent(event.detail); this.logEvent('Component Mounted', event.detail); }); MusPE.on('component:unmounted', (event) => { this.untrackComponent(event.detail); this.logEvent('Component Unmounted', event.detail); }); MusPE.on('route:changed', (event) => { this.logEvent('Route Changed', event.detail); }); MusPE.on('error', (event) => { this.logEvent('Error', event.detail, 'error'); }); } // Hook into component lifecycle if (window.MusPEComponent) { const originalMount = MusPEComponent.prototype.mount; MusPEComponent.prototype.mount = function(container) { const startTime = performance.now(); const result = originalMount.call(this, container); const endTime = performance.now(); devTools.recordPerformance('mount', this.constructor.name, endTime - startTime); devTools.trackComponent(this); return result; }; const originalUpdate = MusPEComponent.prototype.update; MusPEComponent.prototype.update = function() { const startTime = performance.now(); const result = originalUpdate.call(this); const endTime = performance.now(); devTools.recordPerformance('update', this.constructor.name, endTime - startTime); return result; }; } } setupPerformanceMonitoring() { // Monitor render performance if (window.render) { const originalRender = window.render; window.render = function(vnode, container) { const startTime = performance.now(); const result = originalRender(vnode, container); const endTime = performance.now(); devTools.recordPerformance('render', 'VDOM', endTime - startTime); return result; }; } // Monitor navigation performance if (window.router) { const originalNavigate = router.navigate; router.navigate = function(path, options) { const startTime = performance.now(); const result = originalNavigate.call(this, path, options); const endTime = performance.now(); devTools.recordPerformance('navigation', path, endTime - startTime); return result; }; } } trackComponent(component) { const id = this.getComponentId(component); this.componentTree.set(id, { component, name: component.constructor.name, props: component.props, state: component.state, mounted: Date.now() }); } untrackComponent(component) { const id = this.getComponentId(component); this.componentTree.delete(id); } getComponentId(component) { if (!component._devId) { component._devId = `component_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } return component._devId; } recordPerformance(type, name, duration) { const key = `${type}:${name}`; if (!this.performanceMetrics.has(key)) { this.performanceMetrics.set(key, { type, name, calls: 0, totalTime: 0, averageTime: 0, maxTime: 0, minTime: Infinity }); } const metric = this.performanceMetrics.get(key); metric.calls++; metric.totalTime += duration; metric.averageTime = metric.totalTime / metric.calls; metric.maxTime = Math.max(metric.maxTime, duration); metric.minTime = Math.min(metric.minTime, duration); } logEvent(type, data, level = 'info') { const event = { timestamp: Date.now(), type, data, level }; this.eventLog.unshift(event); // Limit log size if (this.eventLog.length > this.maxEventLog) { this.eventLog.splice(this.maxEventLog); } // Console logging const style = level === 'error' ? 'color: red' : 'color: blue'; console.log(`%c[MusPE DevTools] ${type}`, style, data); } updateComponentTree() { const treeContainer = this.devPanel.querySelector('#component-tree'); if (!treeContainer) return; const components = Array.from(this.componentTree.values()); treeContainer.innerHTML = ` <h4>Component Tree (${components.length})</h4> ${components.map(comp => ` <div class="component-item" data-component-id="${this.getComponentId(comp.component)}" style="margin: 4px 0; padding: 4px; border: 1px solid #333; cursor: pointer;"> <strong>${comp.name}</strong> <br><small>Mounted: ${new Date(comp.mounted).toLocaleTimeString()}</small> </div> `).join('')} `; // Add click handlers for component inspection treeContainer.querySelectorAll('.component-item').forEach(item => { item.onclick = () => { const componentId = item.dataset.componentId; this.inspectComponent(componentId); }; }); } inspectComponent(componentId) { const componentData = this.componentTree.get(componentId); if (!componentData) return; this.inspectedComponent = componentData; const inspector = this.devPanel.querySelector('#component-inspector'); const details = this.devPanel.querySelector('#component-details'); if (!inspector || !details) return; inspector.style.display = 'block'; details.innerHTML = ` <h5>${componentData.name}</h5> <div style="margin: 8px 0;"> <strong>Props:</strong> <pre style="font-size: 10px; overflow: auto; max-height: 100px;">${JSON.stringify(componentData.props, null, 2)}</pre> </div> <div style="margin: 8px 0;"> <strong>State:</strong> <pre style="font-size: 10px; overflow: auto; max-height: 100px;">${JSON.stringify(componentData.state, null, 2)}</pre> </div> <button onclick="devTools.highlightComponent('${componentId}')">Highlight in DOM</button> `; } highlightComponent(componentId) { const componentData = this.componentTree.get(componentId); if (!componentData || !componentData.component.element) return; const element = componentData.component.element; const originalBorder = element.style.border; element.style.border = '2px solid #ff6b6b'; element.style.transition = 'border 0.3s'; setTimeout(() => { element.style.border = originalBorder; }, 2000); } updatePerformanceMetrics() { const metricsContainer = this.devPanel.querySelector('#performance-metrics'); if (!metricsContainer) return; const metrics = Array.from(this.performanceMetrics.values()) .sort((a, b) => b.averageTime - a.averageTime); metricsContainer.innerHTML = ` <h4>Performance Metrics</h4> ${metrics.map(metric => ` <div style="margin: 4px 0; padding: 4px; border: 1px solid #333;"> <div><strong>${metric.type}: ${metric.name}</strong></div> <div style="font-size: 10px;"> Calls: ${metric.calls} | Avg: ${metric.averageTime.toFixed(2)}ms | Max: ${metric.maxTime.toFixed(2)}ms </div> </div> `).join('')} `; } updateEventLog() { const eventContainer = this.devPanel.querySelector('#event-log'); if (!eventContainer) return; eventContainer.innerHTML = this.eventLog.slice(0, 50).map(event => { const time = new Date(event.timestamp).toLocaleTimeString(); const levelColor = event.level === 'error' ? '#ff6b6b' : '#4ecdc4'; return ` <div style="margin: 2px 0; padding: 4px; border-left: 3px solid ${levelColor}; font-size: 10px;"> <div><strong>${event.type}</strong> <span style="float: right;">${time}</span></div> <div style="color: #ccc;">${JSON.stringify(event.data).substring(0, 100)}...</div> </div> `; }).join(''); } updateStoreInspector() { const storeContainer = this.devPanel.querySelector('#store-inspector'); if (!storeContainer) return; let storeData = {}; // Collect store data from various sources if (window.MusPE && MusPE.provided) { storeData.provided = Object.fromEntries(MusPE.provided); } if (window.useGlobalState) { try { storeData.globalState = useGlobalState(); } catch (e) { // Ignore if not available } } storeContainer.innerHTML = ` <h4>Store Inspector</h4> <pre style="font-size: 10px; overflow: auto; max-height: 200px; color: #ccc;">${JSON.stringify(storeData, null, 2)}</pre> `; } // Console commands setupConsoleCommands() { window.$muspe = { components: () => Array.from(this.componentTree.values()), performance: () => Array.from(this.performanceMetrics.values()), events: () => this.eventLog, inspect: (componentId) => this.inspectComponent(componentId), highlight: (componentId) => this.highlightComponent(componentId), clear: () => { this.eventLog = []; this.performanceMetrics.clear(); console.clear(); } }; console.log('🛠️ MusPE DevTools console commands available:'); console.log('$muspe.components() - List all components'); console.log('$muspe.performance() - Performance metrics'); console.log('$muspe.events() - Event log'); console.log('$muspe.clear() - Clear data'); } } // Create global dev tools instance const devTools = new MusPEDevTools(); // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = { MusPEDevTools, devTools }; } // Make available globally if (typeof window !== 'undefined') { window.MusPEDevTools = MusPEDevTools; window.devTools = devTools; // Add to MusPE if (window.MusPE) { MusPE.devTools = devTools; } // Setup console commands devTools.setupConsoleCommands(); }