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
JavaScript
// 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();
}