UNPKG

neumorphic-peripheral

Version:

A lightweight, framework-agnostic JavaScript/TypeScript library for beautiful neumorphic styling

473 lines (472 loc) 16.2 kB
"use strict"; /** * Performance optimization utilities for Neumorphic Peripheral * * This module provides tools for optimizing component performance, * monitoring bundle size, and implementing efficient patterns. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.memoryManager = exports.throttleDebounce = exports.lazyLoader = exports.MemoryManager = exports.BundleSizeAnalyzer = exports.VirtualScroller = exports.ThrottleDebounce = exports.LazyLoader = exports.performanceMonitor = void 0; exports.withPerformanceMonitoring = withPerformanceMonitoring; exports.autoCleanup = autoCleanup; class PerformanceMonitor { constructor() { this.metrics = new Map(); this.observers = []; this.resizeObservers = []; } /** * Measure component initialization time */ measureInit(componentName, initFunction) { const startTime = performance.now(); const result = initFunction(); const endTime = performance.now(); this.updateMetric(componentName, 'componentInitTime', endTime - startTime); return result; } /** * Measure render time for DOM operations */ measureRender(componentName, renderFunction) { const startTime = performance.now(); renderFunction(); // Use requestAnimationFrame to measure actual render time requestAnimationFrame(() => { const endTime = performance.now(); this.updateMetric(componentName, 'renderTime', endTime - startTime); }); } /** * Measure event handler performance */ measureEventHandler(componentName, eventHandler) { return (...args) => { const startTime = performance.now(); const result = eventHandler(...args); const endTime = performance.now(); this.updateMetric(componentName, 'eventHandlerTime', endTime - startTime); return result; }; } /** * Monitor memory usage */ measureMemory(componentName) { if ('memory' in performance) { const memInfo = performance.memory; this.updateMetric(componentName, 'memoryUsage', memInfo.usedJSHeapSize); } } /** * Get performance metrics for a component */ getMetrics(componentName) { return this.metrics.get(componentName); } /** * Get all performance metrics */ getAllMetrics() { const result = {}; this.metrics.forEach((metrics, name) => { result[name] = metrics; }); return result; } /** * Reset metrics for a component */ resetMetrics(componentName) { this.metrics.delete(componentName); } /** * Clear all metrics */ clearAllMetrics() { this.metrics.clear(); } updateMetric(componentName, metricType, value) { let metrics = this.metrics.get(componentName); if (!metrics) { metrics = { componentInitTime: 0, renderTime: 0, eventHandlerTime: 0, memoryUsage: 0, bundleSize: 0 }; this.metrics.set(componentName, metrics); } metrics[metricType] = value; } /** * Clean up observers */ cleanup() { this.observers.forEach(observer => observer.disconnect()); this.resizeObservers.forEach(observer => observer.disconnect()); this.observers = []; this.resizeObservers = []; } } // Global performance monitor instance exports.performanceMonitor = new PerformanceMonitor(); /** * Lazy loading utilities */ class LazyLoader { constructor() { this.loadedComponents = new Set(); this.setupIntersectionObserver(); } /** * Lazy load a component when it enters viewport */ lazyLoad(element, componentLoader, componentName) { return new Promise((resolve) => { if (this.loadedComponents.has(componentName)) { resolve(null); return; } const loadComponent = async () => { try { const component = await componentLoader(); this.loadedComponents.add(componentName); resolve(component); } catch (error) { console.error(`Failed to lazy load component ${componentName}:`, error); resolve(null); } }; // Check if element is already in viewport const rect = element.getBoundingClientRect(); const isInViewport = rect.top < window.innerHeight && rect.bottom > 0; if (isInViewport) { loadComponent(); } else { // Wait for element to enter viewport const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { loadComponent(); observer.unobserve(element); } }); }); observer.observe(element); } }); } setupIntersectionObserver() { if (typeof IntersectionObserver !== 'undefined') { this.intersectionObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.dispatchEvent(new CustomEvent('np:in-viewport')); } }); }, { threshold: 0.1 }); } } /** * Clean up resources */ cleanup() { this.intersectionObserver?.disconnect(); this.loadedComponents.clear(); } } exports.LazyLoader = LazyLoader; /** * Throttling and debouncing utilities */ class ThrottleDebounce { constructor() { this.throttleTimers = new Map(); this.debounceTimers = new Map(); } /** * Throttle function execution */ throttle(key, func, limit) { return (...args) => { if (!this.throttleTimers.has(key)) { func.apply(this, args); this.throttleTimers.set(key, setTimeout(() => { this.throttleTimers.delete(key); }, limit)); } }; } /** * Debounce function execution */ debounce(key, func, delay) { return (...args) => { const existingTimer = this.debounceTimers.get(key); if (existingTimer) { clearTimeout(existingTimer); } const timer = setTimeout(() => { func.apply(this, args); this.debounceTimers.delete(key); }, delay); this.debounceTimers.set(key, timer); }; } /** * Clear all timers */ cleanup() { this.throttleTimers.forEach(timer => clearTimeout(timer)); this.debounceTimers.forEach(timer => clearTimeout(timer)); this.throttleTimers.clear(); this.debounceTimers.clear(); } } exports.ThrottleDebounce = ThrottleDebounce; /** * Virtual scrolling for large lists */ class VirtualScroller { constructor(container, itemHeight, renderItem) { this.scrollTop = 0; this.container = container; this.itemHeight = itemHeight; this.renderItem = renderItem; this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2; this.totalItems = 0; this.setupScrollListener(); } /** * Set total number of items */ setTotalItems(count) { this.totalItems = count; this.updateContainer(); } /** * Update visible items based on scroll position */ updateVisibleItems() { const startIndex = Math.floor(this.scrollTop / this.itemHeight); const endIndex = Math.min(startIndex + this.visibleItems, this.totalItems); // Clear container this.container.innerHTML = ''; // Create spacer for items above viewport const topSpacer = document.createElement('div'); topSpacer.style.height = `${startIndex * this.itemHeight}px`; this.container.appendChild(topSpacer); // Render visible items for (let i = startIndex; i < endIndex; i++) { const item = this.renderItem(i); item.style.height = `${this.itemHeight}px`; this.container.appendChild(item); } // Create spacer for items below viewport const bottomSpacer = document.createElement('div'); bottomSpacer.style.height = `${(this.totalItems - endIndex) * this.itemHeight}px`; this.container.appendChild(bottomSpacer); } setupScrollListener() { const throttledUpdate = new ThrottleDebounce().throttle('virtual-scroll', () => this.updateVisibleItems(), 16 // ~60fps ); this.container.addEventListener('scroll', () => { this.scrollTop = this.container.scrollTop; throttledUpdate(); }); } updateContainer() { this.container.style.height = `${this.totalItems * this.itemHeight}px`; this.container.style.overflow = 'auto'; this.updateVisibleItems(); } /** * Clean up event listeners */ cleanup() { // Remove scroll listeners (would need to store reference) } } exports.VirtualScroller = VirtualScroller; /** * Bundle size analyzer */ class BundleSizeAnalyzer { /** * Estimate bundle size of imported modules */ static async analyzeBundleSize() { const sizes = { core: 0, components: {}, total: 0 }; // Estimate sizes (in production, these would be actual measurements) sizes.core = this.estimateModuleSize('core', [ 'themes', 'utils', 'validators', 'types' ]); sizes.components.card = this.estimateModuleSize('card', ['base', 'utils']); sizes.components.input = this.estimateModuleSize('input', ['base', 'utils', 'validators']); sizes.components.password = this.estimateModuleSize('password', ['input', 'validators']); sizes.components.button = this.estimateModuleSize('button', ['base', 'utils']); sizes.components.textarea = this.estimateModuleSize('textarea', ['input', 'utils']); sizes.components.toggle = this.estimateModuleSize('toggle', ['base', 'utils']); sizes.total = sizes.core + Object.values(sizes.components).reduce((a, b) => a + b, 0); return sizes; } /** * Track actual bundle size in production */ static trackBundleSize() { if (typeof window !== 'undefined' && 'performance' in window) { const navigation = performance.getEntriesByType('navigation')[0]; if (navigation) { exports.performanceMonitor.updateMetric('bundle', 'bundleSize', navigation.transferSize || 0); } } } static estimateModuleSize(moduleName, dependencies) { // Base size estimates (in bytes, minified + gzipped) const baseSizes = { core: 3000, base: 2000, card: 1500, input: 2500, password: 1000, // Additional on top of input button: 1800, textarea: 800, // Additional on top of input toggle: 2200, themes: 800, utils: 1200, validators: 1500, types: 100 // TypeScript definitions don't add runtime size }; let size = baseSizes[moduleName] || 1000; // Add dependency sizes (with some overlap reduction) dependencies.forEach(dep => { const depSize = baseSizes[dep] || 500; size += Math.floor(depSize * 0.3); // Assume 30% of dependency size due to tree-shaking }); return size; } /** * Recommend optimizations based on usage */ static getOptimizationRecommendations(usedComponents) { const recommendations = []; const totalComponents = ['card', 'input', 'password', 'button', 'textarea', 'toggle']; const unusedComponents = totalComponents.filter(c => !usedComponents.includes(c)); if (unusedComponents.length > 0) { recommendations.push(`Consider tree-shaking unused components: ${unusedComponents.join(', ')}`); } if (usedComponents.includes('password') && !usedComponents.includes('input')) { recommendations.push('Password component includes Input component code - this is expected'); } if (usedComponents.length === 1) { recommendations.push('Great! You\'re only importing what you need. Consider named imports for even better tree-shaking.'); } if (usedComponents.length > 4) { recommendations.push('Consider using the main bundle import if you\'re using most components'); } return recommendations; } } exports.BundleSizeAnalyzer = BundleSizeAnalyzer; /** * Memory leak detection and prevention */ class MemoryManager { constructor() { this.componentReferences = new WeakMap(); this.globalListeners = new Map(); } /** * Track component event listeners for cleanup */ trackEventListener(element, event, listener) { let listeners = this.componentReferences.get(element); if (!listeners) { listeners = new Set(); this.componentReferences.set(element, listeners); } listeners.add(listener); } /** * Clean up all listeners for an element */ cleanupElement(element) { const listeners = this.componentReferences.get(element); if (listeners) { listeners.clear(); this.componentReferences.delete(element); } } /** * Detect potential memory leaks */ detectMemoryLeaks() { const recommendations = []; let potentialLeaks = 0; // Check for detached DOM nodes with listeners this.componentReferences.forEach((listeners, element) => { if (!document.contains(element)) { potentialLeaks++; recommendations.push(`Detached element with ${listeners.size} listeners found. Call destroy() method.`); } }); // Check global listeners if (this.globalListeners.size > 10) { recommendations.push(`${this.globalListeners.size} global listeners registered. Consider cleanup.`); } return { potentialLeaks, recommendations }; } /** * Force garbage collection (if available) */ forceGarbageCollection() { if ('gc' in window) { window.gc(); } } } exports.MemoryManager = MemoryManager; // Global instances exports.lazyLoader = new LazyLoader(); exports.throttleDebounce = new ThrottleDebounce(); exports.memoryManager = new MemoryManager(); /** * Performance optimization decorators */ function withPerformanceMonitoring(componentName) { return function (constructor) { return class extends constructor { constructor(...args) { const result = exports.performanceMonitor.measureInit(componentName, () => { super(...args); }); // Monitor memory after initialization setTimeout(() => { exports.performanceMonitor.measureMemory(componentName); }, 0); } }; }; } /** * Cleanup utility for preventing memory leaks */ function autoCleanup() { if (typeof window !== 'undefined') { window.addEventListener('beforeunload', () => { exports.performanceMonitor.cleanup(); exports.lazyLoader.cleanup(); exports.throttleDebounce.cleanup(); }); } } // Auto-initialize cleanup autoCleanup();