const fs = require('fs'); const path = require('path'); class CSSPerformanceAnalyzer { constructor() { this.performanceIssues = []; this.optimizations = []; this.metrics = new Map(); } analyzePerformance() { console.log('⚔ CSS PERFORMANCE ANALYSIS'); console.log('='.repeat(50)); const cssFiles = [ './assets/css/hvac-common.css', './assets/css/hvac-dashboard.css', './assets/css/hvac-registration.css', './assets/css/hvac-certificates.css', './assets/css/hvac-email-attendees.css', './assets/css/hvac-event-summary.css', './assets/css/hvac-attendee-profile.css', './assets/css/hvac-mobile-nav.css', './assets/css/hvac-animations.css', './assets/css/hvac-print.css' ]; let totalSize = 0; let totalRules = 0; let totalSelectors = 0; cssFiles.forEach(file => { if (fs.existsSync(file)) { const analysis = this.analyzeFile(file); totalSize += analysis.size; totalRules += analysis.rules; totalSelectors += analysis.selectors; } }); this.generatePerformanceReport(totalSize, totalRules, totalSelectors); } analyzeFile(filePath) { const content = fs.readFileSync(filePath, 'utf8'); const fileName = path.basename(filePath); const stats = fs.statSync(filePath); console.log(`\nšŸ” ${fileName}:`); // File size analysis const sizeKB = Math.round(stats.size / 1024 * 100) / 100; console.log(` šŸ“Š File size: ${sizeKB}KB`); if (sizeKB > 50) { this.performanceIssues.push({ file: fileName, issue: 'Large file size', impact: 'Slower initial load time', severity: 'medium', recommendation: 'Consider splitting into smaller files or minification' }); } // Selector complexity analysis const analysis = this.analyzeSelectorComplexity(content, fileName); // Media query analysis this.analyzeMediaQueries(content, fileName); // Animation performance this.analyzeAnimationPerformance(content, fileName); // CSS specificity analysis this.analyzeSpecificity(content, fileName); // Unused/duplicate code analysis this.analyzeCodeEfficiency(content, fileName); // Critical rendering path impact this.analyzeCriticalPath(content, fileName); this.metrics.set(fileName, { size: sizeKB, rules: analysis.rules, selectors: analysis.selectors, complexity: analysis.avgComplexity }); return { size: sizeKB, rules: analysis.rules, selectors: analysis.selectors }; } analyzeSelectorComplexity(content, fileName) { const selectors = content.match(/[^{}]+(?=\s*{)/g) || []; const rules = content.match(/{[^}]*}/g) || []; console.log(` šŸŽÆ Selectors: ${selectors.length}`); console.log(` šŸ“œ Rules: ${rules.length}`); // Analyze selector complexity const complexSelectors = []; const universalSelectors = []; const deepSelectors = []; const idSelectors = []; let totalComplexity = 0; selectors.forEach(selector => { const cleanSelector = selector.trim(); // Count complexity (rough heuristic) const complexity = this.calculateSelectorComplexity(cleanSelector); totalComplexity += complexity; if (complexity > 5) { complexSelectors.push({ selector: cleanSelector, complexity }); } if (cleanSelector.includes('*')) { universalSelectors.push(cleanSelector); } if (cleanSelector.split(' ').length > 4) { deepSelectors.push(cleanSelector); } if (cleanSelector.includes('#')) { idSelectors.push(cleanSelector); } }); const avgComplexity = selectors.length > 0 ? totalComplexity / selectors.length : 0; console.log(` šŸ”¢ Average selector complexity: ${Math.round(avgComplexity * 100) / 100}`); if (complexSelectors.length > 0) { console.log(` āš ļø Complex selectors: ${complexSelectors.length}`); this.performanceIssues.push({ file: fileName, issue: 'Complex selectors detected', impact: 'Slower CSS parsing and matching', severity: 'medium', count: complexSelectors.length, examples: complexSelectors.slice(0, 3).map(s => s.selector) }); } if (universalSelectors.length > 0) { console.log(` āš ļø Universal selectors: ${universalSelectors.length}`); this.performanceIssues.push({ file: fileName, issue: 'Universal selectors', impact: 'Performance impact on large DOMs', severity: 'low', count: universalSelectors.length }); } if (deepSelectors.length > 0) { console.log(` āš ļø Deep selectors (4+ levels): ${deepSelectors.length}`); this.performanceIssues.push({ file: fileName, issue: 'Deep selector nesting', impact: 'Slower selector matching', severity: 'low', count: deepSelectors.length }); } if (idSelectors.length > 10) { console.log(` šŸ’” High ID selector usage: ${idSelectors.length}`); this.optimizations.push({ file: fileName, optimization: 'Consider using classes instead of IDs for styling', benefit: 'Better reusability and lower specificity' }); } return { selectors: selectors.length, rules: rules.length, avgComplexity, complexSelectors, universalSelectors, deepSelectors }; } calculateSelectorComplexity(selector) { let complexity = 0; // Base complexity complexity += 1; // Add for each combinator complexity += (selector.match(/[>+~]/g) || []).length; // Add for each pseudo-class/element complexity += (selector.match(/:[^:]/g) || []).length; // Add for each attribute selector complexity += (selector.match(/\[[^\]]*\]/g) || []).length; // Add for each class complexity += (selector.match(/\.[^.\s#[]+/g) || []).length; // Add for each ID (heavily weighted) complexity += (selector.match(/#[^.\s#[]+/g) || []).length * 2; // Add for universal selector if (selector.includes('*')) complexity += 1; return complexity; } analyzeMediaQueries(content, fileName) { const mediaQueries = content.match(/@media[^{]*{[^{}]*({[^}]*}[^{}]*)*}/g) || []; if (mediaQueries.length > 0) { console.log(` šŸ“± Media queries: ${mediaQueries.length}`); // Check for redundant media queries const breakpoints = new Set(); mediaQueries.forEach(mq => { const condition = mq.match(/@media[^{]*/)[0]; breakpoints.add(condition); }); if (breakpoints.size < mediaQueries.length) { this.optimizations.push({ file: fileName, optimization: 'Consolidate duplicate media queries', benefit: 'Reduced file size and better organization', details: `${mediaQueries.length} queries, ${breakpoints.size} unique breakpoints` }); } } } analyzeAnimationPerformance(content, fileName) { const animations = content.match(/@keyframes[^{]*{[^{}]*({[^}]*}[^{}]*)*}/g) || []; const transforms = content.match(/transform\s*:[^;]*/g) || []; const transitions = content.match(/transition\s*:[^;]*/g) || []; if (animations.length > 0) { console.log(` šŸŽ¬ Animations: ${animations.length}`); // Check for performance-friendly properties const expensiveProperties = []; animations.forEach(animation => { const expensiveProps = [ 'width', 'height', 'top', 'left', 'right', 'bottom', 'margin', 'padding', 'border', 'box-shadow' ]; expensiveProps.forEach(prop => { if (animation.includes(prop + ':')) { expensiveProperties.push(prop); } }); }); if (expensiveProperties.length > 0) { this.performanceIssues.push({ file: fileName, issue: 'Animations using expensive properties', impact: 'Can cause layout thrashing and poor performance', severity: 'medium', recommendation: 'Use transform and opacity for animations', properties: [...new Set(expensiveProperties)] }); } } if (transforms.length > 10) { console.log(` āœ… Good use of transforms: ${transforms.length}`); this.optimizations.push({ file: fileName, optimization: 'Good use of GPU-accelerated transforms', benefit: 'Smooth animations with hardware acceleration' }); } } analyzeSpecificity(content, fileName) { const selectors = content.match(/[^{}]+(?=\s*{)/g) || []; let highSpecificity = 0; let averageSpecificity = 0; let specificityWarnings = []; selectors.forEach(selector => { const specificity = this.calculateSpecificity(selector.trim()); averageSpecificity += specificity.total; if (specificity.total > 100) { highSpecificity++; if (specificityWarnings.length < 3) { specificityWarnings.push({ selector: selector.trim(), specificity: specificity.total }); } } }); if (selectors.length > 0) { averageSpecificity = Math.round(averageSpecificity / selectors.length); console.log(` šŸŽÆ Average specificity: ${averageSpecificity}`); } if (highSpecificity > 0) { console.log(` āš ļø High specificity selectors: ${highSpecificity}`); this.performanceIssues.push({ file: fileName, issue: 'High specificity selectors', impact: 'Harder to override, maintenance issues', severity: 'low', count: highSpecificity, examples: specificityWarnings }); } } calculateSpecificity(selector) { const ids = (selector.match(/#[^.\s#[]+/g) || []).length; const classes = (selector.match(/\.[^.\s#[]+/g) || []).length; const attributes = (selector.match(/\[[^\]]*\]/g) || []).length; const pseudoClasses = (selector.match(/:[^:]/g) || []).length; const elements = (selector.match(/^[a-zA-Z]+|[^.\s#[:]+/g) || []).length; return { ids, classes: classes + attributes + pseudoClasses, elements, total: (ids * 100) + ((classes + attributes + pseudoClasses) * 10) + elements }; } analyzeCodeEfficiency(content, fileName) { // Check for duplicate rules (simple heuristic) const rules = content.match(/{[^}]*}/g) || []; const ruleContents = rules.map(rule => rule.replace(/\s+/g, ' ').trim()); const uniqueRules = new Set(ruleContents); if (rules.length !== uniqueRules.size) { const duplicates = rules.length - uniqueRules.size; console.log(` āš ļø Potential duplicate rules: ${duplicates}`); this.optimizations.push({ file: fileName, optimization: 'Remove duplicate CSS rules', benefit: 'Smaller file size and better maintainability', details: `${duplicates} potential duplicates found` }); } // Check for shorthand opportunities const longhandProperties = content.match(/margin-top|margin-right|margin-bottom|margin-left|padding-top|padding-right|padding-bottom|padding-left/g) || []; if (longhandProperties.length > 10) { this.optimizations.push({ file: fileName, optimization: 'Use CSS shorthand properties where possible', benefit: 'Reduced file size', count: longhandProperties.length }); } } analyzeCriticalPath(content, fileName) { // Check for render-blocking patterns const imports = content.match(/@import[^;]*;/g) || []; if (imports.length > 0) { console.log(` āš ļø CSS imports: ${imports.length}`); this.performanceIssues.push({ file: fileName, issue: 'CSS @import statements', impact: 'Blocks parallel loading of stylesheets', severity: 'high', recommendation: 'Use HTML link tags instead', count: imports.length }); } // Check for web fonts const fontFaces = content.match(/@font-face\s*{[^}]*}/g) || []; if (fontFaces.length > 0) { console.log(` šŸ”¤ Font faces: ${fontFaces.length}`); // Check for font-display property const fontDisplays = content.match(/font-display\s*:[^;]*/g) || []; if (fontDisplays.length < fontFaces.length) { this.optimizations.push({ file: fileName, optimization: 'Add font-display: swap to @font-face rules', benefit: 'Prevent invisible text during font load', missing: fontFaces.length - fontDisplays.length }); } } } generatePerformanceReport(totalSize, totalRules, totalSelectors) { console.log('\n' + '='.repeat(80)); console.log('⚔ CSS PERFORMANCE REPORT'); console.log('='.repeat(80)); // Overall metrics console.log('\nšŸ“Š OVERALL METRICS:'); console.log(` Total CSS size: ${Math.round(totalSize * 100) / 100}KB`); console.log(` Total rules: ${totalRules}`); console.log(` Total selectors: ${totalSelectors}`); console.log(` Average rules per file: ${Math.round(totalRules / this.metrics.size)}`); // File size breakdown console.log('\nšŸ“ FILE SIZE BREAKDOWN:'); const sortedFiles = Array.from(this.metrics.entries()) .sort(([,a], [,b]) => b.size - a.size); sortedFiles.forEach(([file, metrics]) => { const percentage = Math.round((metrics.size / totalSize) * 100); console.log(` ${file}: ${metrics.size}KB (${percentage}%) - ${metrics.rules} rules, ${metrics.selectors} selectors`); }); // Performance issues if (this.performanceIssues.length > 0) { console.log('\n🚨 PERFORMANCE ISSUES:'); const highSeverity = this.performanceIssues.filter(i => i.severity === 'high'); const mediumSeverity = this.performanceIssues.filter(i => i.severity === 'medium'); const lowSeverity = this.performanceIssues.filter(i => i.severity === 'low'); if (highSeverity.length > 0) { console.log('\n šŸ”“ HIGH SEVERITY:'); highSeverity.forEach((issue, i) => { console.log(` ${i + 1}. ${issue.file}: ${issue.issue}`); console.log(` Impact: ${issue.impact}`); if (issue.recommendation) console.log(` Fix: ${issue.recommendation}`); if (issue.count) console.log(` Count: ${issue.count}`); }); } if (mediumSeverity.length > 0) { console.log('\n 🟔 MEDIUM SEVERITY:'); mediumSeverity.forEach((issue, i) => { console.log(` ${i + 1}. ${issue.file}: ${issue.issue}`); console.log(` Impact: ${issue.impact}`); if (issue.recommendation) console.log(` Fix: ${issue.recommendation}`); if (issue.count) console.log(` Count: ${issue.count}`); }); } if (lowSeverity.length > 0) { console.log('\n 🟢 LOW SEVERITY:'); lowSeverity.forEach((issue, i) => { console.log(` ${i + 1}. ${issue.file}: ${issue.issue}`); console.log(` Impact: ${issue.impact}`); if (issue.count) console.log(` Count: ${issue.count}`); }); } } // Optimization opportunities if (this.optimizations.length > 0) { console.log('\nšŸ’” OPTIMIZATION OPPORTUNITIES:'); this.optimizations.forEach((opt, i) => { console.log(`\n ${i + 1}. ${opt.file}: ${opt.optimization}`); console.log(` Benefit: ${opt.benefit}`); if (opt.details) console.log(` Details: ${opt.details}`); if (opt.count) console.log(` Instances: ${opt.count}`); }); } // Performance recommendations console.log('\nšŸŽÆ PERFORMANCE RECOMMENDATIONS:'); console.log(' 1. Minify CSS files for production'); console.log(' 2. Use CSS compression (gzip/brotli)'); console.log(' 3. Eliminate unused CSS with tools like PurgeCSS'); console.log(' 4. Split critical CSS for above-the-fold content'); console.log(' 5. Use CSS containment for better isolation'); console.log(' 6. Optimize selectors for better matching performance'); console.log(' 7. Use will-change property sparingly for animations'); console.log(' 8. Consider CSS-in-JS for component-scoped styles'); console.log(' 9. Use CSS custom properties efficiently'); console.log(' 10. Regular performance audits with dev tools'); // Performance score const highIssues = this.performanceIssues.filter(i => i.severity === 'high').length; const mediumIssues = this.performanceIssues.filter(i => i.severity === 'medium').length; const lowIssues = this.performanceIssues.filter(i => i.severity === 'low').length; const performanceScore = Math.max(0, 100 - (highIssues * 25) - (mediumIssues * 15) - (lowIssues * 5)); console.log('\nšŸ“ˆ PERFORMANCE SUMMARY:'); console.log(` High Priority Issues: ${highIssues}`); console.log(` Medium Priority Issues: ${mediumIssues}`); console.log(` Low Priority Issues: ${lowIssues}`); console.log(` Optimization Opportunities: ${this.optimizations.length}`); console.log(` Performance Score: ${performanceScore}/100`); if (performanceScore >= 90) { console.log(' 🟢 Excellent CSS performance'); } else if (performanceScore >= 70) { console.log(' 🟔 Good CSS performance with room for improvement'); } else if (performanceScore >= 50) { console.log(' 🟠 Moderate CSS performance - optimization needed'); } else { console.log(' šŸ”“ Poor CSS performance - immediate attention required'); } // File size recommendations if (totalSize > 100) { console.log('\nšŸ’¾ FILE SIZE RECOMMENDATIONS:'); console.log(' • Consider code splitting for large CSS files'); console.log(' • Use critical CSS extraction'); console.log(' • Implement lazy loading for non-critical styles'); } } } const analyzer = new CSSPerformanceAnalyzer(); analyzer.analyzePerformance();