import { PurgeCSS } from 'purgecss'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Create reports directory if it doesn't exist const reportsDir = './reports'; if (!fs.existsSync(reportsDir)) { fs.mkdirSync(reportsDir); } console.log('šŸ” Starting CSS Analysis...\n'); // 1. Run PurgeCSS to find unused CSS console.log('šŸ“Š Running PurgeCSS analysis...'); async function runPurgeCSS() { const purgeCSSResults = await new PurgeCSS().purge({ content: [ './src/v2/**/*.ts', './wwwroot/v2.html' ], css: [ './wwwroot/css/v2/*.css' ], rejected: true, rejectedCss: true, safelist: { standard: [ /^swp-/, /^cols-[1-4]$/, /^stack-level-[0-4]$/, 'dragging', 'hover', 'highlight', 'transitioning', 'filter-active', 'swp--resizing', 'max-event-indicator', 'max-event-overflow-hide', 'max-event-overflow-show', 'allday-chevron', 'collapsed', 'expanded', /^month-/, /^week-/, 'today', 'weekend', 'other-month', 'hidden', 'invisible', 'transparent', 'calendar-wrapper' ] } }); // Calculate statistics let totalOriginalSize = 0; let totalPurgedSize = 0; let totalRejected = 0; const rejectedByFile = {}; purgeCSSResults.forEach(result => { const fileName = path.basename(result.file); const originalSize = result.css.length + (result.rejected ? result.rejected.join('').length : 0); const purgedSize = result.css.length; const rejectedSize = result.rejected ? result.rejected.length : 0; totalOriginalSize += originalSize; totalPurgedSize += purgedSize; totalRejected += rejectedSize; rejectedByFile[fileName] = { originalSize, purgedSize, rejectedCount: rejectedSize, rejected: result.rejected || [] }; }); const report = { summary: { totalFiles: purgeCSSResults.length, totalOriginalSize, totalPurgedSize, totalRejected, percentageRemoved: ((totalRejected / (totalOriginalSize || 1)) * 100).toFixed(2) + '%', potentialSavings: totalOriginalSize - totalPurgedSize }, fileDetails: rejectedByFile }; fs.writeFileSync( path.join(reportsDir, 'purgecss-report.json'), JSON.stringify(report, null, 2) ); console.log('āœ… PurgeCSS analysis complete'); console.log(` - Total CSS rules analyzed: ${totalOriginalSize}`); console.log(` - Unused CSS rules found: ${totalRejected}`); console.log(` - Potential removal: ${report.summary.percentageRemoved}`); return report; } // 2. Analyze CSS with basic stats console.log('\nšŸ“Š Running CSS Stats analysis...'); function runCSSStats() { const cssFiles = [ './wwwroot/css/v2/calendar-v2.css', './wwwroot/css/v2/calendar-v2-base.css', './wwwroot/css/v2/calendar-v2-layout.css', './wwwroot/css/v2/calendar-v2-events.css' ]; const stats = {}; cssFiles.forEach(file => { if (fs.existsSync(file)) { const fileName = path.basename(file); const content = fs.readFileSync(file, 'utf8'); // Basic statistics const lines = content.split('\n').length; const size = Buffer.byteLength(content, 'utf8'); const rules = (content.match(/\{[^}]*\}/g) || []).length; const selectors = (content.match(/[^{]+(?=\{)/g) || []).length; const properties = (content.match(/[^:]+:[^;]+;/g) || []).length; const colors = [...new Set(content.match(/#[0-9a-fA-F]{3,6}|rgba?\([^)]+\)|hsla?\([^)]+\)/g) || [])]; const mediaQueries = (content.match(/@media[^{]+/g) || []).length; stats[fileName] = { lines, size: `${(size / 1024).toFixed(2)} KB`, sizeBytes: size, rules, selectors, properties, uniqueColors: colors.length, colors: colors.slice(0, 10), // First 10 colors mediaQueries }; } }); fs.writeFileSync( path.join(reportsDir, 'css-stats.json'), JSON.stringify(stats, null, 2) ); console.log('āœ… CSS Stats analysis complete'); console.log(` - Files analyzed: ${Object.keys(stats).length}`); return stats; } // 3. Generate HTML report function generateHTMLReport(purgeReport, statsReport) { const totalSize = Object.values(statsReport).reduce((sum, stat) => sum + stat.sizeBytes, 0); const totalSizeKB = (totalSize / 1024).toFixed(2); const html = ` CSS Analysis Report - Calendar Plantempus

šŸ“Š CSS Analysis Report

Calendar Plantempus - Production CSS Analysis

Total CSS Size
${totalSizeKB} KB
CSS Files
${purgeReport.summary.totalFiles}
Unused CSS Rules
${purgeReport.summary.totalRejected}
Potential Removal
${purgeReport.summary.percentageRemoved}

šŸ“ˆ CSS Statistics by File

${Object.entries(statsReport).map(([file, stats]) => ` `).join('')}
File Size Lines Rules Selectors Properties Colors
${file} ${stats.size} ${stats.lines} ${stats.rules} ${stats.selectors} ${stats.properties} ${stats.uniqueColors}

šŸ—‘ļø Unused CSS by File

${Object.entries(purgeReport.fileDetails).map(([file, details]) => `

${file}

${details.rejectedCount} unused rules Original: ${details.originalSize} | After purge: ${details.purgedSize}

${details.rejectedCount > 0 ? `
Show unused selectors
${details.rejected.slice(0, 50).join('
')} ${details.rejected.length > 50 ? `
... and ${details.rejected.length - 50} more` : ''}
` : '

āœ… No unused CSS found!

'}
`).join('')}

šŸ’” Recommendations

Report generated: ${new Date().toLocaleString('da-DK')}

`; fs.writeFileSync(path.join(reportsDir, 'css-analysis-report.html'), html); console.log('\nāœ… HTML report generated: reports/css-analysis-report.html'); } // Run all analyses (async () => { try { const purgeReport = await runPurgeCSS(); const statsReport = runCSSStats(); generateHTMLReport(purgeReport, statsReport); console.log('\nšŸŽ‰ CSS Analysis Complete!'); console.log('šŸ“„ Reports generated in ./reports/ directory'); console.log(' - purgecss-report.json (detailed unused CSS data)'); console.log(' - css-stats.json (CSS statistics)'); console.log(' - css-analysis-report.html (visual report)'); console.log('\nšŸ’” Open reports/css-analysis-report.html in your browser to view the full report'); } catch (error) { console.error('āŒ Error during analysis:', error); process.exit(1); } })();