feat(pagespeed): implementar campo is_critical para CSS crítico dinámico (Phase 4.2)
Implementación completa del sistema de Critical CSS dinámico según plan 13.01: Domain Layer: - Crear CriticalCSSCollectorInterface para DIP compliance Infrastructure Layer: - Implementar CriticalCSSCollector (singleton via DIContainer) - Crear CriticalCSSHooksRegistrar para inyección en wp_head - Actualizar DIContainer con getCriticalCSSCollector() Schemas: - Agregar campo is_critical a navbar, top-notification-bar, hero - Sincronizar con BD (18+39+31 campos) Renderers (navbar, top-notification-bar, hero): - Inyectar CriticalCSSCollectorInterface via constructor - Lógica condicional: si is_critical=true → CSS a <head> Admin (FormBuilders + FieldMappers): - Toggle "CSS Crítico" en sección visibility - Mapeo AJAX para persistencia Beneficios: - LCP optimizado: CSS crítico inline en <head> - Above-the-fold rendering sin FOUC - Componentes configurables desde admin panel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ final class HeroFieldMapper implements FieldMapperInterface
|
||||
'heroShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||
'heroShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||
'heroShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
||||
'heroIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
||||
|
||||
// Content
|
||||
'heroShowCategories' => ['group' => 'content', 'attribute' => 'show_categories'],
|
||||
|
||||
@@ -103,7 +103,7 @@ final class HeroFormBuilder
|
||||
$html .= ' </div>';
|
||||
|
||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
||||
$html .= ' <div class="mb-0 mt-3">';
|
||||
$html .= ' <div class="mb-2 mt-3">';
|
||||
$html .= ' <label for="heroShowOnPages" class="form-label small mb-1 fw-semibold">';
|
||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
||||
$html .= ' Mostrar en';
|
||||
@@ -116,6 +116,20 @@ final class HeroFormBuilder
|
||||
$html .= ' </select>';
|
||||
$html .= ' </div>';
|
||||
|
||||
// Switch: CSS Crítico
|
||||
$isCritical = $this->renderer->getFieldValue($componentId, 'visibility', 'is_critical', true);
|
||||
$html .= ' <div class="mb-0 mt-3">';
|
||||
$html .= ' <div class="form-check form-switch">';
|
||||
$html .= ' <input class="form-check-input" type="checkbox" id="heroIsCritical" ';
|
||||
$html .= checked($isCritical, true, false) . '>';
|
||||
$html .= ' <label class="form-check-label small" for="heroIsCritical">';
|
||||
$html .= ' <i class="bi bi-lightning-charge me-1" style="color: #FF8600;"></i>';
|
||||
$html .= ' <strong>CSS Crítico</strong>';
|
||||
$html .= ' <small class="text-muted d-block">Inyectar CSS en <head> para optimizar LCP</small>';
|
||||
$html .= ' </label>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' </div>';
|
||||
|
||||
$html .= ' </div>';
|
||||
$html .= '</div>';
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ final class NavbarFieldMapper implements FieldMapperInterface
|
||||
'navbarShowDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||
'navbarShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
||||
'navbarSticky' => ['group' => 'visibility', 'attribute' => 'sticky_enabled'],
|
||||
'navbarIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
||||
|
||||
// Layout
|
||||
'navbarContainerType' => ['group' => 'layout', 'attribute' => 'container_type'],
|
||||
|
||||
@@ -119,7 +119,7 @@ final class NavbarFormBuilder
|
||||
|
||||
// Switch: Sticky
|
||||
$sticky = $this->renderer->getFieldValue($componentId, 'visibility', 'sticky_enabled', true);
|
||||
$html .= ' <div class="mb-0">';
|
||||
$html .= ' <div class="mb-2">';
|
||||
$html .= ' <div class="form-check form-switch">';
|
||||
$html .= ' <input class="form-check-input" type="checkbox" id="navbarSticky" name="visibility[sticky_enabled]" ';
|
||||
$html .= checked($sticky, true, false) . '>';
|
||||
@@ -129,6 +129,19 @@ final class NavbarFormBuilder
|
||||
$html .= ' </div>';
|
||||
$html .= ' </div>';
|
||||
|
||||
// Switch: CSS Crítico
|
||||
$isCritical = $this->renderer->getFieldValue($componentId, 'visibility', 'is_critical', true);
|
||||
$html .= ' <div class="mb-0">';
|
||||
$html .= ' <div class="form-check form-switch">';
|
||||
$html .= ' <input class="form-check-input" type="checkbox" id="navbarIsCritical" name="visibility[is_critical]" ';
|
||||
$html .= checked($isCritical, true, false) . '>';
|
||||
$html .= ' <label class="form-check-label small" for="navbarIsCritical">';
|
||||
$html .= ' <strong>CSS Crítico</strong>';
|
||||
$html .= ' <small class="text-muted d-block">Inyectar CSS en <head> para optimizar LCP</small>';
|
||||
$html .= ' </label>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' </div>';
|
||||
|
||||
$html .= ' </div>';
|
||||
$html .= '</div>';
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ final class TopNotificationBarFieldMapper implements FieldMapperInterface
|
||||
'topBarShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||
'topBarShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||
'topBarShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
||||
'topBarIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
||||
|
||||
// Content
|
||||
'topBarIconClass' => ['group' => 'content', 'attribute' => 'icon_class'],
|
||||
|
||||
@@ -107,7 +107,7 @@ final class TopNotificationBarFormBuilder
|
||||
|
||||
// Select: Show on Pages
|
||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'all');
|
||||
$html .= ' <div class="mb-0 mt-3">';
|
||||
$html .= ' <div class="mb-2 mt-3">';
|
||||
$html .= ' <label for="topBarShowOnPages" class="form-label small mb-1 fw-semibold" style="color: #495057;">';
|
||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
||||
$html .= ' Mostrar en';
|
||||
@@ -120,6 +120,20 @@ final class TopNotificationBarFormBuilder
|
||||
$html .= ' </select>';
|
||||
$html .= ' </div>';
|
||||
|
||||
// Switch: CSS Crítico
|
||||
$isCritical = $this->renderer->getFieldValue($componentId, 'visibility', 'is_critical', true);
|
||||
$html .= ' <div class="mb-0 mt-3">';
|
||||
$html .= ' <div class="form-check form-switch">';
|
||||
$html .= ' <input class="form-check-input" type="checkbox" id="topBarIsCritical" ';
|
||||
$html .= checked($isCritical, true, false) . '>';
|
||||
$html .= ' <label class="form-check-label small" for="topBarIsCritical" style="color: #495057;">';
|
||||
$html .= ' <i class="bi bi-lightning-charge me-1" style="color: #FF8600;"></i>';
|
||||
$html .= ' <strong>CSS Crítico</strong>';
|
||||
$html .= ' <small class="text-muted d-block">Inyectar CSS en <head> para optimizar LCP</small>';
|
||||
$html .= ' </label>';
|
||||
$html .= ' </div>';
|
||||
$html .= ' </div>';
|
||||
|
||||
$html .= ' </div>';
|
||||
$html .= '</div>';
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace ROITheme\Public\Hero\Infrastructure\Ui;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface;
|
||||
use ROITheme\Shared\Domain\Entities\Component;
|
||||
|
||||
/**
|
||||
@@ -28,8 +29,13 @@ use ROITheme\Shared\Domain\Entities\Component;
|
||||
*/
|
||||
final class HeroRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
||||
* @param CriticalCSSCollectorInterface $criticalCollector Colector de CSS crítico
|
||||
*/
|
||||
public function __construct(
|
||||
private CSSGeneratorInterface $cssGenerator
|
||||
private CSSGeneratorInterface $cssGenerator,
|
||||
private CriticalCSSCollectorInterface $criticalCollector
|
||||
) {}
|
||||
|
||||
public function render(Component $component): string
|
||||
@@ -47,6 +53,17 @@ final class HeroRenderer implements RendererInterface
|
||||
$css = $this->generateCSS($data);
|
||||
$html = $this->buildHTML($data);
|
||||
|
||||
// Verificar si el CSS debe ser crítico (inyectado en <head>)
|
||||
$isCritical = isset($data['visibility']['is_critical']) &&
|
||||
$data['visibility']['is_critical'] === true;
|
||||
|
||||
if ($isCritical) {
|
||||
// CSS crítico: agregar al collector para inyección en <head>
|
||||
$this->criticalCollector->add('hero', $css);
|
||||
return $html; // Solo HTML, CSS se inyecta en <head>
|
||||
}
|
||||
|
||||
// CSS no crítico: incluir inline con el componente
|
||||
return sprintf("<style>%s</style>\n%s", $css, $html);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\Navbar\Infrastructure\Ui;
|
||||
use ROITheme\Shared\Domain\Entities\Component;
|
||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface;
|
||||
use Walker_Nav_Menu;
|
||||
|
||||
/**
|
||||
@@ -30,9 +31,11 @@ final class NavbarRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
||||
* @param CriticalCSSCollectorInterface $criticalCollector Colector de CSS crítico
|
||||
*/
|
||||
public function __construct(
|
||||
private CSSGeneratorInterface $cssGenerator
|
||||
private CSSGeneratorInterface $cssGenerator,
|
||||
private CriticalCSSCollectorInterface $criticalCollector
|
||||
) {}
|
||||
|
||||
public function render(Component $component): string
|
||||
@@ -46,6 +49,17 @@ final class NavbarRenderer implements RendererInterface
|
||||
$css = $this->generateCSS($data);
|
||||
$html = $this->buildMenu($data);
|
||||
|
||||
// Verificar si el CSS debe ser crítico (inyectado en <head>)
|
||||
$isCritical = isset($data['visibility']['is_critical']) &&
|
||||
$data['visibility']['is_critical'] === true;
|
||||
|
||||
if ($isCritical) {
|
||||
// CSS crítico: agregar al collector para inyección en <head>
|
||||
$this->criticalCollector->add('navbar', $css);
|
||||
return $html; // Solo HTML, CSS se inyecta en <head>
|
||||
}
|
||||
|
||||
// CSS no crítico: incluir inline con el componente
|
||||
return sprintf(
|
||||
"<style>%s</style>\n%s",
|
||||
$css,
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace ROITheme\Public\TopNotificationBar\Infrastructure\Ui;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface;
|
||||
use ROITheme\Shared\Domain\Entities\Component;
|
||||
|
||||
/**
|
||||
@@ -36,9 +37,11 @@ final class TopNotificationBarRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
||||
* @param CriticalCSSCollectorInterface $criticalCollector Colector de CSS crítico
|
||||
*/
|
||||
public function __construct(
|
||||
private CSSGeneratorInterface $cssGenerator
|
||||
private CSSGeneratorInterface $cssGenerator,
|
||||
private CriticalCSSCollectorInterface $criticalCollector
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -64,7 +67,17 @@ final class TopNotificationBarRenderer implements RendererInterface
|
||||
// Generar HTML
|
||||
$html = $this->buildHTML($data);
|
||||
|
||||
// Combinar todo
|
||||
// Verificar si el CSS debe ser crítico (inyectado en <head>)
|
||||
$isCritical = isset($data['visibility']['is_critical']) &&
|
||||
$data['visibility']['is_critical'] === true;
|
||||
|
||||
if ($isCritical) {
|
||||
// CSS crítico: agregar al collector para inyección en <head>
|
||||
$this->criticalCollector->add('top-notification-bar', $css);
|
||||
return $html; // Solo HTML, CSS se inyecta en <head>
|
||||
}
|
||||
|
||||
// CSS no crítico: incluir inline con el componente
|
||||
return sprintf(
|
||||
"<style>%s</style>\n%s",
|
||||
$css,
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
"home": "Solo página de inicio"
|
||||
},
|
||||
"description": "Define en qué tipo de contenido se mostrará el hero"
|
||||
},
|
||||
"is_critical": {
|
||||
"type": "boolean",
|
||||
"label": "CSS Crítico",
|
||||
"default": true,
|
||||
"editable": true,
|
||||
"description": "Inyectar CSS inline en <head> para optimizar LCP (componente above-the-fold)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -48,6 +48,13 @@
|
||||
"default": true,
|
||||
"editable": true,
|
||||
"description": "Mantiene el navbar fijo en la parte superior al hacer scroll"
|
||||
},
|
||||
"is_critical": {
|
||||
"type": "boolean",
|
||||
"label": "CSS Crítico",
|
||||
"default": true,
|
||||
"editable": true,
|
||||
"description": "Inyectar CSS inline en <head> para optimizar LCP (componente above-the-fold)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
"editable": true,
|
||||
"required": true,
|
||||
"description": "Muestra la barra en dispositivos móviles (pantallas pequeñas)"
|
||||
},
|
||||
"is_critical": {
|
||||
"type": "boolean",
|
||||
"label": "CSS Crítico",
|
||||
"default": true,
|
||||
"editable": true,
|
||||
"description": "Inyectar CSS inline en <head> para optimizar LCP (componente above-the-fold)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
45
Shared/Domain/Contracts/CriticalCSSCollectorInterface.php
Normal file
45
Shared/Domain/Contracts/CriticalCSSCollectorInterface.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Domain\Contracts;
|
||||
|
||||
/**
|
||||
* Interface para recolectar CSS crítico de componentes above-the-fold
|
||||
*
|
||||
* RESPONSABILIDAD:
|
||||
* - Definir contrato para recolección de CSS crítico
|
||||
* - Permitir inyección en Renderers (DIP)
|
||||
*
|
||||
* UBICACIÓN: Domain (según 00.02 líneas 239-252)
|
||||
*
|
||||
* @package ROITheme\Shared\Domain\Contracts
|
||||
*/
|
||||
interface CriticalCSSCollectorInterface
|
||||
{
|
||||
/**
|
||||
* Agregar CSS de un componente a la colección crítica
|
||||
*
|
||||
* @param string $componentName Identificador del componente (kebab-case)
|
||||
* @param string $css CSS generado del componente
|
||||
*/
|
||||
public function add(string $componentName, string $css): void;
|
||||
|
||||
/**
|
||||
* Obtener todo el CSS crítico recolectado
|
||||
*
|
||||
* @return array<string, string> [componentName => css]
|
||||
*/
|
||||
public function getAll(): array;
|
||||
|
||||
/**
|
||||
* Renderizar CSS crítico como tag <style>
|
||||
*
|
||||
* @return string HTML del tag <style> o string vacío si no hay CSS
|
||||
*/
|
||||
public function render(): string;
|
||||
|
||||
/**
|
||||
* Limpiar colección (útil para tests)
|
||||
*/
|
||||
public function clear(): void;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use ROITheme\Shared\Domain\Contracts\ValidationServiceInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CacheServiceInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\ComponentSettingsRepositoryInterface;
|
||||
use ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface;
|
||||
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressComponentRepository;
|
||||
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressDefaultsRepository;
|
||||
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressComponentSettingsRepository;
|
||||
@@ -17,6 +18,7 @@ use ROITheme\Shared\Infrastructure\Services\WordPressCacheService;
|
||||
use ROITheme\Shared\Infrastructure\Services\SchemaSyncService;
|
||||
use ROITheme\Shared\Infrastructure\Services\CleanupService;
|
||||
use ROITheme\Shared\Infrastructure\Services\CSSGeneratorService;
|
||||
use ROITheme\Shared\Infrastructure\Services\CriticalCSSCollector;
|
||||
use ROITheme\Shared\Application\UseCases\GetComponentSettings\GetComponentSettingsUseCase;
|
||||
use ROITheme\Shared\Application\UseCases\SaveComponentSettings\SaveComponentSettingsUseCase;
|
||||
use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
|
||||
@@ -253,4 +255,21 @@ final class DIContainer
|
||||
|
||||
return $this->instances['adsensePlacementRenderer'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener colector de CSS crítico
|
||||
*
|
||||
* Lazy initialization: Crea la instancia solo en la primera llamada
|
||||
* IMPORTANTE: Singleton - misma instancia para todos los Renderers
|
||||
*
|
||||
* @return CriticalCSSCollectorInterface
|
||||
*/
|
||||
public function getCriticalCSSCollector(): CriticalCSSCollectorInterface
|
||||
{
|
||||
if (!isset($this->instances['criticalCSSCollector'])) {
|
||||
$this->instances['criticalCSSCollector'] = new CriticalCSSCollector();
|
||||
}
|
||||
|
||||
return $this->instances['criticalCSSCollector'];
|
||||
}
|
||||
}
|
||||
|
||||
67
Shared/Infrastructure/Services/CriticalCSSCollector.php
Normal file
67
Shared/Infrastructure/Services/CriticalCSSCollector.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface;
|
||||
|
||||
/**
|
||||
* Implementación del colector de CSS crítico
|
||||
*
|
||||
* RESPONSABILIDAD:
|
||||
* - Recolectar CSS de componentes above-the-fold
|
||||
* - Renderizar como <style> inline en <head>
|
||||
*
|
||||
* PATRÓN:
|
||||
* - Singleton via DIContainer (NO estático)
|
||||
* - Cumple DIP: Renderers reciben interface, no clase concreta
|
||||
*
|
||||
* UBICACIÓN: Infrastructure/Services (según 00.02)
|
||||
*
|
||||
* @package ROITheme\Shared\Infrastructure\Services
|
||||
*/
|
||||
final class CriticalCSSCollector implements CriticalCSSCollectorInterface
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $criticalStyles = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function add(string $componentName, string $css): void
|
||||
{
|
||||
$this->criticalStyles[$componentName] = $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->criticalStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function render(): string
|
||||
{
|
||||
if (empty($this->criticalStyles)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$css = implode("\n", $this->criticalStyles);
|
||||
return sprintf(
|
||||
'<style id="roi-critical-css">%s</style>',
|
||||
$css
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->criticalStyles = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Wordpress;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface;
|
||||
|
||||
/**
|
||||
* Registra hook wp_head para inyectar CSS crítico
|
||||
*
|
||||
* RESPONSABILIDAD:
|
||||
* - Registrar hook wp_head
|
||||
* - Delegar renderizado a CriticalCSSCollector
|
||||
*
|
||||
* PATRÓN:
|
||||
* - DIP: Recibe interface, no clase concreta
|
||||
* - SRP: Solo registra hook, no contiene lógica de CSS
|
||||
*
|
||||
* UBICACIÓN: Infrastructure/Wordpress (según 00.02 líneas 307-311)
|
||||
*
|
||||
* @package ROITheme\Shared\Infrastructure\Wordpress
|
||||
*/
|
||||
final class CriticalCSSHooksRegistrar
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CriticalCSSCollectorInterface $collector
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Registrar hooks de WordPress
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Priority 1 = muy temprano en <head>, antes de otros estilos
|
||||
add_action('wp_head', [$this, 'renderCriticalCSS'], 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback para wp_head
|
||||
*/
|
||||
public function renderCriticalCSS(): void
|
||||
{
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $this->collector->render();
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,28 @@ function roi_get_navbar_setting(string $group, string $attribute, $default = nul
|
||||
return $value;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CRITICAL CSS COLLECTOR SINGLETON
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Obtiene la instancia singleton del CriticalCSSCollector
|
||||
*
|
||||
* Patrón Singleton implementado via función para mantener una única instancia
|
||||
* que será compartida por todos los Renderers y el HooksRegistrar
|
||||
*
|
||||
* @return \ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface
|
||||
*/
|
||||
function roi_get_critical_css_collector(): \ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface {
|
||||
static $collector = null;
|
||||
|
||||
if ($collector === null) {
|
||||
$collector = new \ROITheme\Shared\Infrastructure\Services\CriticalCSSCollector();
|
||||
}
|
||||
|
||||
return $collector;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTION: roi_render_component()
|
||||
// =============================================================================
|
||||
@@ -186,22 +208,27 @@ function roi_render_component(string $componentName): string {
|
||||
// Crear instancia del CSSGeneratorService (reutilizable para todos los renderers que lo necesiten)
|
||||
$cssGenerator = new \ROITheme\Shared\Infrastructure\Services\CSSGeneratorService();
|
||||
|
||||
// Obtener instancia singleton del CriticalCSSCollector
|
||||
$criticalCollector = roi_get_critical_css_collector();
|
||||
|
||||
switch ($componentName) {
|
||||
// Componentes nuevos (namespace PascalCase correcto)
|
||||
// Componentes con soporte de CSS Crítico (above-the-fold)
|
||||
case 'top-notification-bar':
|
||||
$renderer = new \ROITheme\Public\TopNotificationBar\Infrastructure\Ui\TopNotificationBarRenderer($cssGenerator);
|
||||
$renderer = new \ROITheme\Public\TopNotificationBar\Infrastructure\Ui\TopNotificationBarRenderer($cssGenerator, $criticalCollector);
|
||||
break;
|
||||
case 'navbar':
|
||||
$renderer = new \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer($cssGenerator);
|
||||
break;
|
||||
case 'cta-lets-talk':
|
||||
$renderer = new \ROITheme\Public\CtaLetsTalk\Infrastructure\Ui\CtaLetsTalkRenderer($cssGenerator);
|
||||
$renderer = new \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer($cssGenerator, $criticalCollector);
|
||||
break;
|
||||
case 'hero':
|
||||
error_log("ROI Theme DEBUG: Creating HeroRenderer");
|
||||
$renderer = new \ROITheme\Public\Hero\Infrastructure\Ui\HeroRenderer($cssGenerator);
|
||||
$renderer = new \ROITheme\Public\Hero\Infrastructure\Ui\HeroRenderer($cssGenerator, $criticalCollector);
|
||||
error_log("ROI Theme DEBUG: HeroRenderer created successfully");
|
||||
break;
|
||||
|
||||
// Componentes sin soporte de CSS Crítico (below-the-fold)
|
||||
case 'cta-lets-talk':
|
||||
$renderer = new \ROITheme\Public\CtaLetsTalk\Infrastructure\Ui\CtaLetsTalkRenderer($cssGenerator);
|
||||
break;
|
||||
case 'hero-section':
|
||||
$renderer = new \ROITheme\Public\HeroSection\Infrastructure\Ui\HeroSectionRenderer();
|
||||
break;
|
||||
@@ -250,9 +277,21 @@ function roi_render_component(string $componentName): string {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ESTILOS BASE PARA TOP NOTIFICATION BAR
|
||||
// REGISTRO DE CRITICAL CSS HOOKS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Registra el hook para inyectar CSS crítico en <head>
|
||||
*
|
||||
* IMPORTANTE: El HooksRegistrar usa la misma instancia singleton del collector
|
||||
* que usan los Renderers, garantizando que el CSS recolectado se inyecte
|
||||
* correctamente en wp_head con prioridad 1 (muy temprano).
|
||||
*/
|
||||
add_action('after_setup_theme', function() {
|
||||
$criticalCollector = roi_get_critical_css_collector();
|
||||
$hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar($criticalCollector);
|
||||
$hooksRegistrar->register();
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// NOTA: Los estilos de TOC y CTA Box Sidebar se generan dinámicamente
|
||||
|
||||
Reference in New Issue
Block a user