Skip to content

Commit

Permalink
Merge pull request #4354 from magento-performance/MC-17449
Browse files Browse the repository at this point in the history
[Performance] Introduce CSS critical path and font display swap
  • Loading branch information
vzabaznov authored Jun 14, 2019
2 parents 23d4ad8 + 1260dd5 commit ef1ad60
Show file tree
Hide file tree
Showing 20 changed files with 363 additions and 41 deletions.
58 changes: 58 additions & 0 deletions app/code/Magento/Theme/Block/Html/Header/CriticalCss.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\Theme\Block\Html\Header;

use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Framework\View\Asset\Repository;
use Magento\Framework\View\Asset\File\NotFoundException;

/**
* This ViewModel will add inline critical css in case dev/css/use_css_critical_path is enabled.
*/
class CriticalCss implements ArgumentInterface
{
/**
* @var Repository
*/
private $assetRepo;

/**
* @var string
*/
private $filePath;

/**
* @param Repository $assetRepo
* @param string $filePath
*/
public function __construct(
Repository $assetRepo,
string $filePath = ''
) {
$this->assetRepo = $assetRepo;
$this->filePath = $filePath;
}

/**
* Returns critical css data as string.
*
* @return bool|string
*/
public function getCriticalCssData()
{
try {
$asset = $this->assetRepo->createAsset($this->filePath, ['_secure' => 'false']);
$content = $asset->getContent();
} catch (NotFoundException $e) {
$content = '';
}

return $content;
}
}
79 changes: 79 additions & 0 deletions app/code/Magento/Theme/Controller/Result/AsyncCssPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Theme\Controller\Result;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\App\Response\Http;

/**
* Plugin for asynchronous CSS loading.
*/
class AsyncCssPlugin
{
const XML_PATH_USE_CSS_CRITICAL_PATH = 'dev/css/use_css_critical_path';

/**
* @var ScopeConfigInterface
*/
private $scopeConfig;

/**
* @param ScopeConfigInterface $scopeConfig
*/
public function __construct(ScopeConfigInterface $scopeConfig)
{
$this->scopeConfig = $scopeConfig;
}

/**
* Load CSS asynchronously if it is enabled in configuration.
*
* @param Http $subject
* @return void
*/
public function beforeSendResponse(Http $subject)
{
$content = $subject->getContent();

if (\is_string($content) && strpos($content, '</body') !== false && $this->scopeConfig->isSetFlag(
self::XML_PATH_USE_CSS_CRITICAL_PATH,
ScopeInterface::SCOPE_STORE
)) {
$cssMatches = [];
// add link rel preload to style sheets
$content = preg_replace_callback(
'@<link\b.*?rel=("|\')stylesheet\1.*?/>@',
function ($matches) use (&$cssMatches) {
$cssMatches[] = $matches[0];
preg_match('@href=("|\')(.*?)\1@', $matches[0], $hrefAttribute);
$href = $hrefAttribute[2];
if (preg_match('@media=("|\')(.*?)\1@', $matches[0], $mediaAttribute)) {
$media = $mediaAttribute[2];
}
$media = $media ?? 'all';
$loadCssAsync = sprintf(
'<link rel="preload" as="style" media="%s" .
onload="this.onload=null;this.rel=\'stylesheet\'"' .
'href="%s">',
$media,
$href
);

return $loadCssAsync;
},
$content
);

if (!empty($cssMatches)) {
$content = str_replace('</body', implode("\n", $cssMatches) . "\n</body", $content);
$subject->setContent($content);
}
}
}
}
20 changes: 20 additions & 0 deletions app/code/Magento/Theme/etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<section id="dev">
<group id="css">
<field id="use_css_critical_path" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
<label>Use CSS critical path</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
<comment>CSS files are loading synchronously by default.</comment>
</field>
</group>
</section>
</system>
</config>
3 changes: 3 additions & 0 deletions app/code/Magento/Theme/etc/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ Disallow: /*SID=
<static>
<sign>1</sign>
</static>
<css>
<use_css_critical_path>0</use_css_critical_path>
</css>
</dev>
</default>
</config>
8 changes: 8 additions & 0 deletions app/code/Magento/Theme/etc/frontend/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@
<type name="Magento\Framework\Controller\ResultInterface">
<plugin name="result-messages" type="Magento\Theme\Controller\Result\MessagePlugin"/>
</type>
<type name="Magento\Framework\App\Response\Http">
<plugin name="asyncCssLoad" type="Magento\Theme\Controller\Result\AsyncCssPlugin"/>
</type>
<type name="Magento\Theme\Block\Html\Header\CriticalCss">
<arguments>
<argument name="filePath" xsi:type="string">css/critical.css</argument>
</arguments>
</type>
</config>
4 changes: 3 additions & 1 deletion app/code/Magento/Theme/view/frontend/layout/default.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@
</container>
</referenceContainer>
<referenceContainer name="main">
<container name="content.top" label="Main Content Top"/>
<container name="content.top" label="Main Content Top">
<block name="main_css_preloader" as="main_css_preloader" template="Magento_Theme::html/main_css_preloader.phtml" ifconfig="dev/css/use_css_critical_path"/>
</container>
<container name="content" label="Main Content Area"/>
<container name="content.aside" label="Main Content Aside"/>
<container name="content.bottom" label="Main Content Bottom"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
<script src="requirejs/require.js"/>
</head>
<body>
<referenceBlock name="head.additional">
<block name="critical_css_block" as="critical_css" template="Magento_Theme::html/header/criticalCss.phtml" ifconfig="dev/css/use_css_critical_path">
<arguments>
<argument name="criticalCssViewModel" xsi:type="object">Magento\Theme\Block\Html\Header\CriticalCss</argument>
</arguments>
</block>
<block name="css_rel_preload_script" ifconfig="dev/css/use_css_critical_path" template="Magento_Theme::js/css_rel_preload.phtml"/>
</referenceBlock>
<referenceContainer name="after.body.start">
<block class="Magento\Framework\View\Element\Template" name="head.polyfill" as="polyfill" template="Magento_Theme::js/polyfill.phtml" before="-"/>
<block class="Magento\Framework\View\Element\Js\Components" name="head.components" as="components" template="Magento_Theme::js/components.phtml" before="-"/>
Expand Down
3 changes: 2 additions & 1 deletion app/code/Magento/Theme/view/frontend/requirejs-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ var config = {
'popupWindow': 'mage/popup-window',
'validation': 'mage/validation/validation',
'welcome': 'Magento_Theme/js/view/welcome',
'breadcrumbs': 'Magento_Theme/js/view/breadcrumbs'
'breadcrumbs': 'Magento_Theme/js/view/breadcrumbs',
'criticalCssLoader': 'Magento_Theme/js/view/critical-css-loader'
}
},
paths: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

/**
* @var \Magento\Theme\Block\Html\Header\CriticalCss $criticalCssViewModel
*/
?>
<?php $criticalCssViewModel = $block->getData('criticalCssViewModel'); ?>

<style type="text/css" data-type="criticalCss">
<?= /* @noEscape */ $criticalCssViewModel->getCriticalCssData() ?>
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
?>
<div data-role="main-css-loader" class="loading-mask">
<div class="loader">
<img src="<?= $block->escapeUrl($block->getViewFileUrl('images/loader-1.gif')); ?>"
alt="<?= $block->escapeHtml(__('Loading...')); ?>"
style="position: absolute;">
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
?>
<script>
/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this);
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
<css src="css/styles-m.css"/>
<css src="css/styles-l.css" media="screen and (min-width: 768px)"/>
<css src="css/print.css" media="print"/>
<font src="fonts/opensans/light/opensans-300.woff2"/>
<font src="fonts/opensans/regular/opensans-400.woff2"/>
<font src="fonts/opensans/semibold/opensans-600.woff2"/>
<font src="fonts/opensans/bold/opensans-700.woff2"/>
<font src="fonts/Luma-Icons.woff2"/>
<meta name="format-detection" content="telephone=no"/>
</head>
</page>
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@
._block-content-loading {
position: relative;
}

[data-role='main-css-loader'] {
display: none;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,32 @@
@family-name: @font-family-name__base,
@font-path: '@{baseDir}fonts/opensans/light/opensans-300',
@font-weight: 300,
@font-style: normal
@font-style: normal,
@font-display: swap
);

.lib-font-face(
@family-name: @font-family-name__base,
@font-path: '@{baseDir}fonts/opensans/regular/opensans-400',
@font-weight: 400,
@font-style: normal
@font-style: normal,
@font-display: swap
);

.lib-font-face(
@family-name: @font-family-name__base,
@font-path: '@{baseDir}fonts/opensans/semibold/opensans-600',
@font-weight: 600,
@font-style: normal
@font-style: normal,
@font-display: swap
);

.lib-font-face(
@family-name: @font-family-name__base,
@font-path: '@{baseDir}fonts/opensans/bold/opensans-700',
@font-weight: 700,
@font-style: normal
@font-style: normal,
@font-display: swap
);
}

Expand Down
1 change: 1 addition & 0 deletions app/design/frontend/Magento/luma/web/css/critical.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ef1ad60

Please sign in to comment.