Files
roi-theme/build-bootstrap-subset.js
FrankZamora 19b6c38fbf fix: add toast classes to Bootstrap subset safelist
The toast from IP View Limit plugin uses Bootstrap classes that weren't
being detected because PurgeCSS only scans theme files, not plugins.

Added to safelist: toast-container, toast, toast-body, position-fixed,
bottom-0, end-0, text-dark, bg-warning, btn-close, m-auto

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 11:34:59 -06:00

306 lines
9.0 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',
'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();