From 6aa30a563c0cdb6b8c1df0abe7f82c308094dff6 Mon Sep 17 00:00:00 2001 From: "ivan.hrytsai" Date: Wed, 20 May 2026 12:04:00 +0300 Subject: [PATCH 1/6] 15449-New-Option-in-RocketJavaScript --- Model/Config.php | 12 ++++ Model/Controller/ResultPlugin.php | 94 ++++++++++++++++++++++++++++++- etc/adminhtml/system.xml | 8 +++ etc/config.xml | 1 + 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/Model/Config.php b/Model/Config.php index 68d0ae7..1e446a9 100644 --- a/Model/Config.php +++ b/Model/Config.php @@ -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 @@ -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 * diff --git a/Model/Controller/ResultPlugin.php b/Model/Controller/ResultPlugin.php index 39fc12c..25067e0 100644 --- a/Model/Controller/ResultPlugin.php +++ b/Model/Controller/ResultPlugin.php @@ -8,7 +8,10 @@ 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\UrlInterface; /** * Plugin for processing relocation of javascript @@ -38,24 +41,52 @@ class ResultPlugin */ protected $storeManager; + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var \Magento\PageCache\Model\Config + */ + private $pageCacheConfig; + + /** + * @var \Magento\Framework\View\LayoutInterface + */ + private $layout; + /** * ResultPlugin constructor. * @param \Magento\Framework\App\RequestInterface $request * @param \Magefan\RocketJavaScript\Model\Config $config + * @param Filesystem $filesystem * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager + * @param \Magento\PageCache\Model\Config|null $pageCacheConfig + * @param \Magento\Framework\View\LayoutInterface|null $layout */ public function __construct( \Magento\Framework\App\RequestInterface $request, \Magefan\RocketJavaScript\Model\Config $config, - ?\Magento\Store\Model\StoreManagerInterface $storeManager = null + Filesystem $filesystem, + ?\Magento\Store\Model\StoreManagerInterface $storeManager = null, + ?\Magento\PageCache\Model\Config $pageCacheConfig = null, + ?\Magento\Framework\View\LayoutInterface $layout = null ) { $this->request = $request; $this->config = $config; + $this->filesystem = $filesystem; $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $this->storeManager = $storeManager ?: $objectManager->get( \Magento\Store\Model\StoreManagerInterface::class ); + $this->pageCacheConfig = $pageCacheConfig ?: $objectManager->get( + \Magento\PageCache\Model\Config::class + ); + $this->layout = $layout ?: $objectManager->get( + \Magento\Framework\View\LayoutInterface::class + ); } /** @@ -88,12 +119,14 @@ public function aroundRenderResult( $html = $response->getBody(); $scripts = []; $positions = []; + $prioritScripts = []; $startTag = 'config->isMoveToFileEnabled() && $this->canWriteToFile(); // First pass: find all script tags and their positions while (false !== ($start = stripos($html, $startTag, $start))) { @@ -106,12 +139,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; + } + $prioritScripts[] = $script; + } + $isIgnored = false; foreach ($ignoredStrings as $ignoredString) { if (false !== stripos($script, $ignoredString)) { @@ -148,6 +188,46 @@ public function aroundRenderResult( // Append the remaining HTML after the last script tag $newHtml .= substr($html, $lastPos); + if ($moveToFile) { + $combinedJs = ''; + $externalScriptTags = []; + $allScripts = array_merge($prioritScripts, $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, '') - $openTagEnd - 1)); + + if (!empty($jsContent)) { + $combinedJs .= $jsContent . "\n"; + } + } + + if (!empty($combinedJs)) { + $key = sha1($combinedJs); + $relativePath = 'mfrocketjs/' . $key . '.js'; + $staticDir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); + if (!$staticDir->isExist($relativePath)) { + $staticDir->writeFile($relativePath, $combinedJs); + } + $staticUrl = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_STATIC); + $scripts = array_merge($externalScriptTags, ['']); + } + } + // Append the scripts before the closing tag or at the end $allScripts = implode(PHP_EOL, $scripts); $bodyEndPos = stripos($newHtml, ''); @@ -162,6 +242,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(); diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index a462f1c..0d6ca3c 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -60,6 +60,14 @@ data-rocketjavascript="false" will automatically be ignored. Example <script data-rocketjavascript="false">/* some script *</script>]]> + + + + 1 + + Extract inline scripts from the HTML body and combine them into a separate JavaScript file. Helps reduce DOM size and improves page parsing performance. + Magento\Config\Model\Config\Source\Yesno + diff --git a/etc/config.xml b/etc/config.xml index 6ccc965..aa871dd 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -20,6 +20,7 @@ onestepcheckout/* www.googletagmanager.com + 0 1 From c910f1337e5743ecb4ffc9b064952d5686c5eca4 Mon Sep 17 00:00:00 2001 From: "ivan.hrytsai" Date: Wed, 20 May 2026 12:28:55 +0300 Subject: [PATCH 2/6] 15449-New-Option-in-RocketJavaScript --- Model/Controller/ResultPlugin.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Model/Controller/ResultPlugin.php b/Model/Controller/ResultPlugin.php index 25067e0..5fd1248 100644 --- a/Model/Controller/ResultPlugin.php +++ b/Model/Controller/ResultPlugin.php @@ -12,6 +12,8 @@ use Magento\Framework\App\Response\Http as ResponseHttp; use Magento\Framework\Filesystem; use Magento\Framework\UrlInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\PageCache\Model\Config; /** * Plugin for processing relocation of javascript @@ -47,12 +49,12 @@ class ResultPlugin private $filesystem; /** - * @var \Magento\PageCache\Model\Config + * @var Config */ private $pageCacheConfig; /** - * @var \Magento\Framework\View\LayoutInterface + * @var LayoutInterface */ private $layout; @@ -62,31 +64,26 @@ class ResultPlugin * @param \Magefan\RocketJavaScript\Model\Config $config * @param Filesystem $filesystem * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager - * @param \Magento\PageCache\Model\Config|null $pageCacheConfig - * @param \Magento\Framework\View\LayoutInterface|null $layout + * @param Config|null $pageCacheConfig + * @param LayoutInterface|null $layout */ public function __construct( \Magento\Framework\App\RequestInterface $request, \Magefan\RocketJavaScript\Model\Config $config, Filesystem $filesystem, + Config $pageCacheConfig, + LayoutInterface $layout, ?\Magento\Store\Model\StoreManagerInterface $storeManager = null, - ?\Magento\PageCache\Model\Config $pageCacheConfig = null, - ?\Magento\Framework\View\LayoutInterface $layout = 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->pageCacheConfig = $pageCacheConfig ?: $objectManager->get( - \Magento\PageCache\Model\Config::class - ); - $this->layout = $layout ?: $objectManager->get( - \Magento\Framework\View\LayoutInterface::class - ); } /** @@ -212,6 +209,9 @@ public function aroundRenderResult( $jsContent = trim(substr($script, $openTagEnd + 1, strrpos($script, '') - $openTagEnd - 1)); if (!empty($jsContent)) { + if (!str_ends_with($jsContent, ';')) { + $jsContent .= ';'; + } $combinedJs .= $jsContent . "\n"; } } From 985cc2df898fd40c17c5ba3346c758bbf8a218b7 Mon Sep 17 00:00:00 2001 From: "ivan.hrytsai" Date: Wed, 20 May 2026 13:03:32 +0300 Subject: [PATCH 3/6] 15449-New-Option-in-RocketJavaScript --- Model/Controller/ResultPlugin.php | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Model/Controller/ResultPlugin.php b/Model/Controller/ResultPlugin.php index 5fd1248..bfc9bbc 100644 --- a/Model/Controller/ResultPlugin.php +++ b/Model/Controller/ResultPlugin.php @@ -58,14 +58,26 @@ class ResultPlugin */ private $layout; + /** + * @var \Magento\Framework\View\DesignInterface + */ + private $design; + + /** + * @var \Magento\Framework\Locale\ResolverInterface + */ + private $localeResolver; + /** * 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 Config|null $pageCacheConfig - * @param LayoutInterface|null $layout + * @param \Magento\Framework\View\DesignInterface|null $design + * @param \Magento\Framework\Locale\ResolverInterface|null $localeResolver */ public function __construct( \Magento\Framework\App\RequestInterface $request, @@ -74,6 +86,8 @@ public function __construct( Config $pageCacheConfig, LayoutInterface $layout, ?\Magento\Store\Model\StoreManagerInterface $storeManager = null, + ?\Magento\Framework\View\DesignInterface $design = null, + ?\Magento\Framework\Locale\ResolverInterface $localeResolver = null ) { $this->request = $request; $this->config = $config; @@ -84,6 +98,12 @@ public function __construct( $this->storeManager = $storeManager ?: $objectManager->get( \Magento\Store\Model\StoreManagerInterface::class ); + $this->design = $design ?: $objectManager->get( + \Magento\Framework\View\DesignInterface::class + ); + $this->localeResolver = $localeResolver ?: $objectManager->get( + \Magento\Framework\Locale\ResolverInterface::class + ); } /** @@ -218,7 +238,9 @@ public function aroundRenderResult( if (!empty($combinedJs)) { $key = sha1($combinedJs); - $relativePath = 'mfrocketjs/' . $key . '.js'; + $themePath = $this->design->getDesignTheme()->getThemePath() ?: 'Magento/blank'; + $locale = $this->localeResolver->getLocale(); + $relativePath = 'frontend/' . $themePath . '/' . $locale . '/mfrocketjs/' . $key . '.js'; $staticDir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); if (!$staticDir->isExist($relativePath)) { $staticDir->writeFile($relativePath, $combinedJs); From 916bceb50f5d95177379b9fa89c87b15ba0d254a Mon Sep 17 00:00:00 2001 From: "ivan.hrytsai" Date: Wed, 20 May 2026 14:32:02 +0300 Subject: [PATCH 4/6] 15449-New-Option-in-RocketJavaScript --- Model/Controller/ResultPlugin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Model/Controller/ResultPlugin.php b/Model/Controller/ResultPlugin.php index bfc9bbc..5be4678 100644 --- a/Model/Controller/ResultPlugin.php +++ b/Model/Controller/ResultPlugin.php @@ -136,7 +136,7 @@ public function aroundRenderResult( $html = $response->getBody(); $scripts = []; $positions = []; - $prioritScripts = []; + $priorityScripts = []; $startTag = ''); $openTag = false !== $openTagEnd ? substr($script, 0, $openTagEnd + 1) : $script; From eebfa256c2aa859ba26148b1e0b12f2a1fc71133 Mon Sep 17 00:00:00 2001 From: "ivan.hrytsai" Date: Wed, 20 May 2026 15:38:22 +0300 Subject: [PATCH 5/6] 15449-New-Option-in-RocketJavaScript --- Model/Controller/ResultPlugin.php | 32 ++++++++++--------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/Model/Controller/ResultPlugin.php b/Model/Controller/ResultPlugin.php index 5be4678..eb54607 100644 --- a/Model/Controller/ResultPlugin.php +++ b/Model/Controller/ResultPlugin.php @@ -11,7 +11,6 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\Response\Http as ResponseHttp; use Magento\Framework\Filesystem; -use Magento\Framework\UrlInterface; use Magento\Framework\View\LayoutInterface; use Magento\PageCache\Model\Config; @@ -59,14 +58,9 @@ class ResultPlugin private $layout; /** - * @var \Magento\Framework\View\DesignInterface + * @var \Magento\Framework\View\Asset\Repository */ - private $design; - - /** - * @var \Magento\Framework\Locale\ResolverInterface - */ - private $localeResolver; + private $assetRepository; /** * ResultPlugin constructor. @@ -76,8 +70,7 @@ class ResultPlugin * @param Config $pageCacheConfig * @param LayoutInterface $layout * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager - * @param \Magento\Framework\View\DesignInterface|null $design - * @param \Magento\Framework\Locale\ResolverInterface|null $localeResolver + * @param \Magento\Framework\View\Asset\Repository|null $assetRepository */ public function __construct( \Magento\Framework\App\RequestInterface $request, @@ -86,8 +79,7 @@ public function __construct( Config $pageCacheConfig, LayoutInterface $layout, ?\Magento\Store\Model\StoreManagerInterface $storeManager = null, - ?\Magento\Framework\View\DesignInterface $design = null, - ?\Magento\Framework\Locale\ResolverInterface $localeResolver = null + ?\Magento\Framework\View\Asset\Repository $assetRepository = null ) { $this->request = $request; $this->config = $config; @@ -98,11 +90,8 @@ public function __construct( $this->storeManager = $storeManager ?: $objectManager->get( \Magento\Store\Model\StoreManagerInterface::class ); - $this->design = $design ?: $objectManager->get( - \Magento\Framework\View\DesignInterface::class - ); - $this->localeResolver = $localeResolver ?: $objectManager->get( - \Magento\Framework\Locale\ResolverInterface::class + $this->assetRepository = $assetRepository ?: $objectManager->get( + \Magento\Framework\View\Asset\Repository::class ); } @@ -144,6 +133,7 @@ public function aroundRenderResult( $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))) { @@ -238,15 +228,13 @@ public function aroundRenderResult( if (!empty($combinedJs)) { $key = sha1($combinedJs); - $themePath = $this->design->getDesignTheme()->getThemePath() ?: 'Magento/blank'; - $locale = $this->localeResolver->getLocale(); - $relativePath = 'frontend/' . $themePath . '/' . $locale . '/mfrocketjs/' . $key . '.js'; + $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); } - $staticUrl = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_STATIC); - $scripts = array_merge($externalScriptTags, ['']); + $scripts = array_merge($externalScriptTags, ['']); } } From 57bf18726eb4ab41476e3a99d65b6b934e776f83 Mon Sep 17 00:00:00 2001 From: "ivan.hrytsai" Date: Mon, 25 May 2026 16:44:24 +0300 Subject: [PATCH 6/6] remove test params --- Model/Controller/ResultPlugin.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Model/Controller/ResultPlugin.php b/Model/Controller/ResultPlugin.php index eb54607..1aec143 100644 --- a/Model/Controller/ResultPlugin.php +++ b/Model/Controller/ResultPlugin.php @@ -133,7 +133,6 @@ public function aroundRenderResult( $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))) {