. * */ /* Name: Minify css and js Slug: minify Category: performance Url: https://www.vvveb.com Description: Minify javascript and css for better frontend performance. Author: givanz Version: 0.1 Thumb: minify.svg Author url: https://www.vvveb.com */ use function Vvveb\__; use function Vvveb\isEditor; use function Vvveb\sanitizeFileName; use function Vvveb\slugify; use Vvveb\System\Event; if (! defined('V_VERSION')) { die('Invalid request!'); } define('MINIFY_JS', true); define('MINIFY_CSS', true); class MinifyPlugin { //load minifier files only when minifying to avoid bloat loading them on every page request function initMinifier() { if (MINIFY_JS || MINIFY_CSS) { $path = __DIR__ . DS . 'system' . DS; require_once $path . '/minify/Minify.php'; require_once $path . '/minify/CSS.php'; require_once $path . '/minify/JS.php'; require_once $path . '/minify/Exception.php'; require_once $path . '/minify/Exceptions/BasicException.php'; require_once $path . '/minify/Exceptions/FileImportException.php'; require_once $path . '/minify/Exceptions/IOException.php'; require_once $path . '/minify/ConverterInterface.php'; require_once $path . '/minify/Converter.php'; } } function admin() { //add admin menu item $admin_path = \Vvveb\adminPath(); Event::on('Vvveb\Controller\Base', 'init-menu', __CLASS__, function ($menu) use ($admin_path) { $menu['plugins']['items']['minify-plugin'] = [ 'name' => __('Minify'), 'url' => '/admin/', 'icon-img' => PUBLIC_PATH . 'plugins/minify/minify.svg', ]; return [$menu]; }); } function minifyJs($js, $path = '') { $minifier = new MatthiasMullie\Minify\JS(); $minifier->add($js); return $minifier->minify(); } function minifyCss($css, $path = '') { $minifier = new MatthiasMullie\Minify\CSS(); $minifier->add($css); return $minifier->minify(); } function processJs(&$vTpl, $template, $theme, $prefixSelector = 'head > ') { $inlineJs = ''; /* $scripts = $vTpl->query($prefixSelector . 'script'); foreach ($scripts as $style) { $inlineCss .= trim($style->textContent); } */ $scripts = $vTpl->query($prefixSelector . 'script'); $length = $scripts->length; if ($length > 1) { $fileName = $theme . '-'; $content = ''; $names = []; $customCss = ''; $moveInlineBottom = true; $document = $vTpl->getDocument(); if (MINIFY_JS) { $minifier = new MatthiasMullie\Minify\JS(); } $i = 0; $lastScript = null; $removeScripts = []; foreach ($scripts as $link) { $href = $link->getAttribute('src'); //skip external js if (strncmp($href, 'http', 4) === 0) { $length--; continue; } //skip no minify if ($link->hasAttribute('data-no-minify')) { $length--; continue; } //skip inline if (! $link->hasAttribute('src')) { if ($moveInlineBottom) { $body = $document->getElementsByTagName('body')->item(0); $body->appendChild($link); } $length--; continue; } //skip module if ($link->hasAttribute('type') && (($type = $link->getAttribute('type') == 'module') || ($type = 'application/ld+json') || ($type = 'speculationrules')) ) { $length--; continue; } $i++; $lastScript = $link; $path = DIR_THEMES . $theme . DS; $href = str_replace(PUBLIC_PATH . $theme . '/', '', $href); //absolute path or upper level then use public folder if ($href && ($href[0] == '/' || strncmp($href, '../../', 6) === 0)) { $path = DIR_PUBLIC; } $file = $path . sanitizeFileName($href); if (! file_exists($file)) { $i--; continue; } if (MINIFY_JS) { //$styles = $this->minifyJs($styles, $href); $minifier->add($file); } else { //$content .= "\n/* $href */\n\n" . $styles; $styles = file_get_contents($file); $content .= "\n" . $styles; } $names[] = slugify(basename(str_replace('.min', '', $href), '.js')); //replace last link with minified css if ($i >= $length) { } else { //$link->remove(); } $removeScripts[] = $link; } if ($lastScript) { sort($names); $fileName .= implode('-', $names) . '.js'; if ($inlineJs) { //$content .= "\n/* -- inline - */\n\n" . $inlineJs; $content .= "\n" . $inlineJs; } //if (file_put_contents(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName, $content)) { if ($lastScript && $lastScript->parentNode && $minifier->minify(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName)) { $minified = $document->createElement('script'); $minified->setAttribute('src', '/assets-cache/' . $fileName); $lastScript->parentNode->replaceChild($minified, $lastScript); //if minified succeeded remove all scripts foreach ($removeScripts as $script) { if (! $script->isSameNode($lastScript)) { //$script->remove(); $script->parentNode->removeChild($script); } } //remove inline js /* $inlineCss = $vTpl->query($prefixSelector . 'script'); foreach ($inlineCss as $style) { $style->remove(); }*/ } } } } function processCss(&$vTpl, $template, $theme, $prefixSelector = 'head > ') { $css = $vTpl->query($prefixSelector . 'style'); $inlineCss = ''; foreach ($css as $style) { $inlineCss .= trim($style->textContent); } $css = $vTpl->query($prefixSelector . 'link[rel="stylesheet"]'); $length = $css->length; if ($length > 1) { $fileName = $theme . '-'; $content = ''; $names = []; $customCss = ''; $document = $vTpl->getDocument(); if (MINIFY_CSS) { $minifier = new MatthiasMullie\Minify\CSS(); } $i = 0; $lastStyle = null; foreach ($css as $link) { $href = $link->getAttribute('href'); //skip external css if (! $href || (strncmp($href, 'http', 4) === 0)) { $length--; continue; } //skip no minify if ($link->hasAttribute('data-no-minify')) { $length--; continue; } $i++; $path = DIR_THEMES . $theme . DS; $href = str_replace(PUBLIC_PATH . $theme . '/', '', $href); //absolute path or upper level then use public folder if ($href && ($href[0] == '/' || strncmp($href, '../../', 6) === 0)) { $path = DIR_PUBLIC; } $file = $path . sanitizeFileName($href); if (! file_exists($file)) { $i--; continue; } if (MINIFY_CSS) { //$styles = $this->minifyJs($styles, $href); $minifier->add($file); } else { //$content .= "\n/* $href */\n\n" . $styles; $styles = file_get_contents($file); } //add custom css last for highest priority if (substr_compare($href, 'custom.css', -10, 10) === 0) { if (file_exists($file)) { $customCss = file_get_contents($file); } } else { //$content .= "\n/* $href */\n\n" . $styles; if (! MINIFY_CSS) { $content .= "\n" . $styles; } } $names[] = slugify(basename(str_replace('.min', '', $href), '.css')); $lastStyle = $link; //replace last link with minified css if ($i >= ($length - 1)) { } else { //$link->remove(); $link->parentNode->removeChild($link); } } if ($lastStyle/* && ($i >= $length)*/) { sort($names); $fileName .= implode('-', $names) . '.css'; if ($inlineCss) { if (MINIFY_CSS) { $minifier->add($inlineCss); } else { //$content .= "\n/* -- inline - */\n\n" . $inlineCss; $content .= "\n" . $inlineCss; } } if ($customCss) { if (MINIFY_CSS) { $minifier->add($inlineCss); } else { //$content .= "\n/* -- custom.css - */\n\n" . $customCss; $content .= "\n" . $customCss; } } //if (file_put_contents(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName, $content)) { if ($lastStyle && $lastStyle->parentNode && $minifier->minify(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName)) { $minified = $document->createElement('link'); $minified->setAttribute('rel','stylesheet'); $minified->setAttribute('href', '/assets-cache/' . $fileName); $lastStyle->parentNode->replaceChild($minified, $lastStyle); //remove inline css $inlineCss = $vTpl->query($prefixSelector . 'style'); foreach ($inlineCss as $style) { //$style->remove(); $style->parentNode->removeChild($style); } } } } } function app() { //don't minify if page is opened in editor if (isEditor()) { return; } Event::on('Vvveb\System\Core\View', 'compile:after', __CLASS__, function ($template, $htmlFile, $tplFile, $vTpl, $view) { $theme = $view->getTheme(); $this->initMinifier(); $this->processCss($vTpl, $template, $theme); $this->processCss($vTpl, $template, $theme, 'body > '); $this->processJs($vTpl, $template, $theme); $this->processJs($vTpl, $template, $theme, 'body > '); return [$template, $htmlFile, $tplFile, $vTpl, $view]; }); } function __construct() { if (APP == 'admin') { $this->admin(); } else { if (APP == 'app') { $this->app(); } } } } $minifyPlugin = new MinifyPlugin();