Merge branch 'release/2.3.0'

This commit is contained in:
Sebastian Fischer 2025-04-26 20:04:13 +02:00
commit 46810c675e
2 changed files with 173 additions and 22 deletions

View File

@ -16,6 +16,7 @@ declare(strict_types=1);
namespace Evoweb\EwBase\EventListener; namespace Evoweb\EwBase\EventListener;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Evoweb\EwBase\Services\Tailwindcss4Merger;
use TYPO3\CMS\Core\Core\RequestId; use TYPO3\CMS\Core\Core\RequestId;
use TYPO3\CMS\Core\Page\AssetCollector; use TYPO3\CMS\Core\Page\AssetCollector;
use TYPO3\CMS\Core\Page\Event\BeforeStylesheetsRenderingEvent; use TYPO3\CMS\Core\Page\Event\BeforeStylesheetsRenderingEvent;
@ -26,7 +27,8 @@ use TYPO3\CMS\Core\Utility\PathUtility;
readonly class CssMerger readonly class CssMerger
{ {
public function __construct( public function __construct(
private RequestId $requestId private RequestId $requestId,
protected Tailwindcss4Merger $tailwindcss4Merger
) { ) {
} }
@ -34,6 +36,9 @@ readonly class CssMerger
{ {
if ($event->isInline() && $event->isPriority()) { if ($event->isInline() && $event->isPriority()) {
$assetCollector = $event->getAssetCollector(); $assetCollector = $event->getAssetCollector();
if (empty($assetCollector->getStyleSheets()) && empty($assetCollector->getInlineStyleSheets())) {
return;
}
$styles = []; $styles = [];
$styles = $this->getStylesheetFileContent($assetCollector, $styles); $styles = $this->getStylesheetFileContent($assetCollector, $styles);
@ -41,18 +46,9 @@ readonly class CssMerger
$styles = array_unique($styles); $styles = array_unique($styles);
$styles = trim(implode(LF, $styles)); $styles = trim(implode(LF, $styles));
if (empty($styles)) { $styles = $this->tailwindcss4Merger->cssUnique($styles);
return;
}
/** @var Site $site */ if ($this->assertStylesShouldBeAddedInline()) {
$site = $this->getRequest()->getAttribute('site');
if (
$site instanceof Site
&& ($siteSettings = $site->getSettings())
&& !$siteSettings->isEmpty()
&& $siteSettings->get('ew-base.inlineCssStyles')
) {
$assetCollector->addInlineStyleSheet('ew_base', $styles, [], ['priority' => true]); $assetCollector->addInlineStyleSheet('ew_base', $styles, [], ['priority' => true]);
} else { } else {
$temporaryFile = GeneralUtility::writeStyleSheetContentToTemporaryFile($styles); $temporaryFile = GeneralUtility::writeStyleSheetContentToTemporaryFile($styles);
@ -87,16 +83,6 @@ readonly class CssMerger
return $styles; return $styles;
} }
protected function getStylesheetInlineContent(AssetCollector $assetCollector, array $styles): array
{
$styleSheetsInline = $assetCollector->getInlineStyleSheets(false);
foreach ($styleSheetsInline as $identifier => $asset) {
$styles[] = $asset['source'];
$assetCollector->removeInlineStyleSheet($identifier);
}
return $styles;
}
protected function getFileContent(string $filePath): string protected function getFileContent(string $filePath): string
{ {
$absoluteFilePath = GeneralUtility::getFileAbsFileName($filePath); $absoluteFilePath = GeneralUtility::getFileAbsFileName($filePath);
@ -124,6 +110,24 @@ readonly class CssMerger
); );
} }
protected function getStylesheetInlineContent(AssetCollector $assetCollector, array $styles): array
{
$styleSheetsInline = $assetCollector->getInlineStyleSheets(false);
foreach ($styleSheetsInline as $identifier => $asset) {
$styles[] = $asset['source'];
$assetCollector->removeInlineStyleSheet($identifier);
}
return $styles;
}
protected function assertStylesShouldBeAddedInline(): bool
{
/** @var Site $site */
$site = $this->getRequest()->getAttribute('site');
return $site instanceof Site
&& $site->getSettings()?->get('ew-base.inlineCssStyles');
}
protected function getRequest(): ServerRequestInterface protected function getRequest(): ServerRequestInterface
{ {
return $GLOBALS['TYPO3_REQUEST']; return $GLOBALS['TYPO3_REQUEST'];

View File

@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\Services;
class Tailwindcss4Merger
{
public function cssUnique(string $styles): string
{
$atLayerOrder = $this->getLayerOrder($styles);
$atLayerBase = $this->getLayerBase($styles);
$atLayerTheme = $this->getLayerTheme($styles);
$atLayerUtilities = $this->getLayerUtilities($styles);
$atLayerComponents = $this->getLayerComponents($styles);
$atProperty = $this->getProperties($styles);
$atLayerProperties = $this->getLayerProperties($styles);
return implode(chr(10), [
$atLayerOrder,
$atLayerBase,
$atLayerTheme,
$atLayerUtilities,
$atLayerComponents,
$atProperty,
$atLayerProperties,
]);
}
protected function getLayerOrder(string $styles): string
{
preg_match_all('/(?<layer>@layer[^;{]+;)/i', $styles, $matches);
return implode(chr(10), array_unique($matches['layer'] ?? []));
}
protected function getLayerBase(string $styles): string
{
$matches = $this->getLayerByName($styles, 'base');
$base = [];
foreach ($matches as $match) {
$match = $this->unwrapLayer($match, 'base');
$match = preg_replace('/}\n *\./', "}\n||.", $match);
$match = preg_replace('/}\n\W*@/', "}\n||@", $match);
$parts = explode('||', $match);
$base = array_merge($base, $parts);
}
return $this->wrapLayer('base', array_unique($base));
}
protected function getLayerTheme(string $styles): string
{
$matches = $this->getLayerByName($styles, 'theme');
$themes = [];
foreach ($matches as $match) {
preg_match_all('/(?<theme>--[^;]+;)/i', $match, $subMatches);
$themes = array_merge($themes, $subMatches['theme'] ?? []);
}
return '@layer theme {
:root, :host {'
. chr(10)
. implode(chr(10), array_unique($themes))
. chr(10)
. ' }
}';
}
protected function getLayerUtilities(string $styles): string
{
$matches = $this->getLayerByName($styles, 'utilities');
$utilities = [];
foreach ($matches as $match) {
$match = $this->unwrapLayer($match, 'utilities');
$match = preg_replace('/}\n *\./', "}\n||.", $match);
$parts = explode('||', $match);
$utilities = array_merge($utilities, $parts);
}
return $this->wrapLayer('utilities', array_unique($utilities));
}
protected function getLayerComponents(string $styles): string
{
$matches = $this->getLayerByName($styles, 'components');
$components = [];
foreach ($matches as $match) {
$match = $this->unwrapLayer($match, 'components');
$match = preg_replace('/}\n *\./', "}\n||.", $match);
$parts = explode('||', $match);
$components = array_merge($components, $parts);
}
return $this->wrapLayer('components', array_unique($components));
}
protected function getLayerByName(string $styles, string $name): array
{
preg_match_all('/@layer[^{;]+\{.+?(?=\n})\n}/s', $styles, $matches);
$layer = [];
foreach ($matches['0'] as $match) {
if (str_starts_with($match, '@layer ' . $name) !== false) {
$layer[] = $match;
}
}
return $layer;
}
protected function getProperties(string $styles): string
{
preg_match_all('/(?<property>@property[^{]+\{[^}]+})/i', $styles, $matches);
return implode(chr(10), array_unique($matches['property'] ?? []));
}
protected function getLayerProperties(string $styles): string
{
$matches = $this->getLayerByName($styles, 'properties');
$properties = [
' @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {'
];
foreach ($matches as $match) {
preg_match_all('/(?<properties>--tw[^ ]+: [^;]+;)/i', $match, $subMatches);
$properties = array_merge($properties, $subMatches['properties']);
}
$properties = array_unique($properties);
$properties[] = ' }
}';
return $this->wrapLayer('properties', $properties);
}
protected function unwrapLayer(string $styles, string $name): string
{
return trim(str_replace('@layer ' . $name . ' {', '', substr($styles, 0, -1)));
}
protected function wrapLayer(string $name, array $styles): string
{
return '@layer ' . $name . " {\n" . implode("\n", $styles) . "\n}";
}
}