Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Model/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Config
public const XML_PATH_DEFERRED_ENABLED = 'mfrocketjavascript/deferred_javascript/enabled';
public const XML_PATH_DEFERRED_DISALLOWED_PAGES = 'mfrocketjavascript/deferred_javascript/disallowed_pages_for_deferred_js';
public const XML_PATH_DEFERRED_IGNORE_JAVASCRIPT = 'mfrocketjavascript/deferred_javascript/ignore_deferred_javascript_with';
public const XML_PATH_MOVE_SCRIPTS_TO_EXTERNAL_FILE = 'mfrocketjavascript/deferred_javascript/movebody_scripts_to_file';

/**
* JavaScript Bundling config
Expand Down Expand Up @@ -131,6 +132,17 @@ public function getIncludedInBundling(): string
return (string)$this->getConfig(self::XML_PATH_JAVASCRIPT_BUNDLING_INCLUDED_IN_BUNDLING);
}

/**
* Retrieve true if move scripts to external file is enabled
*
* @param string|null $storeId
* @return bool
*/
public function isMoveToFileEnabled(?string $storeId = null): bool
{
return (bool)$this->getConfig(self::XML_PATH_MOVE_SCRIPTS_TO_EXTERNAL_FILE, $storeId);
}

/**
* Retrieve true if amp enabled
*
Expand Down
106 changes: 102 additions & 4 deletions Model/Controller/ResultPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

namespace Magefan\RocketJavaScript\Model\Controller;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\Response\Http as ResponseHttp;
use Magento\Framework\Filesystem;
use Magento\Framework\View\LayoutInterface;
use Magento\PageCache\Model\Config;

/**
* Plugin for processing relocation of javascript
Expand Down Expand Up @@ -38,24 +42,57 @@ class ResultPlugin
*/
protected $storeManager;

/**
* @var Filesystem
*/
private $filesystem;

/**
* @var Config
*/
private $pageCacheConfig;

/**
* @var LayoutInterface
*/
private $layout;

/**
* @var \Magento\Framework\View\Asset\Repository
*/
private $assetRepository;

/**
* ResultPlugin constructor.
* @param \Magento\Framework\App\RequestInterface $request
* @param \Magefan\RocketJavaScript\Model\Config $config
* @param Filesystem $filesystem
* @param Config $pageCacheConfig
* @param LayoutInterface $layout
* @param \Magento\Store\Model\StoreManagerInterface|null $storeManager
* @param \Magento\Framework\View\Asset\Repository|null $assetRepository
*/
public function __construct(
\Magento\Framework\App\RequestInterface $request,
\Magefan\RocketJavaScript\Model\Config $config,
?\Magento\Store\Model\StoreManagerInterface $storeManager = null
Filesystem $filesystem,
Config $pageCacheConfig,
LayoutInterface $layout,
?\Magento\Store\Model\StoreManagerInterface $storeManager = null,
?\Magento\Framework\View\Asset\Repository $assetRepository = null
) {
$this->request = $request;
$this->config = $config;

$this->filesystem = $filesystem;
$this->pageCacheConfig = $pageCacheConfig;
$this->layout = $layout;
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->storeManager = $storeManager ?: $objectManager->get(
\Magento\Store\Model\StoreManagerInterface::class
);
$this->assetRepository = $assetRepository ?: $objectManager->get(
\Magento\Framework\View\Asset\Repository::class
);
}

/**
Expand Down Expand Up @@ -88,12 +125,15 @@ public function aroundRenderResult(
$html = $response->getBody();
$scripts = [];
$positions = [];
$priorityScripts = [];

$startTag = '<script';
$endTag = '</script>';

$lastPos = 0;
$start = 0;
$moveToFile = $this->config->isMoveToFileEnabled() && $this->canWriteToFile();
$moveToFile = true;

// First pass: find all script tags and their positions
while (false !== ($start = stripos($html, $startTag, $start))) {
Expand All @@ -106,12 +146,19 @@ public function aroundRenderResult(
$script = substr($html, $start, $scriptEnd - $start);

// Check for exclusion flags or ignored content
if (false !== stripos($script, self::EXCLUDE_FLAG_PATTERN) ||
false !== stripos($script, 'application/ld+json')) {
if (false !== stripos($script, 'application/ld+json')) {
$start = $scriptEnd; // Move pointer past this script
continue;
}

if (false !== stripos($script, self::EXCLUDE_FLAG_PATTERN)) {
if (!$moveToFile) {
$start = $scriptEnd;
continue;
}
$priorityScripts[] = $script;
}

$isIgnored = false;
foreach ($ignoredStrings as $ignoredString) {
if (false !== stripos($script, $ignoredString)) {
Expand Down Expand Up @@ -148,6 +195,49 @@ public function aroundRenderResult(
// Append the remaining HTML after the last script tag
$newHtml .= substr($html, $lastPos);

if ($moveToFile) {
$combinedJs = '';
$externalScriptTags = [];
$allScripts = array_merge($priorityScripts, $scripts);
foreach ($allScripts as $script) {
$openTagEnd = strpos($script, '>');
$openTag = false !== $openTagEnd ? substr($script, 0, $openTagEnd + 1) : $script;

if (false !== stripos($openTag, ' src=') ||
false !== stripos($openTag, 'x-magento-init') ||
false !== stripos($openTag, 'x-magento-template') ||
false !== strpos($script, 'require.config(') ||
false !== strpos($script, 'var require =')
) {
$externalScriptTags[] = $script;
continue;
}

if (false === $openTagEnd) {
continue;
}
$jsContent = trim(substr($script, $openTagEnd + 1, strrpos($script, '</script>') - $openTagEnd - 1));

if (!empty($jsContent)) {
if (!str_ends_with($jsContent, ';')) {
$jsContent .= ';';
}
$combinedJs .= $jsContent . "\n";
}
}

if (!empty($combinedJs)) {
$key = sha1($combinedJs);
$asset = $this->assetRepository->createAsset('mfrocketjs/' . $key . '.js');
$relativePath = $asset->getPath();
$staticDir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW);
if (!$staticDir->isExist($relativePath)) {
$staticDir->writeFile($relativePath, $combinedJs);
}
$scripts = array_merge($externalScriptTags, ['<script src="' . $asset->getUrl() . '" defer></script>']);
}
}

// Append the scripts before the closing </body> tag or at the end
$allScripts = implode(PHP_EOL, $scripts);
$bodyEndPos = stripos($newHtml, '</body>');
Expand All @@ -162,6 +252,14 @@ public function aroundRenderResult(
return $result;
}

/**
* @return bool
*/
private function canWriteToFile(): bool
{
return $this->pageCacheConfig->isEnabled() && $this->layout->isCacheable();
}

private function isEnabled()
{
$enabled = $this->config->isEnabled() && $this->config->isDeferredEnabled();
Expand Down
8 changes: 8 additions & 0 deletions etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@
</depends>
<comment><![CDATA[Enter ignored strings, each in a new line. JavaScript that contains these strings will not be moved to the bottom of the page. Scripts that contain <strong>data-rocketjavascript="false"</strong> will automatically be ignored. Example &lt;script data-rocketjavascript="false"&gt;/* some script *&lt;/script&gt;]]></comment>
</field>
<field id="movebody_scripts_to_file" showInDefault="1" showInStore="1" showInWebsite="1" sortOrder="40" translate="label" type="select" canRestore="1">
<label>Move Body Scripts to File</label>
<depends>
<field id="enabled">1</field>
</depends>
<comment>Extract inline scripts from the HTML body and combine them into a separate JavaScript file. Helps reduce DOM size and improves page parsing performance.</comment>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
</group>
<group id="javascript_bundling" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
<label>JavaScript Bundling</label>
Expand Down
1 change: 1 addition & 0 deletions etc/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
onestepcheckout/*
</disallowed_pages_for_deferred_js>
<ignore_deferred_javascript_with>www.googletagmanager.com</ignore_deferred_javascript_with>
<movebody_scripts_to_file>0</movebody_scripts_to_file>
</deferred_javascript>
<javascript_bundling>
<enable_js_bundling_optimization>1</enable_js_bundling_optimization>
Expand Down