diff --git a/Assets/css/critical-bootstrap.css b/Assets/css/critical-bootstrap.css new file mode 100644 index 00000000..d489f013 --- /dev/null +++ b/Assets/css/critical-bootstrap.css @@ -0,0 +1,470 @@ +/** + * Critical Bootstrap CSS Subset + * + * Contiene SOLO las clases Bootstrap usadas en componentes above-the-fold: + * - TopNotificationBar + * - Navbar + * - Hero + * + * Esto permite diferir la carga del Bootstrap completo (31KB) + * mientras se renderiza el contenido crítico inmediatamente. + * + * @version 5.3.2-subset + * @see Inc/enqueue-scripts.php - Bootstrap diferido + * @see Shared/Infrastructure/Services/CriticalBootstrapService.php + */ + +/* ========================================================================== + BOX SIZING & RESETS (Bootstrap Reboot crítico) + ========================================================================== */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: var(--bs-body-font-family, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif); + font-size: var(--bs-body-font-size, 1rem); + font-weight: var(--bs-body-font-weight, 400); + line-height: var(--bs-body-line-height, 1.5); + color: var(--bs-body-color, #212529); + background-color: var(--bs-body-bg, #fff); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} + +a { + color: var(--bs-link-color, #0d6efd); + text-decoration: underline; +} +a:hover { + color: var(--bs-link-hover-color, #0a58ca); +} + +img, svg { + vertical-align: middle; +} + +button { + border-radius: 0; +} +button:focus:not(:focus-visible) { + outline: 0; +} + +/* ========================================================================== + CONTAINER (Layout crítico) + ========================================================================== */ +.container, +.container-fluid { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container { max-width: 540px; } +} +@media (min-width: 768px) { + .container { max-width: 720px; } +} +@media (min-width: 992px) { + .container { max-width: 960px; } +} +@media (min-width: 1200px) { + .container { max-width: 1140px; } +} +@media (min-width: 1400px) { + .container { max-width: 1320px; } +} + +/* ========================================================================== + FLEXBOX UTILITIES (Layout crítico) + ========================================================================== */ +.d-flex { + display: flex !important; +} +.d-none { + display: none !important; +} +.d-block { + display: block !important; +} + +.flex-wrap { + flex-wrap: wrap !important; +} + +.justify-content-center { + justify-content: center !important; +} +.justify-content-between { + justify-content: space-between !important; +} + +.align-items-center { + align-items: center !important; +} + +.gap-2 { + gap: 0.5rem !important; +} + +/* ========================================================================== + SPACING UTILITIES (Margin/Padding críticos) + ========================================================================== */ +.mb-0 { margin-bottom: 0 !important; } +.mb-2 { margin-bottom: 0.5rem !important; } +.mb-3 { margin-bottom: 1rem !important; } + +.me-1 { margin-right: 0.25rem !important; } +.me-2 { margin-right: 0.5rem !important; } + +.ms-2 { margin-left: 0.5rem !important; } +.ms-3 { margin-left: 1rem !important; } + +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} + +/* ========================================================================== + NAVBAR COMPONENT (Crítico - Above the fold) + ========================================================================== */ +.navbar { + --bs-navbar-padding-x: 0; + --bs-navbar-padding-y: 0.5rem; + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-padding-y: 0.3125rem; + --bs-navbar-brand-margin-end: 1rem; + --bs-navbar-brand-font-size: 1.25rem; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-nav-link-padding-x: 0.5rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-padding-x: 0.75rem; + --bs-navbar-toggler-font-size: 1.25rem; + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-border-radius: var(--bs-border-radius, 0.375rem); + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x); +} + +.navbar > .container, +.navbar > .container-fluid { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: space-between; +} + +.navbar-brand { + padding-top: var(--bs-navbar-brand-padding-y); + padding-bottom: var(--bs-navbar-brand-padding-y); + margin-right: var(--bs-navbar-brand-margin-end); + font-size: var(--bs-navbar-brand-font-size); + color: var(--bs-navbar-brand-color); + text-decoration: none; + white-space: nowrap; +} +.navbar-brand:hover, +.navbar-brand:focus { + color: var(--bs-navbar-brand-hover-color); +} + +.navbar-nav { + --bs-nav-link-padding-x: 0; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-color: var(--bs-navbar-color); + --bs-nav-link-hover-color: var(--bs-navbar-hover-color); + --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; + color: var(--bs-nav-link-color); +} +.navbar-nav .nav-link:hover, +.navbar-nav .nav-link:focus { + color: var(--bs-nav-link-hover-color); +} +.navbar-nav .nav-link.active { + color: var(--bs-navbar-active-color); +} + +.nav-link { + display: block; + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); + font-size: var(--bs-nav-link-font-size); + font-weight: var(--bs-nav-link-font-weight); + color: var(--bs-nav-link-color); + text-decoration: none; + background: 0 0; + border: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; +} + +.nav-item { + margin-bottom: 0; +} + +.navbar-toggler { + padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x); + font-size: var(--bs-navbar-toggler-font-size); + line-height: 1; + color: var(--bs-navbar-color); + background-color: transparent; + border: var(--bs-border-width, 1px) solid var(--bs-navbar-toggler-border-color); + border-radius: var(--bs-navbar-toggler-border-radius); + transition: var(--bs-navbar-toggler-transition); +} +.navbar-toggler:hover { + text-decoration: none; +} +.navbar-toggler:focus { + text-decoration: none; + outline: 0; + box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width); +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + background-image: var(--bs-navbar-toggler-icon-bg); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} + +.navbar-dark, +.navbar[data-bs-theme="dark"] { + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +/* ========================================================================== + COLLAPSE COMPONENT (Navbar mobile) + ========================================================================== */ +.collapse:not(.show) { + display: none; +} + +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + align-items: center; +} + +/* ========================================================================== + DROPDOWN COMPONENT (Navbar submenus) + ========================================================================== */ +.dropdown { + position: relative; +} + +.dropdown-toggle { + white-space: nowrap; +} +.dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} + +.dropdown-menu { + --bs-dropdown-zindex: 1000; + --bs-dropdown-min-width: 10rem; + --bs-dropdown-padding-x: 0; + --bs-dropdown-padding-y: 0.5rem; + --bs-dropdown-spacer: 0.125rem; + --bs-dropdown-font-size: 1rem; + --bs-dropdown-color: var(--bs-body-color, #212529); + --bs-dropdown-bg: var(--bs-body-bg, #fff); + --bs-dropdown-border-color: var(--bs-border-color-translucent, rgba(0,0,0,.175)); + --bs-dropdown-border-radius: var(--bs-border-radius, 0.375rem); + --bs-dropdown-border-width: var(--bs-border-width, 1px); + --bs-dropdown-inner-border-radius: calc(var(--bs-border-radius, 0.375rem) - var(--bs-border-width, 1px)); + --bs-dropdown-divider-bg: var(--bs-border-color-translucent, rgba(0,0,0,.175)); + --bs-dropdown-divider-margin-y: 0.5rem; + --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-dropdown-link-color: var(--bs-body-color, #212529); + --bs-dropdown-link-hover-color: var(--bs-body-color, #212529); + --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg, #f8f9fa); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: var(--bs-tertiary-color, #adb5bd); + --bs-dropdown-item-padding-x: 1rem; + --bs-dropdown-item-padding-y: 0.25rem; + --bs-dropdown-header-color: #6c757d; + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; + position: absolute; + z-index: var(--bs-dropdown-zindex); + display: none; + min-width: var(--bs-dropdown-min-width); + padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); + margin: 0; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); + text-align: left; + list-style: none; + background-color: var(--bs-dropdown-bg); + background-clip: padding-box; + border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); + border-radius: var(--bs-dropdown-border-radius); +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-item { + display: block; + width: 100%; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + clear: both; + font-weight: 400; + color: var(--bs-dropdown-link-color); + text-align: inherit; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border: 0; +} +.dropdown-item:hover, +.dropdown-item:focus { + color: var(--bs-dropdown-link-hover-color); + background-color: var(--bs-dropdown-link-hover-bg); +} +.dropdown-item.active, +.dropdown-item:active { + color: var(--bs-dropdown-link-active-color); + text-decoration: none; + background-color: var(--bs-dropdown-link-active-bg); +} + +/* ========================================================================== + TEXT UTILITIES + ========================================================================== */ +.text-decoration-underline { + text-decoration: underline !important; +} + +/* ========================================================================== + BUTTON CLOSE (Dismiss notification) + ========================================================================== */ +.btn-close { + --bs-btn-close-color: #000; + --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e"); + --bs-btn-close-opacity: 0.5; + --bs-btn-close-hover-opacity: 0.75; + --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + --bs-btn-close-focus-opacity: 1; + --bs-btn-close-disabled-opacity: 0.25; + --bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%); + box-sizing: content-box; + width: 1em; + height: 1em; + padding: 0.25em 0.25em; + color: var(--bs-btn-close-color); + background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat; + border: 0; + border-radius: 0.375rem; + opacity: var(--bs-btn-close-opacity); +} +.btn-close:hover { + color: var(--bs-btn-close-color); + text-decoration: none; + opacity: var(--bs-btn-close-hover-opacity); +} +.btn-close:focus { + outline: 0; + box-shadow: var(--bs-btn-close-focus-shadow); + opacity: var(--bs-btn-close-focus-opacity); +} + +.btn-close-white { + filter: var(--bs-btn-close-white-filter); +} + +/* ========================================================================== + RESPONSIVE BREAKPOINTS (navbar-expand-lg) + ========================================================================== */ +@media (min-width: 992px) { + .navbar-expand-lg { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + + .d-lg-block { display: block !important; } + .d-lg-none { display: none !important; } + .mb-lg-0 { margin-bottom: 0 !important; } +} + +@media (max-width: 991.98px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +/* ========================================================================== + RESPONSIVE DISPLAY UTILITIES (md breakpoint) + ========================================================================== */ +@media (min-width: 768px) { + .d-md-block { display: block !important; } + .d-md-none { display: none !important; } +} diff --git a/Inc/enqueue-scripts.php b/Inc/enqueue-scripts.php index 7b46e245..5a77be2f 100644 --- a/Inc/enqueue-scripts.php +++ b/Inc/enqueue-scripts.php @@ -17,9 +17,16 @@ if (!defined('ABSPATH')) { * Estos CSS se cargan con media="print" y onload="this.media='all'" * para evitar bloquear el renderizado inicial. * + * IMPORTANTE: Bootstrap se difiere porque su CSS crítico se inyecta + * inline via CriticalBootstrapService (~8KB subset en ). + * El Bootstrap completo (31KB) carga después sin bloquear. + * * @since 1.0.21 + * @since 1.0.22 Added roi-bootstrap to deferred list */ define('ROI_DEFERRED_CSS', [ + // Framework CSS (critical subset inline, full deferred) + 'roi-bootstrap', // Componentes específicos (below the fold) 'roi-badges', 'roi-pagination', @@ -91,15 +98,23 @@ add_action('wp_enqueue_scripts', 'roi_enqueue_fonts', 1); /** * Enqueue Bootstrap 5 styles and scripts + * + * OPTIMIZACIÓN PageSpeed: Bootstrap CSS se carga diferido. + * El CSS crítico (container, navbar, flexbox) se inyecta inline + * via CriticalBootstrapService antes de que Bootstrap cargue. + * + * @see Shared/Infrastructure/Services/CriticalBootstrapService.php + * @see Assets/css/critical-bootstrap.css */ function roi_enqueue_bootstrap() { - // Bootstrap CSS - with high priority + // Bootstrap CSS - DIFERIDO: critical subset inline, full deferred + // media='print' + onload cambia a 'all' cuando carga (ver ROI_DEFERRED_CSS) wp_enqueue_style( 'roi-bootstrap', get_template_directory_uri() . '/Assets/Vendor/Bootstrap/Css/bootstrap.min.css', array('roi-fonts'), '5.3.2', - 'all' + 'print' // Diferido - CSS crítico inline via CriticalBootstrapService ); // Bootstrap Icons CSS - SUBSET OPTIMIZADO (Fase 4.1 PageSpeed) diff --git a/Shared/Infrastructure/Services/CriticalBootstrapService.php b/Shared/Infrastructure/Services/CriticalBootstrapService.php new file mode 100644 index 00000000..ccbcb001 --- /dev/null +++ b/Shared/Infrastructure/Services/CriticalBootstrapService.php @@ -0,0 +1,148 @@ + via wp_head (priority 0, antes de CriticalCSSService) + * - Cachear contenido para evitar lecturas repetidas + * + * FLUJO: + * 1. wp_head (priority 0) → render() + * 2. Lee Assets/css/critical-bootstrap.css + * 3. Output: + * + * IMPORTANTE: + * - Este servicio se ejecuta ANTES de CriticalCSSService (priority 0 vs 1) + * - Bootstrap completo se carga diferido (media="print" + onload) + * + * @package ROITheme\Shared\Infrastructure\Services + */ +final class CriticalBootstrapService +{ + /** + * Instancia singleton + */ + private static ?self $instance = null; + + /** + * Cache del contenido CSS + */ + private ?string $cssCache = null; + + /** + * Ruta al archivo CSS crítico + */ + private string $cssFilePath; + + /** + * Constructor privado (singleton) + */ + private function __construct() + { + $this->cssFilePath = get_template_directory() . '/Assets/css/critical-bootstrap.css'; + } + + /** + * Obtiene la instancia singleton + * + * @return self + */ + public static function getInstance(): self + { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Renderiza Critical Bootstrap CSS en wp_head + * + * Este método se llama desde wp_head con priority 0 (muy temprano). + * Inyecta el subset de Bootstrap antes del CSS de componentes. + * + * @return void + */ + public function render(): void + { + $css = $this->getCriticalCSS(); + + if (empty($css)) { + return; + } + + // Minificar CSS removiendo comentarios y espacios innecesarios + $minifiedCSS = $this->minifyCSS($css); + + printf( + '' . "\n", + $minifiedCSS + ); + } + + /** + * Obtiene el contenido del CSS crítico + * + * @return string CSS crítico o string vacío si no existe + */ + private function getCriticalCSS(): string + { + // Usar cache si existe + if ($this->cssCache !== null) { + return $this->cssCache; + } + + // Verificar que el archivo existe + if (!file_exists($this->cssFilePath)) { + error_log('ROI Theme: Critical Bootstrap CSS file not found: ' . $this->cssFilePath); + $this->cssCache = ''; + return ''; + } + + // Leer contenido + $content = file_get_contents($this->cssFilePath); + + if ($content === false) { + error_log('ROI Theme: Failed to read Critical Bootstrap CSS'); + $this->cssCache = ''; + return ''; + } + + $this->cssCache = $content; + return $content; + } + + /** + * Minifica CSS removiendo comentarios y espacios innecesarios + * + * @param string $css CSS a minificar + * @return string CSS minificado + */ + private function minifyCSS(string $css): string + { + // Remover comentarios + $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css); + + // Remover espacios, tabs y newlines + $css = str_replace(["\r\n", "\r", "\n", "\t"], '', $css); + + // Remover espacios múltiples + $css = preg_replace('/\s+/', ' ', $css); + + // Remover espacios alrededor de caracteres especiales + $css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css); + + // Remover último punto y coma antes de } + $css = str_replace(';}', '}', $css); + + return trim($css); + } +} diff --git a/Shared/Infrastructure/Wordpress/CriticalCSSHooksRegistrar.php b/Shared/Infrastructure/Wordpress/CriticalCSSHooksRegistrar.php index 3b2f88a1..a57674fa 100644 --- a/Shared/Infrastructure/Wordpress/CriticalCSSHooksRegistrar.php +++ b/Shared/Infrastructure/Wordpress/CriticalCSSHooksRegistrar.php @@ -4,22 +4,31 @@ declare(strict_types=1); namespace ROITheme\Shared\Infrastructure\Wordpress; use ROITheme\Shared\Infrastructure\Services\CriticalCSSService; +use ROITheme\Shared\Infrastructure\Services\CriticalBootstrapService; /** - * Registra hook wp_head para inyectar CSS crítico + * Registra hooks wp_head para inyectar CSS crítico * * RESPONSABILIDAD: - * - Registrar hook wp_head (priority 1) - * - Delegar renderizado a CriticalCSSService + * - Registrar hook wp_head (priority 0) para Critical Bootstrap + * - Registrar hook wp_head (priority 1) para Critical Component CSS + * - Delegar renderizado a servicios especializados * * FLUJO: - * 1. wp_head (priority 1) → renderCriticalCSS() - * 2. CriticalCSSService consulta BD por componentes is_critical=true - * 3. Genera CSS usando Renderers y lo inyecta en - * 4. Los Renderers detectan is_critical y omiten CSS inline + * 1. wp_head (priority 0) → CriticalBootstrapService::render() + * - Lee Assets/css/critical-bootstrap.css (subset ~8KB) + * - Output: + * + * 2. wp_head (priority 1) → CriticalCSSService::render() + * - Consulta BD por componentes is_critical=true + * - Genera CSS usando Renderers + * - Output: + * + * 3. Bootstrap completo se carga diferido (media="print" + onload) + * - No bloquea renderizado inicial * * PATRÓN: - * - SRP: Solo registra hook, delega lógica a CriticalCSSService + * - SRP: Solo registra hooks, delega lógica a servicios * * UBICACIÓN: Infrastructure/Wordpress * @@ -28,7 +37,8 @@ use ROITheme\Shared\Infrastructure\Services\CriticalCSSService; final class CriticalCSSHooksRegistrar { public function __construct( - private readonly CriticalCSSService $criticalCSSService + private readonly CriticalCSSService $criticalCSSService, + private readonly CriticalBootstrapService $criticalBootstrapService ) {} /** @@ -36,12 +46,27 @@ final class CriticalCSSHooksRegistrar */ public function register(): void { - // Priority 1 = muy temprano en , antes de otros estilos + // Priority 0 = Critical Bootstrap (primero, antes de componentes) + add_action('wp_head', [$this, 'renderCriticalBootstrap'], 0); + + // Priority 1 = Critical Component CSS (después de Bootstrap) add_action('wp_head', [$this, 'renderCriticalCSS'], 1); } /** - * Callback para wp_head + * Callback para wp_head - Critical Bootstrap + * + * Inyecta subset de Bootstrap (~8KB) inline: + * - Container, flexbox, navbar, dropdown + * - Output: + */ + public function renderCriticalBootstrap(): void + { + $this->criticalBootstrapService->render(); + } + + /** + * Callback para wp_head - Critical Component CSS * * Ejecuta CriticalCSSService que: * - Consulta BD por componentes con is_critical=true diff --git a/functions-addon.php b/functions-addon.php index 1771de7c..ef2358ef 100644 --- a/functions-addon.php +++ b/functions-addon.php @@ -280,18 +280,31 @@ function roi_render_component(string $componentName): string { // ============================================================================= /** - * Registra el hook para inyectar CSS crítico en + * Registra hooks para inyectar CSS crítico en * * FLUJO: - * 1. wp_head (priority 1) → CriticalCSSService::render() - * 2. CriticalCSSService consulta BD por componentes con is_critical=true - * 3. Genera CSS usando los métodos públicos generateCSS() de los Renderers - * 4. Output: - * 5. Cuando los Renderers ejecutan, detectan is_critical y omiten CSS inline + * 1. wp_head (priority 0) → CriticalBootstrapService::render() + * - Lee Assets/css/critical-bootstrap.css (subset ~8KB) + * - Output: + * + * 2. wp_head (priority 1) → CriticalCSSService::render() + * - Consulta BD por componentes con is_critical=true + * - Genera CSS usando los métodos públicos generateCSS() de los Renderers + * - Output: + * + * 3. Bootstrap completo se carga diferido (media="print" + onload) + * - Ver Inc/enqueue-scripts.php + * + * 4. Cuando los Renderers ejecutan, detectan is_critical y omiten CSS inline */ add_action('after_setup_theme', function() { $criticalCSSService = roi_get_critical_css_service(); - $hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar($criticalCSSService); + $criticalBootstrapService = \ROITheme\Shared\Infrastructure\Services\CriticalBootstrapService::getInstance(); + + $hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar( + $criticalCSSService, + $criticalBootstrapService + ); $hooksRegistrar->register(); });