Skip to main content
Glama
juanqui
by juanqui
performance.js12.4 kB
/** * Performance Optimization Utilities * Additional performance enhancements for the PDF KB frontend */ // Performance monitoring and optimization utilities class PerformanceOptimizer { constructor() { this.observers = new Map(); this.loadTimes = new Map(); this.init(); } init() { // Initialize performance monitoring if ('performance' in window) { this.startLoadTimeTracking(); this.initIntersectionObserver(); this.initIdleCallback(); } } /** * Track page load performance */ startLoadTimeTracking() { const startTime = performance.now(); window.addEventListener('load', () => { const loadTime = performance.now() - startTime; this.loadTimes.set('page', loadTime); console.log(`Page load time: ${loadTime.toFixed(2)}ms`); }); // Track navigation timing window.addEventListener('load', () => { if (performance.getEntriesByType) { const [navigation] = performance.getEntriesByType('navigation'); if (navigation) { console.log('Navigation timing:', { dns: navigation.domainLookupEnd - navigation.domainLookupStart, connection: navigation.connectEnd - navigation.connectStart, ttfb: navigation.responseStart - navigation.requestStart, download: navigation.responseEnd - navigation.responseStart, domContentLoaded: navigation.domContentLoadedEventEnd - navigation.navigationStart, complete: navigation.loadEventEnd - navigation.navigationStart }); } } }); } /** * Initialize intersection observer for lazy loading */ initIntersectionObserver() { if ('IntersectionObserver' in window) { const lazyObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const element = entry.target; // Lazy load images if (element.dataset.src) { element.src = element.dataset.src; element.removeAttribute('data-src'); } // Lazy load components if (element.dataset.lazyLoad) { this.loadComponent(element); } lazyObserver.unobserve(element); } }); }, { rootMargin: '50px', threshold: 0.1 }); this.observers.set('lazy', lazyObserver); } } /** * Initialize idle callback for non-critical tasks */ initIdleCallback() { if ('requestIdleCallback' in window) { // Schedule non-critical tasks during idle time requestIdleCallback(() => { this.preloadCriticalResources(); this.cleanupOldCache(); this.optimizeMemoryUsage(); }, { timeout: 5000 }); } } /** * Preload critical resources */ preloadCriticalResources() { // Preload component files const componentFiles = [ '/components/DocumentList.js', '/components/SearchInterface.js', '/components/DocumentDetail.js', '/components/FileUpload.js', '/components/StatusBar.js' ]; componentFiles.forEach(url => { const link = document.createElement('link'); link.rel = 'prefetch'; link.href = url; document.head.appendChild(link); }); } /** * Clean up old cache entries */ cleanupOldCache() { try { // Clean up old localStorage entries const keysToCheck = ['pdfkb_cache_', 'pdfkb_temp_']; const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000); keysToCheck.forEach(prefix => { Object.keys(localStorage).forEach(key => { if (key.startsWith(prefix)) { try { const data = JSON.parse(localStorage.getItem(key)); if (data.timestamp && data.timestamp < oneWeekAgo) { localStorage.removeItem(key); } } catch (e) { // Remove invalid entries localStorage.removeItem(key); } } }); }); } catch (error) { console.warn('Cache cleanup failed:', error); } } /** * Optimize memory usage */ optimizeMemoryUsage() { // Remove unused event listeners this.cleanupEventListeners(); // Clear unused references if (window.pdfkbApp) { // Clean up old search results if too many if (window.pdfkbApp.searchResults && window.pdfkbApp.searchResults.length > 100) { window.pdfkbApp.searchResults = window.pdfkbApp.searchResults.slice(0, 50); } } } /** * Clean up unused event listeners */ cleanupEventListeners() { // Remove observers for elements that no longer exist this.observers.forEach((observer, key) => { // Implementation would depend on specific observer tracking }); } /** * Load component lazily */ async loadComponent(element) { const componentName = element.dataset.lazyLoad; try { // Dynamic import for modern browsers if ('import' in window) { await import(`/components/${componentName}.js`); } element.classList.add('loaded'); } catch (error) { console.error(`Failed to load component ${componentName}:`, error); } } /** * Measure and report performance metrics */ getPerformanceMetrics() { const metrics = {}; // Memory usage (if available) if ('memory' in performance) { metrics.memory = { used: Math.round(performance.memory.usedJSHeapSize / 1048576), // MB total: Math.round(performance.memory.totalJSHeapSize / 1048576), // MB limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576) // MB }; } // Connection info if ('connection' in navigator) { metrics.connection = { type: navigator.connection.effectiveType, downlink: navigator.connection.downlink, rtt: navigator.connection.rtt, saveData: navigator.connection.saveData }; } // Load times metrics.loadTimes = Object.fromEntries(this.loadTimes); return metrics; } /** * Optimize images for performance */ optimizeImages() { const images = document.querySelectorAll('img[data-src]'); images.forEach(img => { // Add to lazy loading observer if (this.observers.has('lazy')) { this.observers.get('lazy').observe(img); } }); } /** * Debounce function for performance */ static debounce(func, wait, immediate = false) { let timeout; return function executedFunction(...args) { const later = () => { timeout = null; if (!immediate) func(...args); }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func(...args); }; } /** * Throttle function for performance */ static throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } /** * Virtual scrolling implementation for large lists */ static createVirtualScroller(container, items, renderItem, itemHeight = 50) { let scrollTop = 0; let startIndex = 0; let endIndex = 0; const containerHeight = container.clientHeight; const visibleCount = Math.ceil(containerHeight / itemHeight) + 2; // Buffer const updateVisibleItems = PerformanceOptimizer.throttle(() => { startIndex = Math.floor(scrollTop / itemHeight); endIndex = Math.min(startIndex + visibleCount, items.length); // Clear container container.innerHTML = ''; // Create spacer for items above if (startIndex > 0) { const spacer = document.createElement('div'); spacer.style.height = `${startIndex * itemHeight}px`; container.appendChild(spacer); } // Render visible items for (let i = startIndex; i < endIndex; i++) { const itemElement = renderItem(items[i], i); container.appendChild(itemElement); } // Create spacer for items below if (endIndex < items.length) { const spacer = document.createElement('div'); spacer.style.height = `${(items.length - endIndex) * itemHeight}px`; container.appendChild(spacer); } }, 16); // ~60fps container.addEventListener('scroll', (e) => { scrollTop = e.target.scrollTop; updateVisibleItems(); }); // Initial render updateVisibleItems(); return { update: (newItems) => { items = newItems; updateVisibleItems(); } }; } } // Web Workers for heavy computations class WebWorkerHelper { static isSupported() { return typeof Worker !== 'undefined'; } static createWorker(workerFunction) { if (!this.isSupported()) { return null; } const blob = new Blob([`(${workerFunction.toString()})()`], { type: 'application/javascript' }); return new Worker(URL.createObjectURL(blob)); } static processSearchInWorker(documents, query) { if (!this.isSupported()) { return Promise.resolve([]); } return new Promise((resolve, reject) => { const worker = this.createWorker(() => { self.onmessage = function(e) { const { documents, query } = e.data; // Simple text search in worker const results = documents.filter(doc => doc.title.toLowerCase().includes(query.toLowerCase()) || doc.content.toLowerCase().includes(query.toLowerCase()) ); self.postMessage(results); }; }); worker.onmessage = (e) => { resolve(e.data); worker.terminate(); }; worker.onerror = (error) => { reject(error); worker.terminate(); }; worker.postMessage({ documents, query }); }); } } // Initialize performance optimization when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.performanceOptimizer = new PerformanceOptimizer(); // Expose utilities globally window.PerfUtils = { debounce: PerformanceOptimizer.debounce, throttle: PerformanceOptimizer.throttle, createVirtualScroller: PerformanceOptimizer.createVirtualScroller, WebWorkerHelper }; }); // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = { PerformanceOptimizer, WebWorkerHelper }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/juanqui/pdfkb-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server