3

I need to Defer merged css using JavaScript. Is there any possibilities to defer merged css. Example code i want to do.

    <noscript id="deferred-styles">
      <link rel="stylesheet" type="text/css" href="https://xxxx.xx/static/version1xxxx/_cache/merged/f64d21109ccdd1dc8924b35f2614e40f.min.css"/>
    </noscript>

<script>
  var loadDeferredStyles = function() {
    var addStylesNode = document.getElementById("deferred-styles");
    var replacement = document.createElement("div");
    replacement.innerHTML = addStylesNode.textContent;
    document.body.appendChild(replacement)
    addStylesNode.parentElement.removeChild(addStylesNode);
  };
  var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  if (raf) raf(function() { window.setTimeout(loadDeferredStyles, 0); });
  else window.addEventListener('load', loadDeferredStyles);
</script>
1
  • Is there no one can do this? Commented Oct 13, 2020 at 7:42

1 Answer 1

2

I have a method that works but I'm not 100% confident I'm using best practices. The functions that merge and add CSS to the site header are in the Magento Framework module:

Magento\Framework\View\Page\Config\Renderer

The functions you need to edit are protected so we have to use preferences instead of say, a plugin. Make a new module in app/code/[Vendor]/[Namespace] with the usual files:

registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    '[Vendor]_[Namespace]',
    __DIR__
);

etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="[Vendor]_[Namespace]" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Framework" />
        </sequence>
    </module>
</config>

etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <preference for="Magento\Framework\View\Page\Config\Renderer" type="[Vendor]\[Namespace]\View\Page\Config\Renderer" />
</config>

Last thing is to make this file and copy all the Magento code in to it:

[Vendor]\[Namespace]\View\Page\Config\Renderer.php

copy the code from:

vendor\Magento\Framework\View\Page\Config\Renderer.php

You'll need to update the namespace and class definitions:

...
namespace [Vendor]\[Namespace]\View\Page\Config;
...
class Renderer implements \Magento\Framework\View\Page\Config\RendererInterface

And then you can get to customising the functions inside. I followed the Google page speed guide on deferring CSS by altering these two functions as follows:

/**
 * Add default attributes
 *
 * @param string $contentType
 * @param string $attributes
 * @return string
 */
protected function addDefaultAttributes($contentType, $attributes)
{
    if ($contentType === 'js') {
        return ' type="text/javascript" ' . $attributes;
    }

    if ($contentType === 'css') {
        return ' rel="preload" type="text/css" as="style" onload="this.onload=null;this.rel=\'stylesheet\'" ' . ($attributes ?: ' media="all"');
    }

    if ($this->canTypeBeFont($contentType)) {
        return 'rel="preload" as="font" crossorigin="anonymous"';
    }

    return $attributes;
}

and..

/**
 * Render HTML tags referencing corresponding URLs
 *
 * @param \Magento\Framework\View\Asset\PropertyGroup $group
 * @return string
 */
protected function renderAssetHtml(\Magento\Framework\View\Asset\PropertyGroup $group)
{
    $assets = $this->processMerge($group->getAll(), $group);
    $attributes = $this->getGroupAttributes($group);

    $result = '';
    $template = '';
    try {
        /** @var $asset \Magento\Framework\View\Asset\AssetInterface */
        foreach ($assets as $asset) {
            $template = $this->getAssetTemplate(
                $group->getProperty(GroupedCollection::PROPERTY_CONTENT_TYPE),
                $this->addDefaultAttributes($this->getAssetContentType($asset), $attributes)
            );
            if ($group->getProperty(GroupedCollection::PROPERTY_CONTENT_TYPE) === 'css') {
                $result .= sprintf($template, $asset->getUrl(), $asset->getUrl());
            } else {
                $result .= sprintf($template, $asset->getUrl());
            }
        }
    } catch (LocalizedException $e) {
        $this->logger->critical($e);
        $result .= sprintf($template, $this->urlBuilder->getUrl('', ['_direct' => 'core/index/notFound']));
    }
    return $result;
}

It seemed to satisfy the Lighthouse tool but it's not tested thoroughly. Good luck with this!

EDIT: This change affects the backend CSS loading as well... not ideal but not website breaking.

3
  • When I tested this I got: Error: Class '****\DeferCss\View\Page\Config\Generator\Head' not found in /app/code/****/DeferCss/View/Page/Config/Renderer.php: Commented Aug 3, 2021 at 21:20
  • You might need to run setup:upgrade (and di:compile in production) Commented Aug 5, 2021 at 8:37
  • Tried that no joy sadly Commented Aug 5, 2021 at 18:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.