Files
roi-theme/Shared/Infrastructure/Services/CriticalBootstrapService.php
FrankZamora d5a2fd2702 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>
2025-11-29 10:26:24 -06:00

149 lines
3.8 KiB
PHP

<?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);
}
}