#!/usr/bin/env node /** * CSS Bundle Builder * * 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.) * * Usage: * node scripts/build-css-bundles.js [theme] * * 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 ]; let extracted = ''; sitePatterns.forEach(pattern => { const matches = websiteCss.match(pattern); if (matches) { extracted += matches.join('\n') + '\n'; } }); return extracted; } async function buildCssBundles() { try { const assetsStylesDir = path.join(__dirname, '../assets/styles'); const publicStylesDir = path.join(__dirname, '../public/styles'); // Get theme from command line argument or default const theme = process.argv[2] || 'default'; const themeFile = `theme-${theme}.css`; console.log(`🎨 Building CSS bundles with theme: ${theme}`); // Read source files from assets 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') }; // Check if all required files exist const missingFiles = []; for (const [name, filePath] of Object.entries(files)) { if (!fs.existsSync(filePath)) { missingFiles.push(`${name}: ${filePath}`); } } if (missingFiles.length > 0) { console.log('⚠️ Some files are missing but continuing with available files:'); missingFiles.forEach(file => console.log(` ${file}`)); console.log(''); } // 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] = ''; } } 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(''); // 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'); const siteMinified = minifyCss(siteBundle); const sitePath = path.join(assetsStylesDir, 'site.min.css'); fs.writeFileSync(sitePath, siteMinified); // 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'); const appMinified = minifyCss(appBundle); const appPath = path.join(assetsStylesDir, 'app.min.css'); fs.writeFileSync(appPath, appMinified); // 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'); const enhancementsMinified = minifyCss(enhancementsBundle); const enhancementsPath = path.join(assetsStylesDir, 'enhancements.min.css'); fs.writeFileSync(enhancementsPath, enhancementsMinified); // 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) }; 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); 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'); console.log(' 📁 assets/styles/app.min.css'); console.log(' 📁 assets/styles/enhancements.min.css'); console.log(''); console.log('💡 Run copy script to deploy to public/styles/'); } catch (error) { console.error('❌ Error building CSS bundles:', error.message); process.exit(1); } } buildCssBundles();