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:
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user