feat(custom-css-manager): implementar TIPO 3 - CSS Crítico Personalizado
Nuevo sistema de gestión de CSS personalizado con panel admin: - Admin/CustomCSSManager: CRUD de snippets CSS (crítico/diferido) - Public/CustomCSSManager: Inyección dinámica en frontend - Schema JSON para configuración del componente Migración de CSS estático a BD: - Tablas APU (~14KB) → snippet diferido en BD - Tablas Genéricas (~10KB) → snippet diferido en BD - Comentadas funciones legacy en enqueue-scripts.php Limpieza de archivos obsoletos: - Eliminado build-bootstrap-subset.js - Eliminado migrate-legacy-options.php - Eliminado minify-css.php - Eliminado purgecss.config.js Beneficios: - CSS editable desde admin sin tocar código - Soporte crítico (head) y diferido (footer) - Filtrado por scope (all/home/single/archive) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,307 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
Reference in New Issue
Block a user