From 8c9034b3e980d343f21bbc38cb031bf0ca7f3552 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 11 Jan 2014 18:49:51 +0000 Subject: [PATCH] using now 3 minified and concatinated javascript file-bundles: 1. api: egw, jquery, old jsapi and egw_json plus its dependences 2. et2: etemplate2.js plus dependencies 3. jdots: files from Stylite or new pixelegg template all other javascript files are loaded on there own. Bundle-configuration is dynamicly created and cached. EGw configuration allows to disable minifying and concatination of javascript and css files for deverloping purpose or to just concatinate but not minify them aka "debug". --- phpgwapi/inc/class.egw_framework.inc.php | 151 +++++++++++++++++---- phpgwapi/inc/class.egw_include_mgr.inc.php | 2 + 2 files changed, 128 insertions(+), 25 deletions(-) diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php index 4ac9141ef5..2bfcac924b 100644 --- a/phpgwapi/inc/class.egw_framework.inc.php +++ b/phpgwapi/inc/class.egw_framework.inc.php @@ -993,7 +993,7 @@ abstract class egw_framework self::includeCSS('/phpgwapi/js/jquery/chosen/chosen.css'); // eTemplate2 uses jQueryUI, so load it first so et2 can override if needed - egw_framework::includeCSS("/phpgwapi/js/jquery/jquery-ui/redmond/jquery-ui-1.10.3.custom.css"); + self::includeCSS("/phpgwapi/js/jquery/jquery-ui/redmond/jquery-ui-1.10.3.custom.css"); // eTemplate2 - load in top so sidebox has styles too self::includeCSS('/etemplate/templates/default/etemplate2.css'); @@ -1023,7 +1023,6 @@ abstract class egw_framework $print_css = '/phpgwapi/templates/idots/print.css'; } - // search for app specific css file self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app'); @@ -1597,18 +1596,96 @@ abstract class egw_framework */ static public function get_script_links($return_pathes=false, $clear_files=false) { - // RB: disabled minifying (debug=true), 'til I found time to fix it + $to_include = self::bundle_js_includes(self::$js_include_mgr->get_included_files($clear_files)); + + if ($return_pathes) + { + return $to_include; + } + $start = '\n"; + return "\n".$start.implode($end.$start, $to_include).$end; + } + + /** + * Devide js-includes in bundles of javascript files to include eg. api or etemplate2, if minifying is enabled + * + * @param array $js_includes files to include with egw relative url + * @return array egw relative urls to include incl. bundels/minify urls, if enabled + */ + public static function bundle_js_includes(array $js_includes) + { + if ($GLOBALS['egw_info']['server']['debug_minify'] === 'True') + { + return $js_includes; // nothing to do, just return all single files + } + // get used bundles and cache them on tree-level for 2h + $bundles = egw_cache::getTree(__CLASS__, 'bundles', array(__CLASS__, 'get_bundles'), array(), 7200); + $bundles_ts = $bundles['.ts']; + unset($bundles['.ts']); + $file2bundle = array(); + foreach($bundles as $name => $files) + { + $file2bundle += array_combine($files, array_fill(0, count($files), $name)); + } + + $to_include = $included_bundles = array(); + foreach($js_includes as $file) + { + if (!isset($to_include[$file])) + { + if (($bundle = $file2bundle[$file])) + { + if (!in_array($bundle, $included_bundles)) + { + $max_modified = 0; + $to_include = array_merge($to_include, self::bundle_urls($bundles[$bundle], $max_modified)); + $included_bundles[] = $bundle; + // check if bundle-config is more recent then + if ($max_modified > $bundles_ts) + { + // force new bundle config by deleting cached one and call ourself again + egw_cache::unsetTree(__CLASS__, 'bundles'); + return self::bundle_js_includes($js_includes); + } + } + } + else + { + $query = ''; + list($path, $query) = explode('?', $file, 2); + $mod = filemtime(EGW_SERVER_ROOT.$path); + + $to_include[$file] = $path.'?'.$mod.($query ? '&'.$query : ''); + } + } + } + /*_debug_array($js_includes); + _debug_array(array_values($to_include)); + die('STOP');*/ + + return array_values($to_include); + } + + /** + * Generate bundle url(s) for given js files + * + * @param array $js_includes + * @param int& $max_modified=null on return maximum modification time of bundle + * @return array js-files (can be more then one, if one of given files can not be bundeled) + */ + protected static function bundle_urls(array $js_includes, &$max_modified=null) + { $debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True'; - $files = ''; $to_include = $to_minify = array(); $max_modified = 0; - foreach(self::$js_include_mgr->get_included_files($clear_files) as $path) + foreach($js_includes as $path) { if ($path == '/phpgwapi/js/jsapi/egw.js') continue; // loaded via own tag, and we must not load it twice! $query = ''; list($path,$query) = explode('?',$path,2); - if (($mod = filemtime(EGW_SERVER_ROOT.$path)) > $max_modified) $max_modified = $mod; + $mod = filemtime(EGW_SERVER_ROOT.$path); // for now minify does NOT support query parameters, nor php files generating javascript if ($debug_minify || $query || substr($path, -3) != '.js' || strpos($path,'ckeditor') !== false || @@ -1619,6 +1696,7 @@ abstract class egw_framework } else { + if ($mod > $max_modified) $max_modified = $mod; $to_minify[] = substr($path,1); } } @@ -1633,23 +1711,48 @@ abstract class egw_framework // need to include minified javascript before not minified stuff like jscalendar-setup, as it might depend on it array_unshift($to_include, $path); } - if ($return_pathes) - { - return $to_include; - } - $start = '\n"; - return "\n".$start.implode($end.$start, $to_include).$end; + //error_log(__METHOD__."(".array2string($js_includes).") returning ".array2string($to_include)); + return $to_include; + } - // using LABjs to load all javascript would require all other script-tags to run in wait() of queue! - /* - return "\n".$start.'/phpgwapi/js/labjs/LAB.src.js'.$end."\n". - ' -'; - */ + /** + * Return typical bundes we use: + * - api stuff phpgwapi/js/jsapi/* and it's dependencies incl. jquery + * - etemplate2 stuff not including api bundle, but jquery-ui + * + * @return array bundle-url => array of contained files + */ + public static function get_bundles() + { + $inc_mgr = new egw_include_mgr(); + $bundles = array(); + + // generate api bundle + $inc_mgr->include_js_file('/phpgwapi/js/jquery/jquery.js'); + $inc_mgr->include_js_file('/phpgwapi/js/jsapi/jsapi.js'); + $inc_mgr->include_js_file('/phpgwapi/js/egw_json.js'); + $inc_mgr->include_js_file('/phpgwapi/js/jsapi/egw.js'); + $bundles['api'] = $inc_mgr->get_included_files(); + + // generate et2 bundle (excluding files in api bundle) + //$inc_mgr->include_js_file('/etemplate/js/lib/jsdifflib/difflib.js'); // it does not work with "use strict" therefore included in front + $inc_mgr->include_js_file('/etemplate/js/etemplate2.js'); + $bundles['et2'] = array_diff($inc_mgr->get_included_files(), $bundles['api']); + + // generate jdots bundle, if installed + if (file_exists(EGW_SERVER_ROOT.'/jdots')) + { + $inc_mgr->include_js_file('/jdots/js/egw_fw.js'); + $inc_mgr->include_js_file('/jdots/js/egw_fw_ui.js'); + $inc_mgr->include_js_file('/jdots/js/egw_fw_classes.js'); + $bundles['jdots'] = array_diff($inc_mgr->get_included_files(), call_user_func_array('array_merge', $bundles)); + } + + // store timestamp of when bundle-config was created + $bundles['.ts'] = time(); + + error_log(__METHOD__."() returning ".array2string($bundles)); + return $bundles; } /** @@ -1725,11 +1828,9 @@ $LAB.setOptions({AlwaysPreserveOrder:true,BasePath:"'.$GLOBALS['egw_info']['serv // add all js files from egw_framework::validate_file() $files = self::$js_include_mgr->get_included_files(); + $files = self::bundle_js_includes($files); foreach($files as $path) { - $query = ''; - list($path,$query) = explode('?',$path,2); - $path .= '?'. filemtime(EGW_SERVER_ROOT.$path).($query ? '&'.$query : ''); $response->includeScript($GLOBALS['egw_info']['server']['webserver_url'].$path); } } diff --git a/phpgwapi/inc/class.egw_include_mgr.inc.php b/phpgwapi/inc/class.egw_include_mgr.inc.php index 3027590bc0..17fa5fc615 100644 --- a/phpgwapi/inc/class.egw_include_mgr.inc.php +++ b/phpgwapi/inc/class.egw_include_mgr.inc.php @@ -284,6 +284,8 @@ class egw_include_mgr is_readable(EGW_SERVER_ROOT.($path="/$app/js/$package/$file.js")) || $app != 'phpgwapi' && is_readable(EGW_SERVER_ROOT.($path="/phpgwapi/js/$package/$file.js"))) { + // normalise /./ to / + $path = str_replace('/./', '/', $path); // Handle the special case, that the file is an url - in this case // we will do no further processing but just include the file