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:
FrankZamora
2025-12-01 23:06:12 -06:00
parent e1923b630d
commit e01605ec37
15 changed files with 961 additions and 17 deletions

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace ROITheme\Public\CriticalCSS\Infrastructure\Cache;
use ROITheme\Public\CriticalCSS\Domain\Contracts\CriticalCSSCacheInterface;
/**
* Cache de CSS critico en filesystem con auto-invalidacion
*
* Almacena CSS critico generado en archivos .critical.css
* dentro de Assets/CriticalCSS/
*
* @package ROITheme\Public\CriticalCSS\Infrastructure\Cache
*/
final class CriticalCSSFileCache implements CriticalCSSCacheInterface
{
private readonly string $cacheDir;
public function __construct()
{
$this->cacheDir = get_template_directory() . '/Assets/CriticalCSS';
}
/**
* {@inheritDoc}
*/
public function get(string $key): ?string
{
$file = $this->getFilePath($key);
if (!file_exists($file)) {
return null;
}
$content = file_get_contents($file);
return $content !== false ? $content : null;
}
/**
* {@inheritDoc}
*/
public function set(string $key, string $css): bool
{
$this->ensureDirectoryExists();
$file = $this->getFilePath($key);
return file_put_contents($file, $css) !== false;
}
/**
* {@inheritDoc}
*/
public function has(string $key): bool
{
return file_exists($this->getFilePath($key));
}
/**
* {@inheritDoc}
*/
public function clear(): void
{
$files = glob($this->cacheDir . '/*.critical.css');
if ($files === false) {
return;
}
foreach ($files as $file) {
unlink($file);
}
}
/**
* {@inheritDoc}
*
* Compara timestamps para detectar cache desactualizado.
* Costo: ~0.1ms (2 llamadas a filemtime)
*/
public function isStale(string $key, string $sourceFile): bool
{
$cacheFile = $this->getFilePath($key);
// Si no existe cache, esta desactualizado
if (!file_exists($cacheFile)) {
return true;
}
// Si no existe fuente, no esta desactualizado (nada que regenerar)
if (!file_exists($sourceFile)) {
return false;
}
// Comparar timestamps
return filemtime($sourceFile) > filemtime($cacheFile);
}
/**
* Genera ruta del archivo cache
*/
private function getFilePath(string $key): string
{
return $this->cacheDir . '/' . $key . '.critical.css';
}
/**
* Asegura que el directorio de cache existe
*/
private function ensureDirectoryExists(): void
{
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
}