274 lines
8.3 KiB
JavaScript
274 lines
8.3 KiB
JavaScript
/**
|
|
* Performance Optimizer for Odoo Expertos
|
|
* Adds lazy loading, caching, and optimizations without breaking existing functionality
|
|
*/
|
|
|
|
// 1. Lazy Load Images
|
|
function setupLazyLoading() {
|
|
// Create Intersection Observer for lazy loading images
|
|
const imageObserver = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
|
|
// Load the actual image
|
|
if (img.dataset.src) {
|
|
img.src = img.dataset.src;
|
|
img.removeAttribute('data-src');
|
|
}
|
|
|
|
// Remove lazy loading class
|
|
img.classList.remove('lazy-bg');
|
|
|
|
// Stop observing this image
|
|
observer.unobserve(img);
|
|
}
|
|
});
|
|
}, {
|
|
rootMargin: '50px' // Start loading 50px before entering viewport
|
|
});
|
|
|
|
// Observe all images with data-src attribute
|
|
document.querySelectorAll('img[data-src]').forEach(img => {
|
|
imageObserver.observe(img);
|
|
});
|
|
}
|
|
|
|
// 2. Defer D3.js Loading
|
|
function loadD3Async() {
|
|
// Only load D3 when needed
|
|
if (document.querySelector('.visualization, #network-visualization, .hero-visual')) {
|
|
const script = document.createElement('script');
|
|
script.src = 'https://d3js.org/d3.v7.min.js';
|
|
script.async = true;
|
|
script.onload = () => {
|
|
// Initialize D3 visualizations after loading
|
|
if (typeof initializeVisualizations === 'function') {
|
|
initializeVisualizations();
|
|
}
|
|
};
|
|
document.head.appendChild(script);
|
|
}
|
|
}
|
|
|
|
// 3. Resource Hints for Critical Resources
|
|
function addResourceHints() {
|
|
const hints = [
|
|
{ rel: 'preconnect', href: 'https://d3js.org' },
|
|
{ rel: 'preconnect', href: 'https://images.pexels.com' },
|
|
{ rel: 'dns-prefetch', href: 'https://fonts.googleapis.com' }
|
|
];
|
|
|
|
hints.forEach(hint => {
|
|
const link = document.createElement('link');
|
|
link.rel = hint.rel;
|
|
link.href = hint.href;
|
|
if (hint.rel === 'preconnect') {
|
|
link.crossOrigin = '';
|
|
}
|
|
document.head.appendChild(link);
|
|
});
|
|
}
|
|
|
|
// 4. Cache API Responses using localStorage with TTL
|
|
class CacheManager {
|
|
constructor() {
|
|
this.prefix = 'odoo_cache_';
|
|
this.defaultTTL = 3600000; // 1 hour in milliseconds
|
|
}
|
|
|
|
set(key, data, ttl = this.defaultTTL) {
|
|
const cacheData = {
|
|
data: data,
|
|
timestamp: Date.now(),
|
|
ttl: ttl
|
|
};
|
|
|
|
try {
|
|
localStorage.setItem(this.prefix + key, JSON.stringify(cacheData));
|
|
} catch (e) {
|
|
// Handle quota exceeded
|
|
this.clearOldCache();
|
|
try {
|
|
localStorage.setItem(this.prefix + key, JSON.stringify(cacheData));
|
|
} catch (e) {
|
|
console.warn('Cache storage full');
|
|
}
|
|
}
|
|
}
|
|
|
|
get(key) {
|
|
const item = localStorage.getItem(this.prefix + key);
|
|
if (!item) return null;
|
|
|
|
try {
|
|
const cacheData = JSON.parse(item);
|
|
const now = Date.now();
|
|
|
|
// Check if cache is expired
|
|
if (now - cacheData.timestamp > cacheData.ttl) {
|
|
localStorage.removeItem(this.prefix + key);
|
|
return null;
|
|
}
|
|
|
|
return cacheData.data;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
clearOldCache() {
|
|
const now = Date.now();
|
|
const keys = Object.keys(localStorage);
|
|
|
|
keys.forEach(key => {
|
|
if (key.startsWith(this.prefix)) {
|
|
try {
|
|
const item = JSON.parse(localStorage.getItem(key));
|
|
if (now - item.timestamp > item.ttl) {
|
|
localStorage.removeItem(key);
|
|
}
|
|
} catch (e) {
|
|
localStorage.removeItem(key);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 5. Optimize Component Loading
|
|
function optimizeComponentLoading() {
|
|
// Load header and footer asynchronously
|
|
const loadComponent = (scriptSrc, callback) => {
|
|
const script = document.createElement('script');
|
|
script.src = scriptSrc;
|
|
script.async = true;
|
|
script.onload = callback;
|
|
document.head.appendChild(script);
|
|
};
|
|
|
|
// Load components in parallel
|
|
Promise.all([
|
|
new Promise(resolve => loadComponent('/components/header.js', resolve)),
|
|
new Promise(resolve => loadComponent('/components/footer.js', resolve))
|
|
]).then(() => {
|
|
// Initialize components after loading
|
|
if (typeof createHeader === 'function') {
|
|
const headerPlaceholder = document.getElementById('header-placeholder');
|
|
if (headerPlaceholder) {
|
|
headerPlaceholder.innerHTML = createHeader();
|
|
}
|
|
}
|
|
|
|
if (typeof createFooter === 'function') {
|
|
const footerPlaceholder = document.getElementById('footer-placeholder');
|
|
if (footerPlaceholder) {
|
|
footerPlaceholder.innerHTML = createFooter();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 6. Request Idle Callback for Non-Critical Tasks
|
|
function deferNonCritical() {
|
|
if ('requestIdleCallback' in window) {
|
|
requestIdleCallback(() => {
|
|
// Load cookie banner
|
|
const cookieScript = document.createElement('script');
|
|
cookieScript.src = '/js/cookie-banner.js';
|
|
cookieScript.async = true;
|
|
document.body.appendChild(cookieScript);
|
|
|
|
// Preload next page resources
|
|
preloadNextPage();
|
|
});
|
|
} else {
|
|
// Fallback for browsers without requestIdleCallback
|
|
setTimeout(() => {
|
|
const cookieScript = document.createElement('script');
|
|
cookieScript.src = '/js/cookie-banner.js';
|
|
cookieScript.async = true;
|
|
document.body.appendChild(cookieScript);
|
|
|
|
preloadNextPage();
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
// 7. Preload likely next pages
|
|
function preloadNextPage() {
|
|
// Preload links that are likely to be clicked
|
|
const links = document.querySelectorAll('a[href^="/"]');
|
|
const preloadedUrls = new Set();
|
|
|
|
links.forEach(link => {
|
|
link.addEventListener('mouseenter', () => {
|
|
const href = link.getAttribute('href');
|
|
if (!preloadedUrls.has(href)) {
|
|
const prefetch = document.createElement('link');
|
|
prefetch.rel = 'prefetch';
|
|
prefetch.href = href;
|
|
document.head.appendChild(prefetch);
|
|
preloadedUrls.add(href);
|
|
}
|
|
}, { once: true });
|
|
});
|
|
}
|
|
|
|
// 8. WebP Image Support Detection
|
|
function checkWebPSupport(callback) {
|
|
const webP = new Image();
|
|
webP.onload = webP.onerror = function () {
|
|
callback(webP.height === 2);
|
|
};
|
|
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
|
|
}
|
|
|
|
// 9. Initialize Performance Optimizations
|
|
function initializeOptimizations() {
|
|
// Add resource hints immediately
|
|
addResourceHints();
|
|
|
|
// Setup lazy loading for images
|
|
if ('IntersectionObserver' in window) {
|
|
setupLazyLoading();
|
|
}
|
|
|
|
// Load D3.js only when needed
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', loadD3Async);
|
|
} else {
|
|
loadD3Async();
|
|
}
|
|
|
|
// Defer non-critical resources
|
|
deferNonCritical();
|
|
|
|
// Check WebP support
|
|
checkWebPSupport(supported => {
|
|
if (supported) {
|
|
document.documentElement.classList.add('webp');
|
|
}
|
|
});
|
|
|
|
// Initialize cache manager
|
|
window.cacheManager = new CacheManager();
|
|
|
|
// Clear old cache on load
|
|
window.cacheManager.clearOldCache();
|
|
}
|
|
|
|
// Start optimizations
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initializeOptimizations);
|
|
} else {
|
|
initializeOptimizations();
|
|
}
|
|
|
|
// Export for use in other scripts
|
|
window.performanceOptimizer = {
|
|
CacheManager,
|
|
setupLazyLoading,
|
|
loadD3Async,
|
|
checkWebPSupport
|
|
}; |