feat(critical-css): implementar TIPO 4 y TIPO 5 - CSS Below-the-fold y Lazy Loading
## TIPO 4: CSS Below-the-fold (Critical Variables + Responsive) - Inyecta variables CSS críticas inline en wp_head P:-1 - Inyecta media queries críticas inline en wp_head P:2 (corregido de P:1) - Auto-regeneración cuando archivos fuente cambian (filemtime check) - Cache en Assets/CriticalCSS/ para evitar lecturas repetidas - Comando WP-CLI: wp roi-theme generate-critical-css Archivos TIPO 4: - Public/CriticalCSS/Domain/Contracts/ - Interfaces (DIP) - Public/CriticalCSS/Application/UseCases/GetCriticalCSSUseCase.php - Public/CriticalCSS/Infrastructure/Cache/CriticalCSSFileCache.php - Public/CriticalCSS/Infrastructure/Services/CriticalCSSExtractor.php - Public/CriticalCSS/Infrastructure/Services/CriticalCSSInjector.php - bin/generate-critical-css.php ## TIPO 5: CSS No Crítico (Lazy Loading) - Animaciones CSS: carga 2s después de page load via requestIdleCallback - Print CSS: carga solo al imprimir via beforeprint event - Fallback <noscript> para usuarios sin JavaScript - Safari fallback: setTimeout cuando requestIdleCallback no disponible Archivos TIPO 5: - Assets/Js/lazy-css-loader.js - Public/LazyCSSLoader/Infrastructure/Contracts/LazyCSSRegistrarInterface.php - Public/LazyCSSLoader/Infrastructure/Services/LazyCSSRegistrar.php ## Fix: Colisión de prioridades wp_head Antes: TIPO 1 (P:1), TIPO 4 responsive (P:1), TIPO 3 (P:2) - CONFLICTO Después: TIPO 1 (P:1), TIPO 4 responsive (P:2), TIPO 3 (P:3) - OK Nuevo orden de prioridades: P:-1 roi-critical-variables (TIPO 4) P:0 roi-critical-bootstrap (TIPO 2) P:1 roi-critical-css (TIPO 1) P:2 roi-critical-responsive (TIPO 4) P:3 roi-custom-critical-css (TIPO 3) P:5 roi-theme-layout-css (ThemeSettings) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\CriticalCSS\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Public\CriticalCSS\Domain\Contracts\CriticalCSSCacheInterface;
|
||||
use ROITheme\Public\CriticalCSS\Domain\Contracts\CriticalCSSExtractorInterface;
|
||||
|
||||
/**
|
||||
* Extrae CSS critico de archivos fuente
|
||||
*
|
||||
* Estrategia simple:
|
||||
* - Variables: Todo el archivo (siempre critico)
|
||||
* - Responsive: Solo breakpoints mobile-first criticos
|
||||
*
|
||||
* @package ROITheme\Public\CriticalCSS\Infrastructure\Services
|
||||
*/
|
||||
final class CriticalCSSExtractor implements CriticalCSSExtractorInterface
|
||||
{
|
||||
// Archivos fuente
|
||||
private const SOURCE_VARIABLES = '/Assets/Css/css-global-variables.css';
|
||||
private const SOURCE_RESPONSIVE = '/Assets/Css/css-global-responsive.css';
|
||||
|
||||
// Breakpoints criticos (mobile-first)
|
||||
// NOTA: El regex maneja 1 nivel de anidamiento.
|
||||
// Si el CSS tiene @supports dentro de @media, no se capturara correctamente.
|
||||
// Esto es aceptable para css-global-responsive.css que es simple.
|
||||
private const CRITICAL_BREAKPOINTS = [
|
||||
'@media (max-width: 575.98px)',
|
||||
'@media (min-width: 576px)',
|
||||
'@media (max-width: 767.98px)',
|
||||
'@media (min-width: 768px)',
|
||||
'@media (min-width: 992px)',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly CriticalCSSCacheInterface $cache
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extractVariables(): string
|
||||
{
|
||||
$sourceFile = get_template_directory() . self::SOURCE_VARIABLES;
|
||||
|
||||
if (!file_exists($sourceFile)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$css = file_get_contents($sourceFile);
|
||||
|
||||
if ($css === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Para variables, todo el contenido es critico
|
||||
return $this->minify($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extractResponsive(): string
|
||||
{
|
||||
$sourceFile = get_template_directory() . self::SOURCE_RESPONSIVE;
|
||||
|
||||
if (!file_exists($sourceFile)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$css = file_get_contents($sourceFile);
|
||||
|
||||
if ($css === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$criticalCSS = '';
|
||||
|
||||
// Extraer solo media queries criticas
|
||||
// Regex: captura @media (...) { contenido con 1 nivel de {} }
|
||||
foreach (self::CRITICAL_BREAKPOINTS as $breakpoint) {
|
||||
$pattern = '/' . preg_quote($breakpoint, '/') . '\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/s';
|
||||
|
||||
if (preg_match_all($pattern, $css, $matches)) {
|
||||
foreach ($matches[0] as $match) {
|
||||
$criticalCSS .= $match . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->minify($criticalCSS);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function generateAll(): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
// Variables CSS
|
||||
$variablesCSS = $this->extractVariables();
|
||||
if (!empty($variablesCSS)) {
|
||||
$this->cache->set('variables', $variablesCSS);
|
||||
$results['variables'] = strlen($variablesCSS);
|
||||
}
|
||||
|
||||
// Responsive critico
|
||||
$responsiveCSS = $this->extractResponsive();
|
||||
if (!empty($responsiveCSS)) {
|
||||
$this->cache->set('responsive', $responsiveCSS);
|
||||
$results['responsive'] = strlen($responsiveCSS);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function needsRegeneration(): bool
|
||||
{
|
||||
$templateDir = get_template_directory();
|
||||
|
||||
return $this->cache->isStale('variables', $templateDir . self::SOURCE_VARIABLES)
|
||||
|| $this->cache->isStale('responsive', $templateDir . self::SOURCE_RESPONSIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minifica CSS eliminando espacios y comentarios innecesarios
|
||||
*/
|
||||
private function minify(string $css): string
|
||||
{
|
||||
// Eliminar comentarios
|
||||
$css = preg_replace('/\/\*[\s\S]*?\*\//', '', $css) ?? $css;
|
||||
|
||||
// Eliminar espacios innecesarios
|
||||
$css = preg_replace('/\s+/', ' ', $css) ?? $css;
|
||||
$css = preg_replace('/\s*([{};:,>+~])\s*/', '$1', $css) ?? $css;
|
||||
|
||||
// Eliminar ultimo punto y coma antes de }
|
||||
$css = preg_replace('/;}/', '}', $css) ?? $css;
|
||||
|
||||
return trim($css);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Public\CriticalCSS\Infrastructure\Services;
|
||||
|
||||
use ROITheme\Public\CriticalCSS\Domain\Contracts\CriticalCSSCacheInterface;
|
||||
|
||||
/**
|
||||
* Inyecta CSS critico en wp_head
|
||||
*
|
||||
* Prioridades:
|
||||
* - P:-1 Variables CSS (antes de todo)
|
||||
* - P:1 Responsive critico (despues de Bootstrap critico)
|
||||
*
|
||||
* @package ROITheme\Public\CriticalCSS\Infrastructure\Services
|
||||
*/
|
||||
final class CriticalCSSInjector
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CriticalCSSCacheInterface $cache
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Registra hooks de WordPress
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Variables CSS: P:-1 (antes de CriticalBootstrapService P:0)
|
||||
add_action('wp_head', [$this, 'injectVariables'], -1);
|
||||
|
||||
// Responsive critico: P:2 (despues de CriticalCSSService P:1)
|
||||
// NOTA: Cambiado de P:1 a P:2 para evitar colision con roi-critical-css
|
||||
add_action('wp_head', [$this, 'injectResponsive'], 2);
|
||||
|
||||
// Deshabilitar enqueue de archivos que ahora son inline
|
||||
add_action('wp_enqueue_scripts', [$this, 'dequeueInlinedCSS'], 999);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inyecta variables CSS criticas
|
||||
*/
|
||||
public function injectVariables(): void
|
||||
{
|
||||
$css = $this->cache->get('variables');
|
||||
|
||||
if (empty($css)) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<!-- TIPO 4: Variables CSS criticas -->' . "\n" .
|
||||
'<style id="roi-critical-variables">%s</style>' . "\n",
|
||||
$css
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inyecta media queries criticas
|
||||
*/
|
||||
public function injectResponsive(): void
|
||||
{
|
||||
$css = $this->cache->get('responsive');
|
||||
|
||||
if (empty($css)) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<!-- TIPO 4: Responsive critico -->' . "\n" .
|
||||
'<style id="roi-critical-responsive">%s</style>' . "\n",
|
||||
$css
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deshabilita enqueue de archivos que ahora estan inline
|
||||
*/
|
||||
public function dequeueInlinedCSS(): void
|
||||
{
|
||||
// Variables ya inline - no cargar archivo externo
|
||||
if ($this->cache->has('variables')) {
|
||||
wp_dequeue_style('roi-variables');
|
||||
wp_deregister_style('roi-variables');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user