diff --git a/composer.json b/composer.json
index 0b64664..84e0b20 100644
--- a/composer.json
+++ b/composer.json
@@ -1,13 +1,11 @@
{
- "name": "m2-boilerplate/module-critical-css",
+ "name": "studioraz/magento2-critical-css",
"description": "Magento 2 module to automatically generate critical css with the addyosmani/critical npm package",
+ "type": "magento2-module",
+ "prefer-stable": true,
+ "version": "2.1.0",
"keywords": [
],
- "require": {
- "magento/framework": "^102.0|^103.0",
- "php": ">=7.1.0"
- },
- "type": "magento2-module",
"license": [
"MIT"
],
diff --git a/src/Config/Config.php b/src/Config/Config.php
index a7864d7..fd230bd 100644
--- a/src/Config/Config.php
+++ b/src/Config/Config.php
@@ -14,6 +14,7 @@ class Config
const CONFIG_PATH_USERNAME = 'dev/css/critical_css_username';
const CONFIG_PATH_PASSWORD = 'dev/css/critical_css_password';
const CONFIG_PATH_DIMENSIONS = 'dev/css/critical_css_dimensions';
+ const CONFIG_PATH_FORCE_INCLUDE_CSS_SELECTORS = 'dev/css/critical_css_force_include_css_selectors';
/**
* @var ScopeConfigInterface
@@ -38,6 +39,16 @@ public function isEnabled(): bool
return (bool) $this->scopeConfig->isSetFlag(self::CONFIG_PATH_ENABLED);
}
+ public function getForceIncludeCssSelectors(): array
+ {
+ $cssSelectors = $this->scopeConfig->getValue(self::CONFIG_PATH_FORCE_INCLUDE_CSS_SELECTORS);
+ $cssSelectors = explode(',', $cssSelectors);
+ $cssSelectors = array_map('trim', $cssSelectors);
+ $cssSelectors = array_filter($cssSelectors);
+
+ return $cssSelectors;
+ }
+
public function getDimensions(): array
{
$dimensions = $this->scopeConfig->getValue(self::CONFIG_PATH_DIMENSIONS);
@@ -78,4 +89,4 @@ public function getCriticalBinary(): string
{
return $this->scopeConfig->getValue(self::CONFIG_PATH_CRITICAL_BINARY);
}
-}
\ No newline at end of file
+}
diff --git a/src/Console/Command/GenerateCommand.php b/src/Console/Command/GenerateCommand.php
index 9ee08c7..866f78e 100644
--- a/src/Console/Command/GenerateCommand.php
+++ b/src/Console/Command/GenerateCommand.php
@@ -7,17 +7,20 @@
use M2Boilerplate\CriticalCss\Service\CriticalCss;
use M2Boilerplate\CriticalCss\Service\ProcessManager;
use M2Boilerplate\CriticalCss\Service\ProcessManagerFactory;
+use Magento\Framework\App\Cache\Manager;
use Magento\Framework\App\Config\Storage\WriterInterface;
-use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\App\State;
+use Magento\Framework\FlagManager;
+use Magento\Framework\ObjectManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Magento\Framework\FlagManager;
-use Magento\Framework\App\Cache\Manager;
class GenerateCommand extends Command
{
+ public const INPUT_OPTION_KEY_STORE_IDS = 'store-id';
+
/**
* @var ProcessManagerFactory
*/
@@ -88,6 +91,7 @@ public function __construct(
protected function configure()
{
$this->setName('m2bp:critical-css:generate');
+ $this->getDefinition()->addOptions($this->getOptionsList());
parent::configure();
}
@@ -96,25 +100,76 @@ protected function execute(InputInterface $input, OutputInterface $output)
try {
$this->state->setAreaCode(\Magento\Framework\App\Area::AREA_ADMINHTML);
- $this->cacheManager->flush($this->cacheManager->getAvailableTypes());
+ // TODO: decide whether cache flushing is really required. temporally commented.
+ //$this->cacheManager->flush($this->cacheManager->getAvailableTypes());
$this->criticalCssService->test($this->config->getCriticalBinary());
+
$consoleHandler = $this->consoleHandlerFactory->create(['output' => $output]);
+
$logger = $this->objectManager->create('M2Boilerplate\CriticalCss\Logger\Console', ['handlers' => ['console' => $consoleHandler]]);
- $output->writeln('Generating Critical CSS');
/** @var ProcessManager $processManager */
$processManager = $this->processManagerFactory->create(['logger' => $logger]);
+
+
+ $output->writeln('\'Use CSS critical path\' config is ' . ($this->config->isEnabled() ? 'Enabled' : 'Disabled') . '');
+ $output->writeln("-----------------------------------------");
+ $output->writeln('Critical Command Configured Options');
+ $output->writeln("-----------------------------------------");
+ $output->writeln('Screen Dimensions: ' . implode('', $this->config->getDimensions()) . '');
+ $output->writeln('Force Include Css Selectors: ' . implode('', $this->config->getForceIncludeCssSelectors()) . '');
+
+ $output->writeln('HTTP Auth Username: ' . $this->config->getUsername() . '');
+ $output->writeln('HTTP Auth Password: ' . $this->config->getPassword() . '');
+
+ $output->writeln("-----------------------------------------");
$output->writeln('Gathering URLs...');
- $processes = $processManager->createProcesses();
+ $output->writeln("-----------------------------------------");
+
+ $processes = $processManager->createProcesses(
+ $this->getStoreIds($input) ?: null
+ );
+
+ $output->writeln("-----------------------------------------");
$output->writeln('Generating Critical CSS for ' . count($processes) . ' URLs...');
+ $output->writeln("-----------------------------------------");
$processManager->executeProcesses($processes, true);
- $this->cacheManager->flush($this->cacheManager->getAvailableTypes());
+ // TODO: decide whether cache flushing is really required. temporally commented.
+ // $this->cacheManager->flush($this->cacheManager->getAvailableTypes());
} catch (\Throwable $e) {
throw $e;
}
return 0;
}
+
+ /**
+ * Returns list of options and arguments for the command
+ *
+ * @return mixed
+ */
+ public function getOptionsList()
+ {
+ return [
+ new InputOption(
+ self::INPUT_OPTION_KEY_STORE_IDS,
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Coma-separated list of Magento Store IDs or single value to process specific Store.'
+ ),
+ ];
+ }
+
+ /**
+ * @param InputInterface $input
+ * @return int[]
+ */
+ private function getStoreIds(InputInterface $input): array
+ {
+ $ids = $input->getOption(self::INPUT_OPTION_KEY_STORE_IDS) ?: '';
+ $ids = explode(',', $ids);
+ return array_map('intval', array_filter($ids));
+ }
}
diff --git a/src/Plugin/AsyncCssPlugin.php b/src/Plugin/AsyncCssPlugin.php
new file mode 100644
index 0000000..88d8cb2
--- /dev/null
+++ b/src/Plugin/AsyncCssPlugin.php
@@ -0,0 +1,125 @@
+scopeConfig = $scopeConfig;
+ $this->httpHeader = $httpHeader;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function afterRenderResult(Layout $subject, Layout $result, ResponseInterface $httpResponse)
+ {
+ if ($this->canBeProcessed($httpResponse)) {
+ return parent::afterRenderResult($subject, $result, $httpResponse);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return bool
+ */
+ private function canBeProcessed(ResponseInterface $httpResponse): bool
+ {
+ // NOTE: validate Critical Css activity Flag
+ if (!$this->isCssCriticalEnabled()) {
+ return false;
+ }
+
+ // NOTE: validate Request, it MUST NOT be initiated by NPM CRITICAL-CSS
+ // check on user-agent value
+ if ($this->httpHeader->getHttpUserAgent() === 'got (https://github.com/sindresorhus/got)') {
+ return false;
+ }
+
+ // NOTE: validate if CriticalCss node includes not-empty content
+ $content = (string)$httpResponse->getContent();
+ if ($this->isCriticalCssNodeEmpty($content)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * NOTE:
+ * @see \Magento\Theme\Controller\Result\AsyncCssPlugin::isCssCriticalEnabled
+ *
+ * Returns information whether css critical path is enabled
+ *
+ * @return bool
+ */
+ private function isCssCriticalEnabled(): bool
+ {
+ return $this->scopeConfig->isSetFlag(
+ 'dev/css/use_css_critical_path',
+ ScopeInterface::SCOPE_STORE
+ );
+ }
+
+ /**
+ * Validates if STYLE CriticalCss-node exists and is NOT empty
+ *
+ * @param string $content
+ * @return bool
+ */
+ private function isCriticalCssNodeEmpty(string $content): bool
+ {
+ $styles = '';
+ $styleOpen = '@s', $style, $matches)) {
+ $styles = str_replace(
+ ["\n","\r\n","\r"],
+ '',
+ trim((string)($matches[2] ?? null)));
+ break;
+ }
+
+ // NOTE: remove processed style-node from HTML.
+ $content = str_replace($style, '', $content);
+
+ // NOTE: style-node was cut out, search for the next one at its former position.
+ $styleOpenPos = strpos($content, $styleOpen, $styleOpenPos);
+ }
+
+ return empty($styles);
+ }
+}
diff --git a/src/Plugin/CriticalCss.php b/src/Plugin/CriticalCss.php
index e3a8149..3f305bd 100644
--- a/src/Plugin/CriticalCss.php
+++ b/src/Plugin/CriticalCss.php
@@ -65,9 +65,18 @@ public function __construct(
$this->storeManager = $storeManager;
}
+ /**
+ * @param \Magento\Theme\Block\Html\Header\CriticalCss $subject
+ * @param $result generated CSS code to be inline injected to page head
+ * @return string|null
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
public function afterGetCriticalCssData(\Magento\Theme\Block\Html\Header\CriticalCss $subject, $result)
{
+ $result = '';
+
$providers = $this->container->getProviders();
+
try {
$store = $this->storeManager->getStore();
} catch (NoSuchEntityException $e) {
@@ -77,9 +86,9 @@ public function afterGetCriticalCssData(\Magento\Theme\Block\Html\Header\Critica
foreach ($providers as $provider) {
if ($identifier = $provider->getCssIdentifierForRequest($this->request, $this->layout)) {
$identifier = $this->identifier->generateIdentifier($provider, $store, $identifier);
- $css = $this->storage->getCriticalCss($identifier);
- if ($css) {
- return $css;
+ $result = $this->storage->getCriticalCss($identifier);
+ if ($result) {
+ break;
}
}
}
diff --git a/src/Service/CriticalCss.php b/src/Service/CriticalCss.php
index c50de56..e427d37 100644
--- a/src/Service/CriticalCss.php
+++ b/src/Service/CriticalCss.php
@@ -22,6 +22,7 @@ public function __construct(ProcessFactory $processFactory)
public function createCriticalCssProcess(
string $url,
array $dimensions,
+ array $forceIncludeCssSelectors,
string $criticalBinary = 'critical',
?string $username = null,
?string $password = null
@@ -30,6 +31,12 @@ public function createCriticalCssProcess(
$criticalBinary,
$url
];
+
+ foreach ($forceIncludeCssSelectors as $selector) {
+ $command[] = '--penthouse-forceInclude';
+ $command[] = $selector;
+ }
+
foreach ($dimensions as $dimension) {
$command[] = '--dimensions';
$command[] = $dimension;
@@ -44,8 +51,12 @@ public function createCriticalCssProcess(
$command[] = '--strict';
$command[] = '--no-request-https.rejectUnauthorized';
- $command[] = '--ignore-rule';
- $command[] = '[data-role=main-css-loader]';
+
+ $command[] = '--ignore-atrule';
+ $command[] = '@font-face';
+
+ //$command[] = '--penthouse-blockJSRequests';
+ //$command[] = 'true';
/** @var Process $process */
$process = $this->processFactory->create(['command' => $command, 'commandline' => $command]);
diff --git a/src/Service/ProcessManager.php b/src/Service/ProcessManager.php
index f1fa3e1..86f17c9 100644
--- a/src/Service/ProcessManager.php
+++ b/src/Service/ProcessManager.php
@@ -2,9 +2,9 @@
namespace M2Boilerplate\CriticalCss\Service;
-use M2Boilerplate\CriticalCss\Model\ProcessContextFactory;
use M2Boilerplate\CriticalCss\Config\Config;
use M2Boilerplate\CriticalCss\Model\ProcessContext;
+use M2Boilerplate\CriticalCss\Model\ProcessContextFactory;
use M2Boilerplate\CriticalCss\Provider\Container;
use M2Boilerplate\CriticalCss\Provider\ProviderInterface;
use Magento\Store\Api\Data\StoreInterface;
@@ -121,10 +121,20 @@ public function executeProcesses(array $processList, bool $deleteOldFiles = fals
}
- public function createProcesses(): array
+ /**
+ * @param array|null $storeIds
+ * @return array
+ */
+ public function createProcesses(?array $storeIds = null): array
{
$processList = [];
foreach ($this->storeManager->getStores() as $storeId => $store) {
+ // NOTE: skip Store in case specific StoreIds to process are provided,
+ // but current StoreId is not in the List
+ if ($storeIds !== null && !in_array($storeId, $storeIds, true)) {
+ continue;
+ }
+
// Skip store if store is not active
if (!$store->getIsActive()) continue;
$this->emulation->startEnvironmentEmulation($storeId,\Magento\Framework\App\Area::AREA_FRONTEND, true);
@@ -145,10 +155,18 @@ public function createProcessesForProvider(ProviderInterface $provider, StoreInt
$processList = [];
$urls = $provider->getUrls($store);
foreach ($urls as $identifier => $url) {
+ // NOTE: start: SR WORKAROUND
+ // to add random query string to generated URLs to get non cached page content
+ $qParamConcatChar = mb_strpos($url, '?') === false ? '?' : '&';
+ $url .= $qParamConcatChar . 'm2bp_t=' . time();
+ // end: SR WORKAROUND
+
+
$this->logger->info(sprintf('[%s:%s|%s] - %s', $store->getCode(), $provider->getName(), $identifier, $url));
$process = $this->criticalCssService->createCriticalCssProcess(
$url,
$this->config->getDimensions(),
+ $this->config->getForceIncludeCssSelectors(),
$this->config->getCriticalBinary(),
$this->config->getUsername(),
$this->config->getPassword()
diff --git a/src/etc/adminhtml/system.xml b/src/etc/adminhtml/system.xml
index 0abb77c..cc88d2b 100644
--- a/src/etc/adminhtml/system.xml
+++ b/src/etc/adminhtml/system.xml
@@ -3,40 +3,48 @@
-
-
+
+
1
required-entry
Installation instructions can be found here: https://github.com/addyosmani/critical#install
-
-
+
+
1
- required-entry
- Comma separated List, e.g.: 375x812,576x1152,768x1024,1024x768,1280x720
+ validate-digits required-entry
-
-
+
+
1
- validate-digits required-entry
-
-
+
+
1
-
-
+
+
1
+ required-entry
+ Comma separated List, e.g.: 375x812,576x1152,768x1024,1024x768,1280x720
+
+
+
+
+ 1
+
+ required-entry
+ Comma separated css selectors to keep in critical css, even if not appearing in critical viewport. Strings or regex (f.e. '.keepMeEvenIfNotSeenInDom', /^\.button/)
diff --git a/src/etc/di.xml b/src/etc/di.xml
index 9d195f2..ffde044 100644
--- a/src/etc/di.xml
+++ b/src/etc/di.xml
@@ -9,7 +9,7 @@
- - M2Boilerplate\CriticalCss\Provider\DefaultProvider
+
- M2Boilerplate\CriticalCss\Provider\CmsPageProvider
- M2Boilerplate\CriticalCss\Provider\CustomerProvider
- M2Boilerplate\CriticalCss\Provider\ContactProvider
@@ -44,4 +44,4 @@
M2Boilerplate\CriticalCss\Logger\File
-
\ No newline at end of file
+
diff --git a/src/etc/frontend/di.xml b/src/etc/frontend/di.xml
index c413f92..5f8b056 100644
--- a/src/etc/frontend/di.xml
+++ b/src/etc/frontend/di.xml
@@ -2,4 +2,14 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
diff --git a/src/etc/module.xml b/src/etc/module.xml
index 5581f13..d96095c 100644
--- a/src/etc/module.xml
+++ b/src/etc/module.xml
@@ -1,4 +1,8 @@
-
-
\ No newline at end of file
+
+
+
+
+
+