perf: defer Bootstrap CSS with critical subset inline
- Created Assets/css/critical-bootstrap.css (~10KB subset) Contains only Bootstrap classes used in above-the-fold components: container, navbar, flexbox, dropdown, spacing utilities - Created CriticalBootstrapService (singleton) Injects minified critical Bootstrap in <head> at priority 0 Output: <style id="roi-critical-bootstrap">...</style> - Modified enqueue-scripts.php Bootstrap now loads with media="print" + onload="this.media='all'" Full 31KB Bootstrap loads async, doesn't block rendering - Updated CriticalCSSHooksRegistrar Now registers both CriticalBootstrapService (priority 0) and CriticalCSSService (priority 1) Flow: 1. wp_head (priority 0) → Critical Bootstrap (~10KB inline) 2. wp_head (priority 1) → Critical Component CSS (~4KB inline) 3. Bootstrap full (31KB) loads deferred, non-blocking Expected PageSpeed improvement: ~400-600ms LCP reduction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
148
Shared/Infrastructure/Services/CriticalBootstrapService.php
Normal file
148
Shared/Infrastructure/Services/CriticalBootstrapService.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Services;
|
||||
|
||||
/**
|
||||
* CriticalBootstrapService
|
||||
*
|
||||
* Inyecta CSS crítico de Bootstrap en wp_head para evitar
|
||||
* bloqueo de renderizado por el Bootstrap completo.
|
||||
*
|
||||
* RESPONSABILIDAD:
|
||||
* - Leer critical-bootstrap.css (subset de Bootstrap)
|
||||
* - Inyectar en <head> 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: <style id="roi-critical-bootstrap">...</style>
|
||||
*
|
||||
* 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(
|
||||
'<style id="roi-critical-bootstrap">%s</style>' . "\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);
|
||||
}
|
||||
}
|
||||
@@ -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 <head>
|
||||
* 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: <style id="roi-critical-bootstrap">...</style>
|
||||
*
|
||||
* 2. wp_head (priority 1) → CriticalCSSService::render()
|
||||
* - Consulta BD por componentes is_critical=true
|
||||
* - Genera CSS usando Renderers
|
||||
* - Output: <style id="roi-critical-css">...</style>
|
||||
*
|
||||
* 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 <head>, 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: <style id="roi-critical-bootstrap">...</style>
|
||||
*/
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user