🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
308 lines
9.1 KiB
JavaScript
308 lines
9.1 KiB
JavaScript
/**
|
|
* Build Bootstrap Subset Script
|
|
*
|
|
* Genera un subset de Bootstrap con SOLO las clases usadas en el tema.
|
|
*
|
|
* USO:
|
|
* node build-bootstrap-subset.js
|
|
*
|
|
* OUTPUT:
|
|
* Assets/Vendor/Bootstrap/Css/bootstrap-subset.min.css
|
|
*/
|
|
|
|
const { PurgeCSS } = require('purgecss');
|
|
const { globSync } = require('glob');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
async function buildBootstrapSubset() {
|
|
console.log('='.repeat(60));
|
|
console.log('Building Bootstrap Subset for ROI Theme');
|
|
console.log('='.repeat(60));
|
|
|
|
const themeDir = __dirname;
|
|
const inputFile = path.join(themeDir, 'Assets/Vendor/Bootstrap/Css/bootstrap.min.css');
|
|
const outputFile = path.join(themeDir, 'Assets/Vendor/Bootstrap/Css/bootstrap-subset.min.css');
|
|
|
|
// Verificar que existe el archivo de entrada
|
|
if (!fs.existsSync(inputFile)) {
|
|
console.error('ERROR: bootstrap.min.css not found at:', inputFile);
|
|
process.exit(1);
|
|
}
|
|
|
|
const inputSize = fs.statSync(inputFile).size;
|
|
console.log(`Input: bootstrap.min.css (${(inputSize / 1024).toFixed(2)} KB)`);
|
|
|
|
// Encontrar archivos PHP y JS manualmente
|
|
console.log('\nScanning for PHP and JS files...');
|
|
|
|
const patterns = [
|
|
'*.php',
|
|
'Public/**/*.php',
|
|
'Admin/**/*.php',
|
|
'Inc/**/*.php',
|
|
'Shared/**/*.php',
|
|
'template-parts/**/*.php',
|
|
'Assets/js/**/*.js',
|
|
];
|
|
|
|
let contentFiles = [];
|
|
for (const pattern of patterns) {
|
|
const files = globSync(pattern, { cwd: themeDir, absolute: true });
|
|
contentFiles = contentFiles.concat(files);
|
|
}
|
|
|
|
console.log(`Found ${contentFiles.length} files to analyze`);
|
|
|
|
if (contentFiles.length === 0) {
|
|
console.error('ERROR: No content files found. Check glob patterns.');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Mostrar algunos archivos encontrados
|
|
console.log('\nSample files:');
|
|
contentFiles.slice(0, 5).forEach(f => console.log(' -', path.relative(themeDir, f)));
|
|
if (contentFiles.length > 5) {
|
|
console.log(` ... and ${contentFiles.length - 5} more`);
|
|
}
|
|
|
|
try {
|
|
const purgeCSSResult = await new PurgeCSS().purge({
|
|
css: [inputFile],
|
|
content: contentFiles,
|
|
|
|
// Safelist: Clases que SIEMPRE deben incluirse
|
|
safelist: {
|
|
standard: [
|
|
// Estados de navbar scroll (JavaScript)
|
|
'scrolled',
|
|
'navbar-scrolled',
|
|
|
|
// Bootstrap Collapse (JavaScript)
|
|
'show',
|
|
'showing',
|
|
'hiding',
|
|
'collapse',
|
|
'collapsing',
|
|
|
|
// Estados de dropdown
|
|
'dropdown-menu',
|
|
'dropdown-item',
|
|
'dropdown-toggle',
|
|
|
|
// Estados de form
|
|
'is-valid',
|
|
'is-invalid',
|
|
'was-validated',
|
|
|
|
// Visually hidden (accesibilidad)
|
|
'visually-hidden',
|
|
'visually-hidden-focusable',
|
|
|
|
// Screen reader
|
|
'sr-only',
|
|
|
|
// Container
|
|
'container',
|
|
'container-fluid',
|
|
|
|
// Row
|
|
'row',
|
|
|
|
// Display
|
|
'd-flex',
|
|
'd-none',
|
|
'd-block',
|
|
'd-inline-block',
|
|
'd-inline',
|
|
'd-grid',
|
|
|
|
// Common spacing
|
|
'mb-0', 'mb-1', 'mb-2', 'mb-3', 'mb-4', 'mb-5',
|
|
'mt-0', 'mt-1', 'mt-2', 'mt-3', 'mt-4', 'mt-5',
|
|
'me-0', 'me-1', 'me-2', 'me-3', 'me-4', 'me-5',
|
|
'ms-0', 'ms-1', 'ms-2', 'ms-3', 'ms-4', 'ms-5',
|
|
'mx-auto',
|
|
'py-0', 'py-1', 'py-2', 'py-3', 'py-4', 'py-5',
|
|
'px-0', 'px-1', 'px-2', 'px-3', 'px-4', 'px-5',
|
|
'p-0', 'p-1', 'p-2', 'p-3', 'p-4', 'p-5',
|
|
'gap-0', 'gap-1', 'gap-2', 'gap-3', 'gap-4', 'gap-5',
|
|
'g-0', 'g-1', 'g-2', 'g-3', 'g-4', 'g-5',
|
|
|
|
// Flex
|
|
'flex-wrap',
|
|
'flex-nowrap',
|
|
'flex-column',
|
|
'flex-row',
|
|
'justify-content-center',
|
|
'justify-content-between',
|
|
'justify-content-start',
|
|
'justify-content-end',
|
|
'align-items-center',
|
|
'align-items-start',
|
|
'align-items-end',
|
|
|
|
// Text
|
|
'text-center',
|
|
'text-start',
|
|
'text-end',
|
|
'text-white',
|
|
'text-muted',
|
|
'fw-bold',
|
|
'fw-normal',
|
|
'small',
|
|
|
|
// Images
|
|
'img-fluid',
|
|
|
|
// Border/rounded
|
|
'rounded',
|
|
'rounded-circle',
|
|
'border',
|
|
'border-0',
|
|
|
|
// Shadow
|
|
'shadow',
|
|
'shadow-sm',
|
|
'shadow-lg',
|
|
|
|
// Width
|
|
'w-100',
|
|
'w-auto',
|
|
'h-100',
|
|
'h-auto',
|
|
|
|
// Toast classes (plugin IP View Limit)
|
|
'toast-container',
|
|
'toast',
|
|
'toast-body',
|
|
'position-fixed',
|
|
'bottom-0',
|
|
'end-0',
|
|
'start-50',
|
|
'translate-middle-x',
|
|
'text-dark',
|
|
'bg-warning',
|
|
'btn-close',
|
|
'm-auto',
|
|
],
|
|
|
|
deep: [
|
|
// Grid responsive
|
|
/^col-/,
|
|
/^col$/,
|
|
|
|
// Display responsive
|
|
/^d-[a-z]+-/,
|
|
|
|
// Navbar responsive
|
|
/^navbar-expand/,
|
|
/^navbar-/,
|
|
|
|
// Responsive margins/padding
|
|
/^m[tbsexy]?-[a-z]+-/,
|
|
/^p[tbsexy]?-[a-z]+-/,
|
|
|
|
// Text responsive
|
|
/^text-[a-z]+-/,
|
|
|
|
// Flex responsive
|
|
/^flex-[a-z]+-/,
|
|
/^justify-content-[a-z]+-/,
|
|
/^align-items-[a-z]+-/,
|
|
],
|
|
|
|
greedy: [
|
|
// Form controls
|
|
/form-/,
|
|
/input-/,
|
|
|
|
// Buttons
|
|
/btn/,
|
|
|
|
// Cards
|
|
/card/,
|
|
|
|
// Navbar
|
|
/navbar/,
|
|
/nav-/,
|
|
|
|
// Tables
|
|
/table/,
|
|
|
|
// Alerts
|
|
/alert/,
|
|
|
|
// Toast
|
|
/toast/,
|
|
|
|
// Badges
|
|
/badge/,
|
|
|
|
// Lists
|
|
/list-/,
|
|
],
|
|
},
|
|
|
|
// Mantener variables CSS de Bootstrap
|
|
variables: true,
|
|
|
|
// Mantener keyframes
|
|
keyframes: true,
|
|
|
|
// Mantener font-face
|
|
fontFace: true,
|
|
});
|
|
|
|
if (purgeCSSResult.length === 0 || !purgeCSSResult[0].css) {
|
|
console.error('ERROR: PurgeCSS returned empty result');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Agregar header al CSS generado
|
|
const header = `/**
|
|
* Bootstrap 5.3.2 Subset - ROI Theme
|
|
*
|
|
* Generado automáticamente con PurgeCSS
|
|
* Contiene SOLO las clases Bootstrap usadas en el tema.
|
|
*
|
|
* Original: ${(inputSize / 1024).toFixed(2)} KB
|
|
* Subset: ${(purgeCSSResult[0].css.length / 1024).toFixed(2)} KB
|
|
* Reduccion: ${(100 - (purgeCSSResult[0].css.length / inputSize * 100)).toFixed(1)}%
|
|
*
|
|
* Generado: ${new Date().toISOString()}
|
|
*
|
|
* Para regenerar:
|
|
* node build-bootstrap-subset.js
|
|
*/
|
|
`;
|
|
|
|
const outputCSS = header + purgeCSSResult[0].css;
|
|
|
|
// Escribir archivo
|
|
fs.writeFileSync(outputFile, outputCSS);
|
|
|
|
const outputSize = fs.statSync(outputFile).size;
|
|
const reduction = ((1 - outputSize / inputSize) * 100).toFixed(1);
|
|
|
|
console.log('');
|
|
console.log('SUCCESS!');
|
|
console.log('-'.repeat(60));
|
|
console.log(`Output: bootstrap-subset.min.css (${(outputSize / 1024).toFixed(2)} KB)`);
|
|
console.log(`Reduction: ${reduction}% smaller`);
|
|
console.log('-'.repeat(60));
|
|
console.log('');
|
|
console.log('Next steps:');
|
|
console.log('1. Update Inc/enqueue-scripts.php to use bootstrap-subset.min.css');
|
|
console.log('2. Test the theme thoroughly');
|
|
console.log('3. Run PageSpeed Insights to verify improvement');
|
|
|
|
} catch (error) {
|
|
console.error('ERROR:', error.message);
|
|
console.error(error.stack);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
buildBootstrapSubset();
|