upskill-event-manager/css-performance-analysis.js
bengizmo 993a820a84 feat: Add comprehensive development artifacts to repository
- Add 26 documentation files including test reports, deployment guides, and troubleshooting documentation
- Include 3 CSV data files for trainer imports and user registration tracking
- Add 43 JavaScript test files covering mobile optimization, Safari compatibility, and E2E testing
- Include 18 PHP utility files for debugging, geocoding, and data analysis
- Add 12 shell scripts for deployment verification, user management, and database operations
- Update .gitignore with whitelist patterns for development files, documentation, and CSV data

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 12:26:11 -03:00

525 lines
No EOL
20 KiB
JavaScript

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();