mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-12-06 17:27:57 +00:00
Implement CSS optimization pipeline with PurgeCSS, Autoprefixer, and cssnano; reduce bundle size by 42% (uncompressed) and 39% (gzipped). Add analysis tool for CSS metrics and update build scripts in package.json. Create comprehensive documentation for CSS optimization process.
This commit is contained in:
166
scripts/analyze-css.js
Normal file
166
scripts/analyze-css.js
Normal file
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* CSS Analysis Script
|
||||
*
|
||||
* Analyzes the compiled CSS bundle to identify:
|
||||
* - Total size and line count
|
||||
* - Bootstrap vs custom CSS breakdown
|
||||
* - Most common selectors and patterns
|
||||
* - Potential optimization opportunities
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const CSS_FILE = path.join(__dirname, '../static/css/devportal2024-v1.css');
|
||||
|
||||
function analyzeCSS() {
|
||||
console.log('🔍 Analyzing CSS Bundle...\n');
|
||||
|
||||
if (!fs.existsSync(CSS_FILE)) {
|
||||
console.error(`❌ CSS file not found: ${CSS_FILE}`);
|
||||
console.log('💡 Run "npm run build-css" first to generate the CSS bundle.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const css = fs.readFileSync(CSS_FILE, 'utf-8');
|
||||
const stats = fs.statSync(CSS_FILE);
|
||||
|
||||
// Basic stats
|
||||
const lines = css.split('\n').length;
|
||||
const sizeKB = (stats.size / 1024).toFixed(2);
|
||||
const selectors = css.match(/[^{}]+(?=\{)/g) || [];
|
||||
const uniqueSelectors = new Set(selectors.map(s => s.trim())).size;
|
||||
|
||||
console.log('📊 Bundle Statistics:');
|
||||
console.log('━'.repeat(50));
|
||||
console.log(` Size: ${sizeKB} KB`);
|
||||
console.log(` Lines: ${lines.toLocaleString()}`);
|
||||
console.log(` Total Selectors: ${selectors.length.toLocaleString()}`);
|
||||
console.log(` Unique Selectors: ${uniqueSelectors.toLocaleString()}`);
|
||||
console.log('');
|
||||
|
||||
// Bootstrap component detection
|
||||
const bootstrapPatterns = {
|
||||
'Grid System': /\.(container|row|col-)/g,
|
||||
'Buttons': /\.btn-/g,
|
||||
'Forms': /\.(form-control|form-select|form-check)/g,
|
||||
'Cards': /\.card-/g,
|
||||
'Navbar': /\.navbar-/g,
|
||||
'Dropdown': /\.dropdown-/g,
|
||||
'Modal': /\.modal-/g,
|
||||
'Alert': /\.alert-/g,
|
||||
'Badge': /\.badge-/g,
|
||||
'Breadcrumb': /\.breadcrumb/g,
|
||||
'Pagination': /\.page-/g,
|
||||
'Accordion': /\.accordion/g,
|
||||
'Carousel': /\.carousel/g,
|
||||
'Tooltip': /\.tooltip/g,
|
||||
'Popover': /\.popover/g,
|
||||
'Toast': /\.toast/g,
|
||||
'Spinner': /\.spinner-/g,
|
||||
};
|
||||
|
||||
console.log('🎨 Bootstrap Component Usage:');
|
||||
console.log('━'.repeat(50));
|
||||
|
||||
const componentUsage = [];
|
||||
for (const [component, pattern] of Object.entries(bootstrapPatterns)) {
|
||||
const matches = css.match(pattern);
|
||||
const count = matches ? matches.length : 0;
|
||||
componentUsage.push({ component, count });
|
||||
}
|
||||
|
||||
componentUsage.sort((a, b) => b.count - a.count);
|
||||
componentUsage.forEach(({ component, count }) => {
|
||||
const bar = '█'.repeat(Math.min(Math.floor(count / 10), 40));
|
||||
console.log(` ${component.padEnd(20)} ${count.toString().padStart(4)} ${bar}`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Custom classes analysis
|
||||
const customPatterns = [
|
||||
{ name: 'Dev Tools', pattern: /\.(rpc-tool|websocket|code-tab)/g },
|
||||
{ name: 'Navigation', pattern: /\.(top-nav|side-nav|breadcrumb)/g },
|
||||
{ name: 'Content', pattern: /\.(content-|landing-|page-)/g },
|
||||
{ name: 'Cards', pattern: /\.(card-deck|project-card)/g },
|
||||
{ name: 'Video', pattern: /\.video-/g },
|
||||
{ name: 'Blog', pattern: /\.blog-/g },
|
||||
];
|
||||
|
||||
console.log('🎯 Custom Component Patterns:');
|
||||
console.log('━'.repeat(50));
|
||||
customPatterns.forEach(({ name, pattern }) => {
|
||||
const matches = css.match(pattern);
|
||||
const count = matches ? matches.length : 0;
|
||||
if (count > 0) {
|
||||
console.log(` ${name.padEnd(20)} ${count.toString().padStart(4)}`);
|
||||
}
|
||||
});
|
||||
console.log('');
|
||||
|
||||
// Optimization recommendations
|
||||
console.log('💡 Optimization Recommendations:');
|
||||
console.log('━'.repeat(50));
|
||||
|
||||
const recommendations = [];
|
||||
|
||||
// Check for unused Bootstrap components
|
||||
const lowUsageComponents = componentUsage.filter(c => c.count < 5 && c.count > 0);
|
||||
if (lowUsageComponents.length > 0) {
|
||||
recommendations.push({
|
||||
priority: 'HIGH',
|
||||
message: `${lowUsageComponents.length} Bootstrap components with <5 usages detected`,
|
||||
action: 'Consider manually importing only needed Bootstrap modules'
|
||||
});
|
||||
}
|
||||
|
||||
const noUsageComponents = componentUsage.filter(c => c.count === 0);
|
||||
if (noUsageComponents.length > 0) {
|
||||
recommendations.push({
|
||||
priority: 'HIGH',
|
||||
message: `${noUsageComponents.length} Bootstrap components with 0 usages detected`,
|
||||
action: 'Remove unused components from Bootstrap import'
|
||||
});
|
||||
}
|
||||
|
||||
if (sizeKB > 200) {
|
||||
recommendations.push({
|
||||
priority: 'CRITICAL',
|
||||
message: 'Bundle size exceeds 200KB',
|
||||
action: 'Implement PurgeCSS to remove unused styles'
|
||||
});
|
||||
}
|
||||
|
||||
recommendations.push({
|
||||
priority: 'MEDIUM',
|
||||
message: 'No code splitting detected',
|
||||
action: 'Consider splitting vendor CSS from custom styles'
|
||||
});
|
||||
|
||||
recommendations.forEach(({ priority, message, action }) => {
|
||||
const emoji = priority === 'CRITICAL' ? '🔴' : priority === 'HIGH' ? '🟡' : '🔵';
|
||||
console.log(` ${emoji} [${priority}] ${message}`);
|
||||
console.log(` → ${action}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// Estimate potential savings
|
||||
const estimatedReduction = Math.round(parseFloat(sizeKB) * 0.75);
|
||||
const estimatedFinal = Math.round(parseFloat(sizeKB) * 0.25);
|
||||
|
||||
console.log('📈 Estimated Optimization Potential:');
|
||||
console.log('━'.repeat(50));
|
||||
console.log(` Current Size: ${sizeKB} KB`);
|
||||
console.log(` Potential Savings: ~${estimatedReduction} KB (75%)`);
|
||||
console.log(` Expected Size: ~${estimatedFinal} KB`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
analyzeCSS();
|
||||
|
||||
Reference in New Issue
Block a user