Files
reactShop/prerender/utils.cjs
2025-07-02 12:49:06 +02:00

130 lines
3.9 KiB
JavaScript

const fs = require('fs');
const path = require('path');
// Helper function to collect all categories from the tree
const collectAllCategories = (categoryNode, categories = []) => {
if (!categoryNode) return categories;
// Add current category (skip root category 209)
if (categoryNode.id !== 209) {
categories.push({
id: categoryNode.id,
name: categoryNode.name,
seoName: categoryNode.seoName,
parentId: categoryNode.parentId
});
}
// Recursively add children
if (categoryNode.children) {
for (const child of categoryNode.children) {
collectAllCategories(child, categories);
}
}
return categories;
};
// Advanced CSS minification and optimization
const optimizeCss = (cssContent) => {
if (!cssContent || typeof cssContent !== 'string') {
return '';
}
try {
let optimized = cssContent
// Remove comments (/* ... */)
.replace(/\/\*[\s\S]*?\*\//g, '')
// Remove unnecessary whitespace but preserve structure
.replace(/\s*{\s*/g, '{')
.replace(/;\s*}/g, '}')
.replace(/}\s*/g, '}')
.replace(/,\s*/g, ',')
.replace(/:\s*/g, ':')
.replace(/;\s*/g, ';')
// Remove empty rules
.replace(/[^}]*\{\s*\}/g, '')
// Normalize multiple spaces/tabs/newlines
.replace(/\s+/g, ' ')
// Remove leading/trailing whitespace
.trim();
// Remove redundant semicolons before closing braces
optimized = optimized.replace(/;+}/g, '}');
// Remove empty media queries
optimized = optimized.replace(/@media[^{]*\{\s*\}/g, '');
return optimized;
} catch (error) {
console.warn(`⚠️ CSS optimization failed: ${error.message}`);
return cssContent; // Return original if optimization fails
}
};
// Extract critical CSS selectors (basic implementation)
const extractCriticalCss = (cssContent, criticalSelectors = []) => {
if (!cssContent || !criticalSelectors.length) {
return { critical: '', nonCritical: cssContent };
}
try {
const rules = cssContent.match(/[^{}]+\{[^{}]*\}/g) || [];
let critical = '';
let nonCritical = '';
rules.forEach(rule => {
const selector = rule.split('{')[0].trim();
const isCritical = criticalSelectors.some(criticalSel => {
return selector.includes(criticalSel) ||
selector.includes('body') ||
selector.includes('html') ||
selector.includes(':root') ||
selector.includes('@font-face') ||
selector.includes('@import');
});
if (isCritical) {
critical += rule;
} else {
nonCritical += rule;
}
});
return {
critical: optimizeCss(critical),
nonCritical: optimizeCss(nonCritical)
};
} catch (error) {
console.warn(`⚠️ Critical CSS extraction failed: ${error.message}`);
return { critical: cssContent, nonCritical: '' };
}
};
const writeCombinedCssFile = (globalCssCollection, outputDir) => {
const combinedCss = Array.from(globalCssCollection).join('\n');
// Optimize the combined CSS
const optimizedCss = optimizeCss(combinedCss);
const cssFilePath = path.resolve(__dirname, '..', outputDir, 'prerender.css');
fs.writeFileSync(cssFilePath, optimizedCss);
const originalSize = combinedCss.length;
const optimizedSize = optimizedCss.length;
const savings = originalSize - optimizedSize;
const savingsPercent = originalSize > 0 ? Math.round((savings / originalSize) * 100) : 0;
console.log(`✅ Combined CSS file written to ${cssFilePath}`);
console.log(` - Total CSS rules: ${globalCssCollection.size}`);
console.log(` - Original size: ${Math.round(originalSize / 1024)}KB`);
console.log(` - Optimized size: ${Math.round(optimizedSize / 1024)}KB`);
console.log(` - Space saved: ${Math.round(savings / 1024)}KB (${savingsPercent}%)`);
};
module.exports = {
collectAllCategories,
writeCombinedCssFile,
optimizeCss,
extractCriticalCss
};