Skip to content
This repository has been archived by the owner on May 28, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4 from weprovide/feature/block_script_handling
Browse files Browse the repository at this point in the history
Option added for adding async defer to scripts inside blocks
  • Loading branch information
MrBlueEyez authored Oct 12, 2022
2 parents 427296b + c23c62a commit f952fa3
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 96 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ $ bin/magento setup:upgrade --keep-generated
In `System > Configuration > Advanced > Developer > JavaScript Settings` two configuration settings can be seen. One of them
is the regular expression used for the `async` HTML attribute, the other for `defer`.

There is also an other setting, the `Check block scripts for "async" and "defer"` which defaults to "No", and can be
enabled when there is also a need to apply `async` and `defer` to scripts inside blocks.

![Configuration Settings](adjs-configuration.png)

The following regex can be used to match all scripts accept the require.js: `^((?!require.js).)*$`
Binary file modified adjs-configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
"name": "weprovide/magento2-module-async-defer-js",
"description": "A module which adds the \"async\" and \"defer\" HTML attributes to scripts",
"type": "magento2-module",
"version": "1.0.1",
"version": "1.1.0",
"license": "MIT",
"authors": [
{
"email": "[email protected]",
"name": "Julian van den Berkmortel"
},
{
"email": "[email protected]",
"name": "Sander Merks"
}
],
"require": {
Expand Down
46 changes: 46 additions & 0 deletions src/Plugin/AddAsyncDeferToBlockScripts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);

namespace WeProvide\AsyncDeferJs\Plugin;

use Magento\Framework\View\Element\AbstractBlock;
use WeProvide\AsyncDeferJs\Service\Config as ConfigService;
use WeProvide\AsyncDeferJs\Service\ScriptAdjuster;

/**
* Responsible for adding the "async" and "defer" attribute to HTML script elements that match a set of regular
* expressions
*/
class AddAsyncDeferToBlockScripts
{
/** @var ScriptAdjuster */
protected ScriptAdjuster $scriptAdjuster;
/** @var ConfigService */
protected ConfigService $configService;

/**
* @param ScriptAdjuster $scriptAdjuster
* @param ConfigService $configService
*/
public function __construct(ScriptAdjuster $scriptAdjuster, ConfigService $configService)
{
$this->scriptAdjuster = $scriptAdjuster;
$this->configService = $configService;
}

/**
* Gives the result to the script adjuster to apply async and defer
*
* @param AbstractBlock $subject
* @param string $result
* @return string
*/
public function afterToHtml(AbstractBlock $subject, string $result): string
{
if ($this->configService->shouldCheckBlockScript()) {
$result = $this->scriptAdjuster->adjustScripts($result);
}

return $result;
}
}
107 changes: 14 additions & 93 deletions src/Plugin/AddAsyncDeferToPageAssets.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,113 +4,34 @@
namespace WeProvide\AsyncDeferJs\Plugin;

use Magento\Framework\View\Page\Config\Renderer;
use Psr\Log\LoggerInterface;
use WeProvide\AsyncDeferJs\Config\AsyncRegexValueReader;
use WeProvide\AsyncDeferJs\Config\DeferRegexValueReader;
use WeProvide\AsyncDeferJs\Config\ValueReaderInterface;
use WeProvide\AsyncDeferJs\Exception\Config\ValueReaderException;
use WeProvide\AsyncDeferJs\Factory\HTMLDocumentFactory;
use WeProvide\AsyncDeferJs\Service\ScriptAdjuster;

/**
* Responsible for adding the "async" and "defer" attribute to HTML script elements that match a set of regular expressions
* Responsible for adding the "async" and "defer" attribute to HTML script elements that match a set of regular
* expressions
*/
class AddAsyncDeferToPageAssets
{
/** @var HTMLDocumentFactory */
protected $htmlDocumentFactory;

/** @var ValueReaderInterface */
protected $asyncRegexValueReader;

/** @var ValueReaderInterface */
protected $deferRegexValueReader;

/** @var LoggerInterface */
protected $logger;
/** @var ScriptAdjuster */
protected ScriptAdjuster $scriptAdjuster;

/**
* AddAsyncDeferToPageAssets constructor
*
* @param HTMLDocumentFactory $htmlDocumentFactory
* @param ValueReaderInterface $asyncRegexValueReader
* @param ValueReaderInterface $deferRegexValueReader
* @param LoggerInterface $logger
* @param ScriptAdjuster $scriptAdjuster
*/
public function __construct(
HTMLDocumentFactory $htmlDocumentFactory,
ValueReaderInterface $asyncRegexValueReader,
ValueReaderInterface $deferRegexValueReader,
LoggerInterface $logger
) {
$this->htmlDocumentFactory = $htmlDocumentFactory;
$this->asyncRegexValueReader = $asyncRegexValueReader;
$this->deferRegexValueReader = $deferRegexValueReader;
$this->logger = $logger;
}

public function afterRenderAssets(Renderer $subject, string $result): string
public function __construct(ScriptAdjuster $scriptAdjuster)
{
$document = $this->htmlDocumentFactory->create($result);

foreach ($document->querySelectorAll('script') as $script) {
$src = $script->getAttribute('src');

if ($src === null) {
continue;
}

if ($this->matchAsyncRegex($src)) {
$script->setAttribute('async', 'true');
}

if ($this->matchDeferRegex($src)) {
$script->setAttribute('defer', 'true');
}
}

// https://stackoverflow.com/a/10023094/4391861
return preg_replace('~<(?:!DOCTYPE|/?(?:html|body|head))[^>]*>\s*~i', '', $document->saveHTML());
}

/**
* Check if the given source matches the regular expression for adding the "async" HTML attribute
*
* @param string $src the source to match
* @return bool true if the given source matches the regular expression, false otherwise
*/
protected function matchAsyncRegex(string $src): bool
{
try {
$regex = $this->asyncRegexValueReader->read();
return $regex !== null && preg_match($regex, $src) === 1;
} catch (ValueReaderException $exception) {
$this->logger->warning(sprintf(
'Something went wrong whilst reading the regular expression for the "async" HTML attribute: %s',
$exception->getMessage()
));

return false;
}
$this->scriptAdjuster = $scriptAdjuster;
}

/**
* Check if the given source matches the regular expression for adding the "defer" HTML attribute
* Gives the result to the script adjuster to apply async and defer
*
* @param string $src the source to match
* @return bool true if the given source matches the regular expression, false otherwise
* @param Renderer $subject
* @param string $result
* @return string
*/
protected function matchDeferRegex(string $src): bool
public function afterRenderAssets(Renderer $subject, string $result): string
{
try {
$regex = $this->deferRegexValueReader->read();
return $regex !== null && preg_match($regex, $src) === 1;
} catch (ValueReaderException $exception) {
$this->logger->warning(sprintf(
'Something went wrong whilst reading the regular expression for the "defer" HTML attribute: %s',
$exception->getMessage()
));

return false;
}
return $this->scriptAdjuster->adjustScripts($result);
}
}
38 changes: 38 additions & 0 deletions src/Service/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace WeProvide\AsyncDeferJs\Service;

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

class Config
{
/** @var string */
protected const CONFIG_APPLY_TO_BLOCKS = 'dev/js/apply_async_defer_to_blocks';

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

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

/**
* Whether blocks should be checked on scripts as well
*
* @return bool
*/
public function shouldCheckBlockScript(): bool
{
return $this->scopeConfig->isSetFlag(
static::CONFIG_APPLY_TO_BLOCKS,
ScopeInterface::SCOPE_STORE
);
}
}
115 changes: 115 additions & 0 deletions src/Service/ScriptAdjuster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

declare(strict_types=1);

namespace WeProvide\AsyncDeferJs\Service;

use Psr\Log\LoggerInterface;
use WeProvide\AsyncDeferJs\Config\ValueReaderInterface;
use WeProvide\AsyncDeferJs\Exception\Config\ValueReaderException;
use WeProvide\AsyncDeferJs\Factory\HTMLDocumentFactory;

class ScriptAdjuster
{
/** @var HTMLDocumentFactory */
protected HTMLDocumentFactory $htmlDocumentFactory;

/** @var ValueReaderInterface */
protected ValueReaderInterface $asyncRegexValueReader;

/** @var ValueReaderInterface */
protected ValueReaderInterface $deferRegexValueReader;

/** @var LoggerInterface */
protected LoggerInterface $logger;

/**
* @param HTMLDocumentFactory $htmlDocumentFactory
* @param ValueReaderInterface $asyncRegexValueReader
* @param ValueReaderInterface $deferRegexValueReader
* @param LoggerInterface $logger
*/
public function __construct(
HTMLDocumentFactory $htmlDocumentFactory,
ValueReaderInterface $asyncRegexValueReader,
ValueReaderInterface $deferRegexValueReader,
LoggerInterface $logger
) {
$this->htmlDocumentFactory = $htmlDocumentFactory;
$this->asyncRegexValueReader = $asyncRegexValueReader;
$this->deferRegexValueReader = $deferRegexValueReader;
$this->logger = $logger;
}

/**
* Adds async and defer to the scripts inside the html when they match with the regex
*
* @param string $html
* @return string
*/
public function adjustScripts(string $html): string
{
$document = $this->htmlDocumentFactory->create($html);

foreach ($document->querySelectorAll('script[src]') as $script) {
$src = $script->getAttribute('src');

if ($src === null) {
continue;
}

if ($this->matchAsyncRegex($src)) {
$script->setAttribute('async', 'true');
}

if ($this->matchDeferRegex($src)) {
$script->setAttribute('defer', 'true');
}
}

// https://stackoverflow.com/a/10023094/4391861
return preg_replace('~<(?:!DOCTYPE|/?(?:html|body|head))[^>]*>\s*~i', '', $document->saveHTML());
}

/**
* Check if the given source matches the regular expression for adding the "async" HTML attribute
*
* @param string $src the source to match
* @return bool true if the given source matches the regular expression, false otherwise
*/
protected function matchAsyncRegex(string $src): bool
{
try {
$regex = $this->asyncRegexValueReader->read();
return $regex !== null && preg_match($regex, $src) === 1;
} catch (ValueReaderException $exception) {
$this->logger->warning(sprintf(
'Something went wrong whilst reading the regular expression for the "async" HTML attribute: %s',
$exception->getMessage()
));

return false;
}
}

/**
* Check if the given source matches the regular expression for adding the "defer" HTML attribute
*
* @param string $src the source to match
* @return bool true if the given source matches the regular expression, false otherwise
*/
protected function matchDeferRegex(string $src): bool
{
try {
$regex = $this->deferRegexValueReader->read();
return $regex !== null && preg_match($regex, $src) === 1;
} catch (ValueReaderException $exception) {
$this->logger->warning(sprintf(
'Something went wrong whilst reading the regular expression for the "defer" HTML attribute: %s',
$exception->getMessage()
));

return false;
}
}
}
5 changes: 5 additions & 0 deletions src/etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
<system>
<section id="dev">
<group id="js">
<field id="apply_async_defer_to_blocks" translate="label" type="select" sortOrder="21" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Check block scripts for "async" and "defer"</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>

<field id="async_attr_regex" translate="label comment" type="text" sortOrder="22" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Regular Expression for "async"</label>
<comment>If the source of a script matches this regular expression an "async" attribute will be added</comment>
Expand Down
2 changes: 1 addition & 1 deletion src/etc/di.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="WeProvide\AsyncDeferJs\Plugin\AddAsyncDeferToPageAssets">
<type name="WeProvide\AsyncDeferJs\Service\ScriptAdjuster">
<arguments>
<argument name="asyncRegexValueReader" xsi:type="object">WeProvide\AsyncDeferJs\Config\AsyncRegexValueReader</argument>
<argument name="deferRegexValueReader" xsi:type="object">WeProvide\AsyncDeferJs\Config\DeferRegexValueReader</argument>
Expand Down
9 changes: 8 additions & 1 deletion src/etc/frontend/di.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\View\Page\Config\Renderer">
<plugin name="add_async_defer_to_page_assets" type="WeProvide\AsyncDeferJs\Plugin\AddAsyncDeferToPageAssets" />
<plugin name="add_async_defer_to_page_assets"
type="WeProvide\AsyncDeferJs\Plugin\AddAsyncDeferToPageAssets"
sortOrder="999"/>
</type>
<type name="Magento\Framework\View\Element\AbstractBlock">
<plugin name="add_async_defer_to_block_scripts"
type="WeProvide\AsyncDeferJs\Plugin\AddAsyncDeferToBlockScripts"
sortOrder="999"/>
</type>
</config>

0 comments on commit f952fa3

Please sign in to comment.