Files
roi-theme/Inc/enqueue-scripts.php
FrankZamora b7ae8cac21 perf(bootstrap): Reduce Bootstrap CSS de 227KB a 145KB con PurgeCSS
- Genera bootstrap-subset.min.css con solo clases usadas (36% reduccion)
- Actualiza enqueue-scripts.php para usar el subset
- Agrega scripts de build: npm run build:bootstrap
- Mejora LCP al reducir tiempo de parseo CSS

Impacto estimado:
- Bootstrap: 227KB -> 145KB (-82KB)
- Tiempo parseo CSS: ~800ms -> ~500ms
- LCP: mejora esperada ~300-500ms

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

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

581 lines
18 KiB
PHP

<?php
/**
* Enqueue Bootstrap 5 and Custom Scripts
*
* @package ROI_Theme
* @since 1.0.0
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* CSS no críticos que se cargarán de forma diferida (Fase 4.2 PageSpeed)
*
* Estos CSS se cargan con media="print" y onload="this.media='all'"
* para evitar bloquear el renderizado inicial.
*
* NOTA: Bootstrap NO está diferido porque causa CLS alto (0.954).
* Bootstrap debe cargar bloqueante para evitar layout shifts.
*
* @since 1.0.21
*/
define('ROI_DEFERRED_CSS', [
// Componentes específicos (below the fold)
'roi-badges',
'roi-pagination',
'roi-generic-tables',
'roi-video',
'roi-animations',
'roi-youtube-facade',
'roi-tables-apu',
// CSS globales no críticos
'roi-utilities',
'roi-accessibility',
'roi-responsive',
'bootstrap-icons',
]);
/**
* Add async loading to deferred stylesheets (Fase 4.2 PageSpeed)
*
* Converts media="print" to media="all" after load to prevent render-blocking.
*
* @since 1.0.21
* @param string $html The link tag HTML.
* @param string $handle The stylesheet handle.
* @return string Modified link tag.
*/
function roi_add_deferred_css_onload($html, $handle) {
if (!in_array($handle, ROI_DEFERRED_CSS, true)) {
return $html;
}
// Add onload attribute to switch media to "all" after load
$html = str_replace(
"media='print'",
"media='print' onload=\"this.media='all'\"",
$html
);
// Add noscript fallback for users without JavaScript
$href = preg_match('/href=[\'"]([^\'"]+)[\'"]/', $html, $matches) ? $matches[1] : '';
if ($href) {
$html .= '<noscript><link rel="stylesheet" href="' . esc_url($href) . '"></noscript>' . "\n";
}
return $html;
}
add_filter('style_loader_tag', 'roi_add_deferred_css_onload', 10, 2);
/**
* Enqueue typography system
*
* SELF-HOSTED: Fuentes Poppins ahora se cargan desde Assets/fonts/
* Eliminada dependencia de Google Fonts para:
* - Mejorar rendimiento (sin requests externos)
* - Cumplimiento GDPR (sin tracking de Google)
* - Evitar bloqueo de renderizado
*/
function roi_enqueue_fonts() {
// Fonts CSS local con @font-face para Poppins self-hosted
wp_enqueue_style(
'roi-fonts',
get_template_directory_uri() . '/Assets/css/css-global-fonts.css',
array(),
'1.1.0', // Bump version: self-hosted fonts
'all'
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_fonts', 1);
/**
* Enqueue Bootstrap 5 styles and scripts
*
* OPTIMIZACIÓN LCP (PageSpeed Fase 5):
* - Usa bootstrap-subset.min.css (145KB) en lugar de bootstrap.min.css (227KB)
* - Reducción: 36% menos CSS para parsear
* - Generado con PurgeCSS: node build-bootstrap-subset.js
*
* NOTA: Bootstrap debe cargar BLOQUEANTE (media='all').
* Diferirlo causa CLS alto (0.954) por layout shifts.
*/
function roi_enqueue_bootstrap() {
// Bootstrap CSS SUBSET - BLOQUEANTE para evitar CLS
// Original: 227KB -> Subset: 145KB (36% reducción)
wp_enqueue_style(
'roi-bootstrap',
get_template_directory_uri() . '/Assets/Vendor/Bootstrap/Css/bootstrap-subset.min.css',
array('roi-fonts'),
'5.3.2-subset',
'all' // Bloqueante - diferirlo causa CLS alto
);
// Bootstrap Icons CSS - SUBSET OPTIMIZADO (Fase 4.1 PageSpeed)
// Original: 211 KB (2050 iconos) -> Subset: 13 KB (104 iconos) = 94% reduccion
// DIFERIDO: Fase 4.3 - no crítico para renderizado inicial
wp_enqueue_style(
'bootstrap-icons',
get_template_directory_uri() . '/Assets/Vendor/bootstrap-icons-subset.min.css',
array('roi-bootstrap'),
ROI_VERSION,
'print'
);
// Variables CSS del Template RDash (NIVEL 1 - BLOQUEANTE - Issue #48)
wp_enqueue_style(
'roi-variables',
get_template_directory_uri() . '/Assets/css/css-global-variables.css',
array('roi-bootstrap'),
ROI_VERSION,
'all'
);
// Bootstrap JS Bundle - in footer with defer
wp_enqueue_script(
'roi-bootstrap-js',
get_template_directory_uri() . '/Assets/Vendor/Bootstrap/Js/bootstrap.bundle.min.js',
array(),
'5.3.2',
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
// Dequeue jQuery if it was enqueued
wp_dequeue_script('jquery');
wp_deregister_script('jquery');
}
add_action('wp_enqueue_scripts', 'roi_enqueue_bootstrap', 5);
/**
* Enqueue main theme stylesheet
* FASE 1 - Este es el archivo CSS principal del tema
*/
function roi_enqueue_main_stylesheet() {
wp_enqueue_style(
'roi-main-style',
get_template_directory_uri() . '/Assets/css/style.css',
array('roi-variables'),
'1.0.5', // Arquitectura: Separación de responsabilidades CSS
'all'
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_main_stylesheet', 5);
/**
* Enqueue FASE 2 CSS - Template RDash Component Styles (Issues #58-64)
*
* Estilos que replican componentes del template RDash
*
* NOTA: Hero Section, Post Content y Related Posts ahora usan
* estilos generados dinámicamente desde sus Renderers.
*/
function roi_enqueue_fase2_styles() {
// Hero Section CSS - DESHABILITADO: estilos generados por HeroRenderer
// @see Public/Hero/Infrastructure/Ui/HeroRenderer.php
// Category Badges CSS - Clase genérica (Issue #62)
// DIFERIDO: Fase 4.2 PageSpeed - media='print' + onload
wp_enqueue_style(
'roi-badges',
get_template_directory_uri() . '/Assets/css/css-global-badges.css',
array('roi-bootstrap'),
filemtime(get_template_directory() . '/Assets/css/css-global-badges.css'),
'print' // Diferido para no bloquear renderizado
);
// Pagination CSS - Estilos personalizados (Issue #64)
// DIFERIDO: Fase 4.2 PageSpeed - below the fold
wp_enqueue_style(
'roi-pagination',
get_template_directory_uri() . '/Assets/css/css-global-pagination.css',
array('roi-bootstrap'),
filemtime(get_template_directory() . '/Assets/css/css-global-pagination.css'),
'print' // Diferido para no bloquear renderizado
);
// Post Content Typography y Related Posts - DESHABILITADOS
// Los estilos ahora están integrados en style.css o generados dinámicamente
}
add_action('wp_enqueue_scripts', 'roi_enqueue_fase2_styles', 6);
/**
* Enqueue Global Components CSS
*
* ARQUITECTURA: Componentes globales que aparecen en todas las páginas
* Issue #121 - Separación de componentes del style.css
*
* @since 1.0.7
*/
function roi_enqueue_global_components() {
// Notification Bar CSS - DESHABILITADO: Los estilos de la barra de notificación ahora se generan
// dinámicamente desde el TopNotificationBarRenderer basado en los valores de la BD.
// @see Public/TopNotificationBar/Infrastructure/Ui/TopNotificationBarRenderer.php
/*
wp_enqueue_style(
'roi-notification-bar',
get_template_directory_uri() . '/Assets/css/componente-top-bar.css',
array('roi-bootstrap'),
filemtime(get_template_directory() . '/Assets/css/componente-top-bar.css'),
'all'
);
*/
// Navbar CSS - DESHABILITADO: Los estilos del navbar ahora se generan
// dinámicamente desde el NavbarRenderer basado en los valores de la BD.
// El archivo componente-navbar.css tenía !important que sobrescribía
// los estilos configurados por el usuario en el Admin Panel.
// @see Public/Navbar/Infrastructure/Ui/NavbarRenderer.php
/*
wp_enqueue_style(
'roi-navbar',
get_template_directory_uri() . '/Assets/css/componente-navbar.css',
array('roi-bootstrap'),
filemtime(get_template_directory() . '/Assets/css/componente-navbar.css'),
'all'
);
*/
// Buttons CSS - DESHABILITADO: Los estilos del botón Let's Talk ahora se generan
// dinámicamente desde el CtaLetsTalkRenderer basado en los valores de la BD.
// El archivo componente-boton-lets-talk.css tenía !important que sobrescribía
// los estilos configurados por el usuario en el Admin Panel.
// @see Public/CtaLetsTalk/Infrastructure/Ui/CtaLetsTalkRenderer.php
/*
wp_enqueue_style(
'roi-buttons',
get_template_directory_uri() . '/Assets/css/componente-boton-lets-talk.css',
array('roi-bootstrap'),
filemtime(get_template_directory() . '/Assets/css/componente-boton-lets-talk.css'),
'all'
);
*/
}
add_action('wp_enqueue_scripts', 'roi_enqueue_global_components', 7);
/**
* Enqueue header scripts
*
* NOTA: CSS del header se genera dinámicamente desde NavbarRenderer
* @see Public/Navbar/Infrastructure/Ui/NavbarRenderer.php
*
* DESHABILITADO: header.js (2025-11-27 - Optimización TBT Fase 2.3)
* Motivo: ~90% código muerto. Los elementos que busca no existen:
* - mobile-menu-toggle: no existe (sitio usa .navbar-toggler de Bootstrap)
* - mobile-menu: no existe (usa Bootstrap collapse)
* - masthead: no existe
* - Smooth scroll duplicado de main.js
* Reducción: ~343 líneas de JS eliminadas del parsing
*/
// function roi_enqueue_header() {
// wp_enqueue_script(
// 'roi-header-js',
// get_template_directory_uri() . '/Assets/js/header.js',
// array(),
// '1.0.0',
// array(
// 'in_footer' => true,
// 'strategy' => 'defer',
// )
// );
// }
// add_action('wp_enqueue_scripts', 'roi_enqueue_header', 10);
/**
* Enqueue generic tables styles
*
* ARQUITECTURA: Estilos para tablas genéricas en post-content
* Solo en posts individuales
*/
function roi_enqueue_generic_tables() {
// Only enqueue on single posts
if (!is_single()) {
return;
}
// Generic Tables CSS
// DIFERIDO: Fase 4.2 PageSpeed - below the fold
wp_enqueue_style(
'roi-generic-tables',
get_template_directory_uri() . '/Assets/css/css-global-generic-tables.css',
array('roi-bootstrap'),
ROI_VERSION,
'print' // Diferido para no bloquear renderizado
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_generic_tables', 11);
/**
* Enqueue video iframe styles
*
* ARQUITECTURA: Estilos para videos embebidos (YouTube, Vimeo)
* Solo en posts individuales
*/
function roi_enqueue_video_styles() {
// Only enqueue on single posts
if (!is_single()) {
return;
}
// Video CSS
// DIFERIDO: Fase 4.2 PageSpeed - below the fold
wp_enqueue_style(
'roi-video',
get_template_directory_uri() . '/Assets/css/css-global-video.css',
array('roi-bootstrap'),
ROI_VERSION,
'print' // Diferido para no bloquear renderizado
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_video_styles', 11);
/**
* Enqueue main JavaScript
*
* ELIMINADO: custom-style.css (Issue #131)
* Motivo: Archivo contenía 95% código duplicado de style.css y otros componentes
* Código único movido a: generic-tables.css, video.css
*/
function roi_enqueue_main_javascript() {
// Main JavaScript - navbar scroll effects
// OPTIMIZACIÓN TBT Fase 2.3: Eliminado ~300 líneas de código muerto
// Solo mantiene: navbar scroll effect (agrega clase 'scrolled')
wp_enqueue_script(
'roi-main-js',
get_template_directory_uri() . '/Assets/js/main.js',
array('roi-bootstrap-js'),
'1.0.5', // TBT Fase 2.3: eliminado código muerto (~315 → ~25 líneas)
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_main_javascript', 11);
// ELIMINADO: roi_remove_defer_from_main_js
// Motivo: wp_localize_script ya no se usa, main.js ahora puede usar defer
// Fecha: 2025-11-27 - Optimización TBT Fase 2
/**
* ELIMINADO: roi_enqueue_footer_styles
* Motivo: footer.css NO está documentado - CSS debe estar en style.css
* Ver: theme-documentation/16-componente-footer-contact-form/CSS-ESPECIFICO.md
*/
/**
* Enqueue accessibility styles and scripts
*/
function roi_enqueue_accessibility() {
// Accessibility CSS
// DIFERIDO: Fase 4.3 - no crítico para renderizado inicial
wp_enqueue_style(
'roi-accessibility',
get_template_directory_uri() . '/Assets/css/css-global-accessibility.css',
array('roi-main-style'),
ROI_VERSION,
'print'
);
// Accessibility JavaScript
wp_enqueue_script(
'roi-accessibility-js',
get_template_directory_uri() . '/Assets/js/accessibility.js',
array('roi-bootstrap-js'),
ROI_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_accessibility', 15);
/**
* Enqueue del script de carga retrasada de AdSense
*
* Este script se encarga de detectar la primera interacción del usuario
* (scroll, click, touch, etc.) y cargar los scripts de AdSense solo
* en ese momento, mejorando significativamente el rendimiento inicial.
*/
function roi_enqueue_adsense_loader() {
// Solo ejecutar en frontend
if (is_admin()) {
return;
}
// Verificar si el retardo de AdSense está habilitado (Clean Architecture)
$is_enabled = roi_get_component_setting('adsense-delay', 'visibility', 'is_enabled', true);
if (!$is_enabled) {
return;
}
// Enqueue del script de carga de AdSense
wp_enqueue_script(
'roi-adsense-loader',
get_template_directory_uri() . '/Assets/js/adsense-loader.js',
array(),
ROI_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_adsense_loader', 10);
/**
* Enqueue theme core styles
*
* ELIMINADO: theme.css (Issue #125)
* Motivo: theme.css contenía código experimental y sobrescrituras Bootstrap no documentadas
* Resultado: 638 líneas eliminadas - TODO el CSS documentado va en style.css
* Fecha: 2025-01-07
*/
function roi_enqueue_theme_styles() {
// Theme Core Styles - ELIMINADO theme.css
// wp_enqueue_style(
// 'roi-theme',
// get_template_directory_uri() . '/Assets/css/theme.css',
// array('roi-bootstrap'),
// '1.0.0',
// 'all'
// );
// Theme Animations
// DIFERIDO: Fase 4.2 PageSpeed - mejoras visuales no críticas
wp_enqueue_style(
'roi-animations',
get_template_directory_uri() . '/Assets/css/css-global-animations.css',
array('roi-bootstrap'),
'1.0.0',
'print' // Diferido para no bloquear renderizado
);
// Theme Responsive Styles
// DIFERIDO: Fase 4.3 - media queries no críticas para primer paint
wp_enqueue_style(
'roi-responsive',
get_template_directory_uri() . '/Assets/css/css-global-responsive.css',
array('roi-bootstrap'),
'1.1.0',
'print'
);
// Theme Utilities
// DIFERIDO: Fase 4.3 - clases utilitarias no críticas
wp_enqueue_style(
'roi-utilities',
get_template_directory_uri() . '/Assets/css/css-global-utilities.css',
array('roi-bootstrap'),
'1.0.0',
'print'
);
// Print Styles
wp_enqueue_style(
'roi-print',
get_template_directory_uri() . '/Assets/css/css-global-print.css',
array(),
'1.0.0',
'print'
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_theme_styles', 13);
/**
* Enqueue social share styles
*
* DESHABILITADO: Los estilos de Social Share ahora se generan
* dinámicamente desde SocialShareRenderer basado en valores de BD.
* @see Public/SocialShare/Infrastructure/Ui/SocialShareRenderer.php
*/
// function roi_enqueue_social_share_styles() - REMOVED
/**
* Enqueue APU Tables styles
*/
function roi_enqueue_apu_tables_styles() {
// APU Tables CSS
// DIFERIDO: Fase 4.2 PageSpeed - below the fold
wp_enqueue_style(
'roi-tables-apu',
get_template_directory_uri() . '/Assets/css/css-tablas-apu.css',
array('roi-bootstrap'),
ROI_VERSION,
'print' // Diferido para no bloquear renderizado
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_apu_tables_styles', 15);
/**
* Enqueue APU Tables auto-class JavaScript
*
* Este script detecta automáticamente filas especiales en tablas .desglose y .analisis
* y les agrega las clases CSS correspondientes (section-header, subtotal-row, total-row)
*
* Issue #132
*/
function roi_enqueue_apu_tables_autoclass_script() {
// APU Tables Auto-Class JS
wp_enqueue_script(
'roi-apu-tables-autoclass',
get_template_directory_uri() . '/Assets/js/apu-tables-auto-class.js',
array(),
ROI_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
}
add_action('wp_enqueue_scripts', 'roi_enqueue_apu_tables_autoclass_script', 15);
/**
* Enqueue CTA Box Sidebar styles (Issue #36)
*
* DESHABILITADO: Los estilos del CTA Box Sidebar ahora se generan
* dinámicamente desde CtaBoxSidebarRenderer basado en valores de BD.
* @see Public/CtaBoxSidebar/Infrastructure/Ui/CtaBoxSidebarRenderer.php
*/
// function roi_enqueue_cta_box_sidebar_assets() - REMOVED
/**
* Enqueue TOC Sidebar styles (only on single posts)
*
* DESHABILITADO: Los estilos del TOC ahora se generan
* dinámicamente desde TableOfContentsRenderer basado en valores de BD.
* @see Public/TableOfContents/Infrastructure/Ui/TableOfContentsRenderer.php
*
* @since 1.0.5
*/
// function roi_enqueue_toc_sidebar_assets() - REMOVED
/**
* Enqueue Footer Contact Form styles
*
* DESHABILITADO: Los estilos del Contact Form ahora se generan
* dinámicamente desde ContactFormRenderer basado en valores de BD.
* @see Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php
*/
// function roi_enqueue_footer_contact_assets() - REMOVED