Skip to content

Commit 8a348c8

Browse files
committed
Merge remote-tracking branch 'mainline/2.3-develop' into 2.3-develop-prs
2 parents 6fca73d + fb9a02a commit 8a348c8

File tree

21 files changed

+301
-14
lines changed

21 files changed

+301
-14
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Theme\Block\Html\Header;
10+
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\View\Element\Block\ArgumentInterface;
13+
use Magento\Framework\View\Asset\Repository;
14+
use Magento\Framework\View\Asset\File\NotFoundException;
15+
16+
/**
17+
* This ViewModel will add inline critical css in case dev/css/use_css_critical_path is enabled.
18+
*/
19+
class CriticalCss implements ArgumentInterface
20+
{
21+
/**
22+
* @var Repository
23+
*/
24+
private $assetRepo;
25+
26+
/**
27+
* @var $filePath
28+
*/
29+
private $filePath;
30+
31+
/**
32+
* @param Repository $assetRepo
33+
* @param string $filePath
34+
*/
35+
public function __construct(
36+
Repository $assetRepo,
37+
string $filePath = ''
38+
) {
39+
$this->assetRepo = $assetRepo;
40+
$this->filePath = $filePath;
41+
}
42+
43+
/**
44+
* Returns critical css data as string.
45+
*
46+
* @return bool|string
47+
*/
48+
public function getCriticalCssData()
49+
{
50+
try {
51+
$asset = $this->assetRepo->createAsset($this->filePath, ['_secure' => 'false']);
52+
$content = $asset->getContent();
53+
} catch (LocalizedException | NotFoundException $e) {
54+
$content = '';
55+
}
56+
57+
return $content;
58+
}
59+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Theme\Controller\Result;
9+
10+
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
use Magento\Store\Model\ScopeInterface;
12+
use Magento\Framework\App\Response\Http;
13+
14+
/**
15+
* Plugin for asynchronous CSS loading.
16+
*/
17+
class AsyncCssPlugin
18+
{
19+
private const XML_PATH_USE_CSS_CRITICAL_PATH = 'dev/css/use_css_critical_path';
20+
21+
/**
22+
* @var ScopeConfigInterface
23+
*/
24+
private $scopeConfig;
25+
26+
/**
27+
* @param ScopeConfigInterface $scopeConfig
28+
*/
29+
public function __construct(ScopeConfigInterface $scopeConfig)
30+
{
31+
$this->scopeConfig = $scopeConfig;
32+
}
33+
34+
/**
35+
* Load CSS asynchronously if it is enabled in configuration.
36+
*
37+
* @param Http $subject
38+
* @return void
39+
*/
40+
public function beforeSendResponse(Http $subject): void
41+
{
42+
$content = $subject->getContent();
43+
44+
if (strpos($content, '</body') !== false && $this->scopeConfig->isSetFlag(
45+
self::XML_PATH_USE_CSS_CRITICAL_PATH,
46+
ScopeInterface::SCOPE_STORE
47+
)) {
48+
$cssMatches = [];
49+
// add link rel preload to style sheets
50+
$content = preg_replace_callback(
51+
'@<link\b.*?rel=("|\')stylesheet\1.*?/>@',
52+
function ($matches) use (&$cssMatches) {
53+
$cssMatches[] = $matches[0];
54+
preg_match('@href=("|\')(.*?)\1@', $matches[0], $hrefAttribute);
55+
$href = $hrefAttribute[2];
56+
if (preg_match('@media=("|\')(.*?)\1@', $matches[0], $mediaAttribute)) {
57+
$media = $mediaAttribute[2];
58+
}
59+
$media = $media ?? 'all';
60+
$loadCssAsync = sprintf(
61+
'<link rel="preload" as="style" media="%s" .
62+
onload="this.onload=null;this.rel=\'stylesheet\'"' .
63+
'href="%s">',
64+
$media,
65+
$href
66+
);
67+
68+
return $loadCssAsync;
69+
},
70+
$content
71+
);
72+
73+
if (!empty($cssMatches)) {
74+
$content = str_replace('</body', implode("\n", $cssMatches) . "\n</body", $content);
75+
$subject->setContent($content);
76+
}
77+
}
78+
}
79+
}

app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\Theme\Controller\Result;
79

810
use Magento\Framework\App\Config\ScopeConfigInterface;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
9+
<system>
10+
<section id="dev">
11+
<group id="css">
12+
<field id="use_css_critical_path" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
13+
<label>Use CSS critical path</label>
14+
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
15+
<comment>
16+
<![CDATA[<strong style="color:red">Warning!</strong> Be sure that you have critical.css file for your theme. Other CSS files will be loaded asynchronously.]]>
17+
</comment>
18+
</field>
19+
</group>
20+
</section>
21+
</system>
22+
</config>

app/code/Magento/Theme/etc/config.xml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ Disallow: /*SID=
6666
<static>
6767
<sign>1</sign>
6868
</static>
69-
<dev>
70-
<js>
71-
<move_inline_to_bottom>0</move_inline_to_bottom>
72-
</js>
73-
</dev>
69+
<js>
70+
<move_inline_to_bottom>0</move_inline_to_bottom>
71+
</js>
72+
<css>
73+
<use_css_critical_path>0</use_css_critical_path>
74+
</css>
7475
</dev>
7576
</default>
7677
</config>

app/code/Magento/Theme/etc/frontend/di.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,11 @@
2828
</type>
2929
<type name="Magento\Framework\App\Response\Http">
3030
<plugin name="result-js-footer" type="Magento\Theme\Controller\Result\JsFooterPlugin"/>
31+
<plugin name="asyncCssLoad" type="Magento\Theme\Controller\Result\AsyncCssPlugin"/>
32+
</type>
33+
<type name="Magento\Theme\Block\Html\Header\CriticalCss">
34+
<arguments>
35+
<argument name="filePath" xsi:type="string">css/critical.css</argument>
36+
</arguments>
3137
</type>
3238
</config>

app/code/Magento/Theme/view/frontend/layout/default.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@
107107
</container>
108108
</referenceContainer>
109109
<referenceContainer name="main">
110-
<container name="content.top" label="Main Content Top"/>
110+
<container name="content.top" label="Main Content Top">
111+
<block name="main_css_preloader" as="main_css_preloader" template="Magento_Theme::html/main_css_preloader.phtml" ifconfig="dev/css/use_css_critical_path"/>
112+
</container>
111113
<container name="content" label="Main Content Area"/>
112114
<container name="content.aside" label="Main Content Aside"/>
113115
<container name="content.bottom" label="Main Content Bottom"/>

app/code/Magento/Theme/view/frontend/layout/default_head_blocks.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
<script src="mage/polyfill.js"/>
1414
</head>
1515
<body>
16+
<referenceBlock name="head.additional">
17+
<block name="critical_css_block" as="critical_css" template="Magento_Theme::html/header/criticalCss.phtml" ifconfig="dev/css/use_css_critical_path">
18+
<arguments>
19+
<argument name="criticalCssViewModel" xsi:type="object">Magento\Theme\Block\Html\Header\CriticalCss</argument>
20+
</arguments>
21+
</block>
22+
<block name="css_rel_preload_script" ifconfig="dev/css/use_css_critical_path" template="Magento_Theme::js/css_rel_preload.phtml"/>
23+
</referenceBlock>
1624
<referenceContainer name="after.body.start">
1725
<block class="Magento\Framework\View\Element\Js\Components" name="head.components" as="components" template="Magento_Theme::js/components.phtml" before="-"/>
1826
</referenceContainer>

app/code/Magento/Theme/view/frontend/requirejs-config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ var config = {
2828
'popupWindow': 'mage/popup-window',
2929
'validation': 'mage/validation/validation',
3030
'welcome': 'Magento_Theme/js/view/welcome',
31-
'breadcrumbs': 'Magento_Theme/js/view/breadcrumbs'
31+
'breadcrumbs': 'Magento_Theme/js/view/breadcrumbs',
32+
'criticalCssLoader': 'Magento_Theme/js/view/critical-css-loader'
3233
}
3334
},
3435
paths: {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
/**
8+
* @var \Magento\Theme\Block\Html\Header\CriticalCss $criticalCssViewModel
9+
*/
10+
?>
11+
<?php $criticalCssViewModel = $block->getData('criticalCssViewModel'); ?>
12+
13+
<style type="text/css" data-type="criticalCss">
14+
<?= /* @noEscape */ $criticalCssViewModel->getCriticalCssData() ?>
15+
</style>

0 commit comments

Comments
 (0)