From e9fa9d95dd2b3ff36b32718aa64f77c5ec5e9518 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 12 Nov 2020 15:36:31 +0100 Subject: [PATCH] JavaScript: Also optimize `define()` calls in module JS --- library/Icinga/Web/JavaScript.php | 185 +++++++++++++++++------------- 1 file changed, 104 insertions(+), 81 deletions(-) diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php index 56c0bf982..fc081c285 100644 --- a/library/Icinga/Web/JavaScript.php +++ b/library/Icinga/Web/JavaScript.php @@ -68,67 +68,72 @@ class JavaScript { header('Content-Type: application/javascript'); $basedir = Icinga::app()->getBootstrapDirectory(); + $moduleManager = Icinga::app()->getModuleManager(); + $files = []; $js = $out = ''; $min = $minified ? '.min' : ''; // Prepare vendor file list $vendorFiles = []; foreach (self::$vendorFiles as $file) { - $vendorFiles[] = $basedir . '/' . $file . $min . '.js'; + $filePath = $basedir . '/' . $file . $min . '.js'; + $vendorFiles[] = $filePath; + $files[] = $filePath; } // Prepare base file list $baseFiles = []; foreach (self::$baseFiles as $file) { - $baseFiles[] = $basedir . '/' . $file; + $filePath = $basedir . '/' . $file; + $baseFiles[] = $filePath; + $files[] = $filePath; } // Prepare library file list - $libraryFiles = []; foreach (Icinga::app()->getLibraries() as $library) { - $libraryFiles = array_merge( - $libraryFiles, - $library->getJsAssets() - ); + $files = array_merge($files, $library->getJsAssets()); } - // Prepare Icinga JS file list - $jsFiles = []; + // Prepare core file list + $coreFiles = []; foreach (self::$jsFiles as $file) { - $jsFiles[] = $basedir . '/' . $file; + $filePath = $basedir . '/' . $file; + $coreFiles[] = $filePath; + $files[] = $filePath; } - $sharedFiles = []; - foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) { + $moduleFiles = []; + foreach ($moduleManager->getLoadedModules() as $name => $module) { if ($module->hasJs()) { + $jsDir = $module->getJsDir(); foreach ($module->getJsFiles() as $path) { if (file_exists($path)) { - $jsFiles[] = $path; + $moduleFiles[$name][$jsDir][] = $path; + $files[] = $path; } } } - if ($module->requiresJs()) { - foreach ($module->getJsRequires() as $path) { - $sharedFiles[] = $path; - } + $assetDir = $module->getJsAssetDir(); + foreach ($module->getJsAssets() as $path) { + $moduleFiles[$name][$assetDir][] = $path; + $files[] = $path; } } - $sharedFiles = array_unique($sharedFiles); - $files = array_merge($vendorFiles, $baseFiles, $libraryFiles, $jsFiles, $sharedFiles); - $request = Icinga::app()->getRequest(); $noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache'; header('Cache-Control: public'); + if (! $noCache && FileCache::etagMatchesFiles($files)) { header("HTTP/1.1 304 Not Modified"); return; } else { $etag = FileCache::etagForFiles($files); } + header('ETag: "' . $etag . '"'); header('Content-Type: application/javascript'); @@ -151,73 +156,29 @@ class JavaScript // Library files need to be namespaced first before they can be included foreach (Icinga::app()->getLibraries() as $library) { foreach ($library->getJsAssets() as $file) { - $content = file_get_contents($file) . "\n\n\n"; - if (preg_match(self::DEFINE_RE, $content, $match)) { - try { - $assetName = $match[1] ? Json::decode($match[1]) : ''; - if (! $assetName) { - $assetName = explode('.', basename($file))[0]; - } - - $assetName = join(DIRECTORY_SEPARATOR, array_filter([ - $library->getName(), - ltrim(substr(dirname($file), strlen($library->getJsAssetPath())), DIRECTORY_SEPARATOR), - $assetName - ])); - - $assetName = Json::encode($assetName, JSON_UNESCAPED_SLASHES); - } catch (JsonDecodeException $_) { - $assetName = $match[1]; - Logger::error( - 'Can\'t optimize name of "%s". Are single quotes used instead of double quotes?', - $file - ); - } - - try { - $dependencies = $match[2] ? Json::decode($match[2]) : []; - foreach ($dependencies as &$dependencyName) { - if (preg_match('~^((?:\.\.?/)+)*(.*)~', $dependencyName, $natch)) { - $dependencyName = join(DIRECTORY_SEPARATOR, array_filter([ - $library->getName(), - ltrim(substr( - realpath(join(DIRECTORY_SEPARATOR, [dirname($file), $natch[1]])), - strlen(realpath($library->getJsAssetPath())) - ), DIRECTORY_SEPARATOR), - $natch[2] - ])); - } - } - - $dependencies = Json::encode($dependencies, JSON_UNESCAPED_SLASHES); - } catch (JsonDecodeException $_) { - $dependencies = $match[2]; - Logger::error( - 'Can\'t optimize dependencies of "%s". Are single quotes used instead of double quotes?', - $file - ); - } - - $content = str_replace( - $match[0], - sprintf("define(%s, %s, %s", $assetName, $dependencies, $match[3]), - $content - ); - } - - $js .= $content; + $js .= self::optimizeDefine( + file_get_contents($file), + $file, + $library->getJsAssetPath(), + $library->getName() + ) . "\n\n\n"; } } - foreach ($jsFiles as $file) { + foreach ($coreFiles as $file) { $js .= file_get_contents($file) . "\n\n\n"; } - foreach ($sharedFiles as $file) { - if (substr($file, -7, 7) === '.min.js') { - $out .= ';' . ltrim(trim(file_get_contents($file)), ';') . "\n"; - } else { - $js .= file_get_contents($file) . "\n\n\n"; + foreach ($moduleFiles as $name => $paths) { + foreach ($paths as $basePath => $filePaths) { + foreach ($filePaths as $file) { + $content = self::optimizeDefine(file_get_contents($file), $file, $basePath, $name); + if (substr($file, -7, 7) === '.min.js') { + $out .= ';' . ltrim(trim($content), ';') . "\n"; + } else { + $js .= $content . "\n\n\n"; + } + } } } @@ -227,7 +188,69 @@ class JavaScript } else { $out .= $js; } + $cache->store($cacheFile, $out); echo $out; } + + /** + * Optimize define() calls in the given JS + * + * @param string $js + * @param string $filePath + * @param string $basePath + * @param string $packageName + * + * @return string + */ + public static function optimizeDefine($js, $filePath, $basePath, $packageName) + { + if (! preg_match(self::DEFINE_RE, $js, $match)) { + return $js; + } + + try { + $assetName = $match[1] ? Json::decode($match[1]) : ''; + if (! $assetName) { + $assetName = explode('.', basename($filePath))[0]; + } + + $assetName = join(DIRECTORY_SEPARATOR, array_filter([ + $packageName, + ltrim(substr(dirname($filePath), strlen($basePath)), DIRECTORY_SEPARATOR), + $assetName + ])); + + $assetName = Json::encode($assetName, JSON_UNESCAPED_SLASHES); + } catch (JsonDecodeException $_) { + $assetName = $match[1]; + Logger::error('Can\'t optimize name of "%s". Are single quotes used instead of double quotes?', $filePath); + } + + try { + $dependencies = $match[2] ? Json::decode($match[2]) : []; + foreach ($dependencies as &$dependencyName) { + if (preg_match('~^((?:\.\.?/)+)*(.*)~', $dependencyName, $natch)) { + $dependencyName = join(DIRECTORY_SEPARATOR, array_filter([ + $packageName, + ltrim(substr( + realpath(join(DIRECTORY_SEPARATOR, [dirname($filePath), $natch[1]])), + strlen(realpath($basePath)) + ), DIRECTORY_SEPARATOR), + $natch[2] + ])); + } + } + + $dependencies = Json::encode($dependencies, JSON_UNESCAPED_SLASHES); + } catch (JsonDecodeException $_) { + $dependencies = $match[2]; + Logger::error( + 'Can\'t optimize dependencies of "%s". Are single quotes used instead of double quotes?', + $filePath + ); + } + + return str_replace($match[0], sprintf("define(%s, %s, %s", $assetName, $dependencies, $match[3]), $js); + } }