Skip to content

Commit

Permalink
BB-24798: Added support for Critical CSS in layout (#40191)
Browse files Browse the repository at this point in the history
- added new assets group "critical_css"
- added PublicDirectoryProvider for handling public directory paths
- added ThemeProvider::getStylesOutputContent to dynamically inject Critical CSS content
- added critical CSS to GrapesJS editor
  • Loading branch information
kotliarmikhail authored Jan 24, 2025
1 parent 843e639 commit 31b3252
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Oro\Bundle\DistributionBundle\Provider;

/**
* Provides the path to the public directory of the application.
*/
class PublicDirectoryProvider
{
public function __construct(private readonly string $projectDir)
{
}

public function getPublicDirectory(): string
{
$defaultPublicDir = 'public';
$composerFilePath = $this->projectDir . DIRECTORY_SEPARATOR . 'composer.json';

if (!file_exists($composerFilePath)) {
return $this->projectDir . DIRECTORY_SEPARATOR . $defaultPublicDir;
}

$composerConfig = json_decode(file_get_contents($composerFilePath), true);

if (json_last_error() !== JSON_ERROR_NONE) {
return $this->projectDir . DIRECTORY_SEPARATOR . $defaultPublicDir;
}

return $this->projectDir . DIRECTORY_SEPARATOR . ($composerConfig['extra']['public-dir'] ?? $defaultPublicDir);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,9 @@ services:
class: Oro\Bundle\DistributionBundle\EventListener\ControllerTemplateListener
tags:
- { name: kernel.event_subscriber }

oro_distribution.provider.public_directory_provider:
class: Oro\Bundle\DistributionBundle\Provider\PublicDirectoryProvider
arguments:
- '%kernel.project_dir%'

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Oro\Bundle\DistributionBundle\Tests\Unit\Provider;

use Oro\Bundle\DistributionBundle\Provider\PublicDirectoryProvider;
use PHPUnit\Framework\TestCase;

class PublicDirectoryProviderTest extends TestCase
{
private string $projectDir;

protected function setUp(): void
{
$this->projectDir = sys_get_temp_dir() . '/test_project';
if (!is_dir($this->projectDir)) {
mkdir($this->projectDir, 0777, true);
}
}

protected function tearDown(): void
{
$this->removeDirectory($this->projectDir);
}

public function testGetPublicDirectoryDefault(): void
{
$provider = new PublicDirectoryProvider($this->projectDir);

self::assertSame($this->projectDir . '/public', $provider->getPublicDirectory());
}

public function testGetPublicDirectoryFromComposerConfig(): void
{
$composerJsonPath = $this->projectDir . '/composer.json';

file_put_contents($composerJsonPath, json_encode(['extra' => ['public-dir' => 'custom_public']]));

$provider = new PublicDirectoryProvider($this->projectDir);

self::assertSame($this->projectDir . '/custom_public', $provider->getPublicDirectory());
}

public function testGetPublicDirectoryInvalidJson(): void
{
$composerJsonPath = $this->projectDir . '/composer.json';

file_put_contents($composerJsonPath, '{invalid json}');

$provider = new PublicDirectoryProvider($this->projectDir);

self::assertSame($this->projectDir . '/public', $provider->getPublicDirectory());
}

public function testGetPublicDirectoryWhenComposerFileDoesNotExist(): void
{
$provider = new PublicDirectoryProvider($this->projectDir);

self::assertSame($this->projectDir . '/public', $provider->getPublicDirectory());
}

private function removeDirectory(string $directory): void
{
if (!is_dir($directory)) {
return;
}

$files = array_diff(scandir($directory), ['.', '..']);
foreach ($files as $file) {
$filePath = $directory . '/' . $file;
is_dir($filePath) ? $this->removeDirectory($filePath) : unlink($filePath);
}
rmdir($directory);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
critical_css:
inputs:
- 'bundles/oroform/default/scss/settings/global-settings.scss'

styles:
inputs:
- 'bundles/oroform/default/scss/settings/global-settings.scss'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,12 @@ services:
arguments:
- '@oro_layout.theme_manager'
- '@oro_locale.provider.current_localization'
- '@oro_distribution.provider.public_directory_provider'
calls:
- ['setLogger', ['@logger']]
tags:
- { name: layout.data_provider, alias: theme }
- { name: monolog.logger, channel: oro_layout }

oro_layout.expression_language.compiled_cache_warmer:
class: Oro\Component\Layout\ExpressionLanguage\ExpressionLanguageCacheWarmer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
critical_css:
inputs:
- 'bundles/oroui/default/scss/settings/global-settings.scss'

styles:
inputs:
- 'bundles/oroui/default/scss/settings/global-settings.scss'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ layout:
visible: '=data["theme"].getIcon(context["theme"])!=null'
href: '=data["asset"].getUrl(data["theme"].getIcon(context["theme"]))'
rel: shortcut icon
critical_css:
blockType: style
options:
content: '=data["theme"].getStylesOutputContent(context["theme"], "critical_css")'
styles:
blockType: style
options:
Expand Down Expand Up @@ -73,6 +77,7 @@ layout:
meta_charset: ~
meta_viewport: ~
theme_icon: ~
critical_css: ~
service_worker: ~
styles: ~
print_styles: ~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@

namespace Oro\Component\Layout\Extension\Theme\DataProvider;

use Oro\Bundle\DistributionBundle\Provider\PublicDirectoryProvider;
use Oro\Bundle\LocaleBundle\Provider\LocalizationProviderInterface;
use Oro\Component\Layout\Extension\Theme\Model\Theme;
use Oro\Component\Layout\Extension\Theme\Model\ThemeManager;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

/**
* Provides theme icon and path to css files in theme by passed styles entry point
*/
class ThemeProvider
class ThemeProvider implements LoggerAwareInterface
{
protected ThemeManager $themeManager;
use LoggerAwareTrait;

protected LocalizationProviderInterface $localizationProvider;
/** @var array<string, Theme> */
private array $themes = [];

/** @var Theme[] */
protected $themes = [];

public function __construct(ThemeManager $themeManager, LocalizationProviderInterface $localizationProvider)
{
$this->themeManager = $themeManager;
$this->localizationProvider = $localizationProvider;
public function __construct(
private readonly ThemeManager $themeManager,
private readonly LocalizationProviderInterface $localizationProvider,
private readonly PublicDirectoryProvider $publicDirectoryProvider,
) {
$this->logger = new NullLogger();
}

/**
Expand Down Expand Up @@ -110,4 +114,26 @@ private function getOutputPath(string $themeName, string $sectionName): ?string

return sprintf('%s.rtl%s', $matches['path'], $matches['extension'] ?? '');
}

public function getStylesOutputContent(string $themeName, string $sectionName): string
{
$outputPath = $this->getStylesOutput($themeName, $sectionName);

if ($outputPath === null) {
return '';
}

$filePath = sprintf('%s/%s', $this->publicDirectoryProvider->getPublicDirectory(), $outputPath);

if (!is_file($filePath) || !is_readable($filePath)) {
$this->logger->error(
'CSS file not found: {filePath}. Theme: "{themeName}", Section: "{sectionName}". ' .
'Ensure the file exists and is readable.',
['filePath' => $filePath, 'themeName' => $themeName, 'sectionName' => $sectionName]
);
return '';
}

return file_get_contents($filePath);
}
}
Loading

0 comments on commit 31b3252

Please sign in to comment.