Rustelo/scripts/build/build-css-bundles.js

200 lines
6.8 KiB
JavaScript
Raw Normal View History

2026-02-08 20:18:46 +00:00
#!/usr/bin/env node
/**
* CSS Bundle Builder
2026-02-08 20:37:49 +00:00
*
2026-02-08 20:18:46 +00:00
* Combines and minifies CSS files into optimized bundles:
* - site.min.css: Essential site styles (design system, theme, layout)
* - app.min.css: Main application styles (UnoCSS, components)
* - enhancements.min.css: Progressive enhancement styles (highlighting, etc.)
2026-02-08 20:37:49 +00:00
*
2026-02-08 20:18:46 +00:00
* Usage:
* node scripts/build-css-bundles.js [theme]
2026-02-08 20:37:49 +00:00
*
2026-02-08 20:18:46 +00:00
* Examples:
* node scripts/build-css-bundles.js # default theme
* node scripts/build-css-bundles.js purple # purple theme
*/
const fs = require('fs');
const path = require('path');
// Simple CSS minifier
function minifyCss(css) {
return css
// Remove comments
.replace(/\/\*[\s\S]*?\*\//g, '')
// Remove extra whitespace
.replace(/\s+/g, ' ')
// Remove whitespace around specific characters
.replace(/\s*([{}:;,>+~])\s*/g, '$1')
// Remove trailing semicolons before }
.replace(/;}/g, '}')
// Remove leading/trailing whitespace
.trim();
}
// Extract critical above-the-fold styles from website.css
function extractSiteStyles(websiteCss) {
// Extract CSS reset, root variables, and essential layout styles
// This is a simplified extraction - in a real scenario you might use a more sophisticated approach
const sitePatterns = [
// CSS reset and variables
/\/\* layer: preflights \*\/[\s\S]*?(?=\/\* layer:|$)/g,
// Root variables
/:root\s*\{[^}]*\}/g,
// Essential layout classes (simplified extraction)
/\.(?:min-h-screen|max-w-|mx-auto|py-|flex|flex-col|flex-grow)[^{]*\{[^}]*\}/g
];
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
let extracted = '';
sitePatterns.forEach(pattern => {
const matches = websiteCss.match(pattern);
if (matches) {
extracted += matches.join('\n') + '\n';
}
});
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
return extracted;
}
async function buildCssBundles() {
try {
const assetsStylesDir = path.join(__dirname, '../assets/styles');
const publicStylesDir = path.join(__dirname, '../public/styles');
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Get theme from command line argument or default
const theme = process.argv[2] || 'default';
const themeFile = `theme-${theme}.css`;
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
console.log(`🎨 Building CSS bundles with theme: ${theme}`);
2026-02-08 20:37:49 +00:00
// Read source files from assets
2026-02-08 20:18:46 +00:00
const files = {
designSystem: path.join(assetsStylesDir, 'design-system.css'),
theme: path.join(assetsStylesDir, themeFile),
website: path.join(assetsStylesDir, 'website.css'),
contactOverrides: path.join(assetsStylesDir, 'overrides/contact-tailwind-overrides.css'),
custom: path.join(assetsStylesDir, 'custom.css'),
highlight: path.join(publicStylesDir, 'highlight-github-dark.min.css')
};
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Check if all required files exist
const missingFiles = [];
for (const [name, filePath] of Object.entries(files)) {
if (!fs.existsSync(filePath)) {
missingFiles.push(`${name}: ${filePath}`);
}
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
if (missingFiles.length > 0) {
console.log('⚠️ Some files are missing but continuing with available files:');
missingFiles.forEach(file => console.log(` ${file}`));
console.log('');
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Read file contents
const contents = {};
for (const [name, filePath] of Object.entries(files)) {
if (fs.existsSync(filePath)) {
contents[name] = fs.readFileSync(filePath, 'utf8');
} else {
contents[name] = '';
}
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
console.log('📂 Source files read:');
for (const [name, content] of Object.entries(contents)) {
const size = Math.round(content.length / 1024);
console.log(` ${name}: ${size}KB`);
}
console.log('');
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// 1. Build site.min.css (essential styles)
const siteExtracted = extractSiteStyles(contents.website);
const siteBundle = [
'/* Site Bundle - Essential Styles */',
`/* Generated on ${new Date().toISOString()} */`,
'/* Theme: ' + theme + ' */',
'',
'/* Design System Variables */',
contents.designSystem,
'',
'/* Theme Variables */',
contents.theme,
'',
'/* Essential Layout Styles */',
siteExtracted
].join('\n');
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
const siteMinified = minifyCss(siteBundle);
const sitePath = path.join(assetsStylesDir, 'site.min.css');
fs.writeFileSync(sitePath, siteMinified);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// 2. Build app.min.css (main application styles)
const appBundle = [
'/* App Bundle - Main Application Styles */',
`/* Generated on ${new Date().toISOString()} */`,
'',
'/* Main Website Styles (minus site essentials) */',
contents.website.replace(siteExtracted, ''), // Remove extracted site styles
'',
'/* Custom Styles */',
contents.custom,
'',
'/* Contact Page Overrides */',
contents.contactOverrides
].join('\n');
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
const appMinified = minifyCss(appBundle);
const appPath = path.join(assetsStylesDir, 'app.min.css');
fs.writeFileSync(appPath, appMinified);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// 3. Build enhancements.min.css (progressive features)
const enhancementsBundle = [
'/* Enhancements Bundle - Progressive Features */',
`/* Generated on ${new Date().toISOString()} */`,
'',
'/* Code Highlighting Styles */',
contents.highlight
].join('\n');
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
const enhancementsMinified = minifyCss(enhancementsBundle);
const enhancementsPath = path.join(assetsStylesDir, 'enhancements.min.css');
fs.writeFileSync(enhancementsPath, enhancementsMinified);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Get final file sizes
const finalSizes = {
site: Math.round(fs.statSync(sitePath).size / 1024),
app: Math.round(fs.statSync(appPath).size / 1024),
enhancements: Math.round(fs.statSync(enhancementsPath).size / 1024)
};
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
const totalSize = finalSizes.site + finalSizes.app + finalSizes.enhancements;
const originalTotal = Math.round(Object.values(contents).reduce((sum, content) => sum + content.length, 0) / 1024);
const savings = Math.round(((originalTotal - totalSize) / originalTotal) * 100);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
console.log('✅ CSS bundles created successfully!');
console.log('');
console.log('📊 Bundle Sizes:');
console.log(` 📁 site.min.css: ${finalSizes.site}KB (design system + theme + essential layout)`);
console.log(` 📁 app.min.css: ${finalSizes.app}KB (main application styles)`);
console.log(` 📁 enhancements.min.css: ${finalSizes.enhancements}KB (code highlighting + progressive features)`);
console.log('');
console.log(`📈 Total: ${totalSize}KB (${savings}% size reduction from ${originalTotal}KB)`);
console.log('');
console.log('🚀 CSS bundles generated in assets/styles/');
console.log(' 📁 assets/styles/site.min.css');
2026-02-08 20:37:49 +00:00
console.log(' 📁 assets/styles/app.min.css');
2026-02-08 20:18:46 +00:00
console.log(' 📁 assets/styles/enhancements.min.css');
console.log('');
console.log('💡 Run copy script to deploy to public/styles/');
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
} catch (error) {
console.error('❌ Error building CSS bundles:', error.message);
process.exit(1);
}
}
2026-02-08 20:37:49 +00:00
buildCssBundles();