JavaScript: Also optimize `define()` calls in module JS

This commit is contained in:
Johannes Meyer 2020-11-12 15:36:31 +01:00
parent 19e4eb6e1e
commit e9fa9d95dd
1 changed files with 104 additions and 81 deletions

View File

@ -68,67 +68,72 @@ class JavaScript
{ {
header('Content-Type: application/javascript'); header('Content-Type: application/javascript');
$basedir = Icinga::app()->getBootstrapDirectory(); $basedir = Icinga::app()->getBootstrapDirectory();
$moduleManager = Icinga::app()->getModuleManager();
$files = [];
$js = $out = ''; $js = $out = '';
$min = $minified ? '.min' : ''; $min = $minified ? '.min' : '';
// Prepare vendor file list // Prepare vendor file list
$vendorFiles = []; $vendorFiles = [];
foreach (self::$vendorFiles as $file) { foreach (self::$vendorFiles as $file) {
$vendorFiles[] = $basedir . '/' . $file . $min . '.js'; $filePath = $basedir . '/' . $file . $min . '.js';
$vendorFiles[] = $filePath;
$files[] = $filePath;
} }
// Prepare base file list // Prepare base file list
$baseFiles = []; $baseFiles = [];
foreach (self::$baseFiles as $file) { foreach (self::$baseFiles as $file) {
$baseFiles[] = $basedir . '/' . $file; $filePath = $basedir . '/' . $file;
$baseFiles[] = $filePath;
$files[] = $filePath;
} }
// Prepare library file list // Prepare library file list
$libraryFiles = [];
foreach (Icinga::app()->getLibraries() as $library) { foreach (Icinga::app()->getLibraries() as $library) {
$libraryFiles = array_merge( $files = array_merge($files, $library->getJsAssets());
$libraryFiles,
$library->getJsAssets()
);
} }
// Prepare Icinga JS file list // Prepare core file list
$jsFiles = []; $coreFiles = [];
foreach (self::$jsFiles as $file) { foreach (self::$jsFiles as $file) {
$jsFiles[] = $basedir . '/' . $file; $filePath = $basedir . '/' . $file;
$coreFiles[] = $filePath;
$files[] = $filePath;
} }
$sharedFiles = []; $moduleFiles = [];
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) { foreach ($moduleManager->getLoadedModules() as $name => $module) {
if ($module->hasJs()) { if ($module->hasJs()) {
$jsDir = $module->getJsDir();
foreach ($module->getJsFiles() as $path) { foreach ($module->getJsFiles() as $path) {
if (file_exists($path)) { if (file_exists($path)) {
$jsFiles[] = $path; $moduleFiles[$name][$jsDir][] = $path;
$files[] = $path;
} }
} }
} }
if ($module->requiresJs()) { $assetDir = $module->getJsAssetDir();
foreach ($module->getJsRequires() as $path) { foreach ($module->getJsAssets() as $path) {
$sharedFiles[] = $path; $moduleFiles[$name][$assetDir][] = $path;
$files[] = $path;
} }
} }
}
$sharedFiles = array_unique($sharedFiles);
$files = array_merge($vendorFiles, $baseFiles, $libraryFiles, $jsFiles, $sharedFiles);
$request = Icinga::app()->getRequest(); $request = Icinga::app()->getRequest();
$noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache'; $noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache';
header('Cache-Control: public'); header('Cache-Control: public');
if (! $noCache && FileCache::etagMatchesFiles($files)) { if (! $noCache && FileCache::etagMatchesFiles($files)) {
header("HTTP/1.1 304 Not Modified"); header("HTTP/1.1 304 Not Modified");
return; return;
} else { } else {
$etag = FileCache::etagForFiles($files); $etag = FileCache::etagForFiles($files);
} }
header('ETag: "' . $etag . '"'); header('ETag: "' . $etag . '"');
header('Content-Type: application/javascript'); header('Content-Type: application/javascript');
@ -151,27 +156,75 @@ class JavaScript
// Library files need to be namespaced first before they can be included // Library files need to be namespaced first before they can be included
foreach (Icinga::app()->getLibraries() as $library) { foreach (Icinga::app()->getLibraries() as $library) {
foreach ($library->getJsAssets() as $file) { foreach ($library->getJsAssets() as $file) {
$content = file_get_contents($file) . "\n\n\n"; $js .= self::optimizeDefine(
if (preg_match(self::DEFINE_RE, $content, $match)) { file_get_contents($file),
$file,
$library->getJsAssetPath(),
$library->getName()
) . "\n\n\n";
}
}
foreach ($coreFiles as $file) {
$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";
}
}
}
}
if ($minified) {
require_once 'JShrink/Minifier.php';
$out .= Minifier::minify($js, array('flaggedComments' => false));
} 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 { try {
$assetName = $match[1] ? Json::decode($match[1]) : ''; $assetName = $match[1] ? Json::decode($match[1]) : '';
if (! $assetName) { if (! $assetName) {
$assetName = explode('.', basename($file))[0]; $assetName = explode('.', basename($filePath))[0];
} }
$assetName = join(DIRECTORY_SEPARATOR, array_filter([ $assetName = join(DIRECTORY_SEPARATOR, array_filter([
$library->getName(), $packageName,
ltrim(substr(dirname($file), strlen($library->getJsAssetPath())), DIRECTORY_SEPARATOR), ltrim(substr(dirname($filePath), strlen($basePath)), DIRECTORY_SEPARATOR),
$assetName $assetName
])); ]));
$assetName = Json::encode($assetName, JSON_UNESCAPED_SLASHES); $assetName = Json::encode($assetName, JSON_UNESCAPED_SLASHES);
} catch (JsonDecodeException $_) { } catch (JsonDecodeException $_) {
$assetName = $match[1]; $assetName = $match[1];
Logger::error( Logger::error('Can\'t optimize name of "%s". Are single quotes used instead of double quotes?', $filePath);
'Can\'t optimize name of "%s". Are single quotes used instead of double quotes?',
$file
);
} }
try { try {
@ -179,10 +232,10 @@ class JavaScript
foreach ($dependencies as &$dependencyName) { foreach ($dependencies as &$dependencyName) {
if (preg_match('~^((?:\.\.?/)+)*(.*)~', $dependencyName, $natch)) { if (preg_match('~^((?:\.\.?/)+)*(.*)~', $dependencyName, $natch)) {
$dependencyName = join(DIRECTORY_SEPARATOR, array_filter([ $dependencyName = join(DIRECTORY_SEPARATOR, array_filter([
$library->getName(), $packageName,
ltrim(substr( ltrim(substr(
realpath(join(DIRECTORY_SEPARATOR, [dirname($file), $natch[1]])), realpath(join(DIRECTORY_SEPARATOR, [dirname($filePath), $natch[1]])),
strlen(realpath($library->getJsAssetPath())) strlen(realpath($basePath))
), DIRECTORY_SEPARATOR), ), DIRECTORY_SEPARATOR),
$natch[2] $natch[2]
])); ]));
@ -194,40 +247,10 @@ class JavaScript
$dependencies = $match[2]; $dependencies = $match[2];
Logger::error( Logger::error(
'Can\'t optimize dependencies of "%s". Are single quotes used instead of double quotes?', 'Can\'t optimize dependencies of "%s". Are single quotes used instead of double quotes?',
$file $filePath
); );
} }
$content = str_replace( return str_replace($match[0], sprintf("define(%s, %s, %s", $assetName, $dependencies, $match[3]), $js);
$match[0],
sprintf("define(%s, %s, %s", $assetName, $dependencies, $match[3]),
$content
);
}
$js .= $content;
}
}
foreach ($jsFiles 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";
}
}
if ($minified) {
require_once 'JShrink/Minifier.php';
$out .= Minifier::minify($js, array('flaggedComments' => false));
} else {
$out .= $js;
}
$cache->store($cacheFile, $out);
echo $out;
} }
} }