From 27a90ef6947628703d48d3f2fc565e14f154b8e8 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Thu, 19 Mar 2015 16:31:48 -0700 Subject: [PATCH] CRM-16145 - Civi/Angular - Aggregate JS and CSS files CRM-16145 aims to setup a more intuitive file structure, but the file structure has been constrained by the functional requirement to minimize file-requests. Aggregation frees us to split and reorg files with affecting the number of file-requests. --- Civi/Angular/Manager.php | 60 ++++++++++++---------- Civi/Angular/Page/Main.php | 34 ++++++++---- Civi/Angular/Page/Modules.php | 97 ++++++++++++++++++++++++++++++----- 3 files changed, 142 insertions(+), 49 deletions(-) diff --git a/Civi/Angular/Manager.php b/Civi/Angular/Manager.php index a92ecd9c18..60bfc37170 100644 --- a/Civi/Angular/Manager.php +++ b/Civi/Angular/Manager.php @@ -85,7 +85,7 @@ class Manager { ); $angularModules['crmUi'] = array( 'ext' => 'civicrm', - 'js' => array('js/angular-crm-ui.js', 'packages/ckeditor/ckeditor.js'), + 'js' => array('js/angular-crm-ui.js'), 'partials' => array('partials/crmUi'), ); $angularModules['crmUtil'] = array( @@ -255,36 +255,42 @@ class Manager { } /** - * @param string $name - * Module name. + * Get resources for one or more modules. + * + * @param string|array $moduleNames + * List of module names. + * @param string $resType + * Type of resource ('js', 'css'). + * @param string $refType + * Type of reference to the resource ('cacheUrl', 'rawUrl', 'path'). * @return array - * List of URLs. - * @throws \Exception + * List of URLs or paths. + * @throws \CRM_Core_Exception */ - public function getScriptUrls($name) { - $module = $this->getModule($name); + public function getResources($moduleNames, $resType, $refType) { $result = array(); - if (isset($module['js'])) { - foreach ($module['js'] as $file) { - $result[] = $this->res->getUrl($module['ext'], $file, TRUE); - } - } - return $result; - } + $moduleNames = (array) $moduleNames; + foreach ($moduleNames as $moduleName) { + $module = $this->getModule($moduleName); + if (isset($module[$resType])) { + foreach ($module[$resType] as $file) { + switch ($refType) { + case 'path': + $result[] = $this->res->getPath($module['ext'], $file); + break; - /** - * @param string $name - * Module name. - * @return array - * List of URLs. - * @throws \Exception - */ - public function getStyleUrls($name) { - $module = $this->getModule($name); - $result = array(); - if (isset($module['css'])) { - foreach ($module['css'] as $file) { - $result[] = $this->res->getUrl($module['ext'], $file, TRUE); + case 'rawUrl': + $result[] = $this->res->getUrl($module['ext'], $file); + break; + + case 'cacheUrl': + $result[] = $this->res->getUrl($module['ext'], $file, TRUE); + break; + + default: + throw new \CRM_Core_Exception("Unrecognized resource format"); + } + } } } return $result; diff --git a/Civi/Angular/Page/Main.php b/Civi/Angular/Page/Main.php index 795120f10e..854f022583 100644 --- a/Civi/Angular/Page/Main.php +++ b/Civi/Angular/Page/Main.php @@ -82,17 +82,33 @@ class Main extends \CRM_Core_Page { $this->res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, 'html-header', FALSE); $this->res->addScriptFile('civicrm', 'bower_components/angular-route/angular-route.min.js', 110, 'html-header', FALSE); - $headOffset = 0; - foreach ($modules as $moduleName => $module) { - foreach ($this->angular->getStyleUrls($moduleName) as $url) { - $this->res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header'); - } - foreach ($this->angular->getScriptUrls($moduleName) as $url) { - $this->res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header'); - // addScriptUrl() bypasses the normal string-localization of addScriptFile(), - // but that's OK because all Angular strings (JS+HTML) will load via crmResource. + + // FIXME: crmUi depends on loading ckeditor, but ckeditor doesn't work with this aggregation. + $this->res->addScriptFile('civicrm', 'packages/ckeditor/ckeditor.js', 100, 'html-header', FALSE); + + $config = \CRM_Core_Config::singleton(); + if ($config->debug) { + $headOffset = 0; + foreach ($modules as $moduleName => $module) { + foreach ($this->angular->getResources($moduleName, 'css', 'cacheUrl') as $url) { + $this->res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header'); + } + foreach ($this->angular->getResources($moduleName, 'js', 'cacheUrl') as $url) { + $this->res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), 'html-header'); + // addScriptUrl() bypasses the normal string-localization of addScriptFile(), + // but that's OK because all Angular strings (JS+HTML) will load via crmResource. + } } } + else { + // Note: addScriptUrl() bypasses the normal string-localization of addScriptFile(), + // but that's OK because all Angular strings (JS+HTML) will load via crmResource. + $aggScriptUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=js&r=' . $page->res->getCacheCode(), FALSE, NULL, FALSE); + $this->res->addScriptUrl($aggScriptUrl, 120, 'html-header'); + + $aggStyleUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=css&r=' . $page->res->getCacheCode(), FALSE, NULL, FALSE); + $this->res->addStyleUrl($aggStyleUrl, 120, 'html-header'); + } } } diff --git a/Civi/Angular/Page/Modules.php b/Civi/Angular/Page/Modules.php index 8141b77e54..70a7a20837 100644 --- a/Civi/Angular/Page/Modules.php +++ b/Civi/Angular/Page/Modules.php @@ -3,49 +3,120 @@ namespace Civi\Angular\Page; /** - * This page returns HTML partials used by Angular. + * This page aggregates data from Angular modules. + * + * Example: Aggregate metadata about all modules in JSON format. + * civicrm/ajax/angular-modules?format=json + * + * Example: Aggregate metadata for crmUi and crmUtil modules. + * civicrm/ajax/angular-modules?format=json&modules=crmUi,crmUtil + * + * Example: Aggregate *.js files for all modules. + * civicrm/ajax/angular-modules?format=js + * + * Example: Aggregate *.css files for all modules. + * civicrm/ajax/angular-modules?format=css */ class Modules extends \CRM_Core_Page { /** - * This page aggregates HTML partials used by Angular. + * See class description. */ public function run() { - //$config = \CRM_Core_Config::singleton(); - //\CRM_Core_Page_AJAX::setJsHeaders($config->debug ? 30 : NULL); - \CRM_Core_Page_AJAX::setJsHeaders(); - /** * @var \Civi\Angular\Manager $angular */ $angular = \Civi\Core\Container::singleton()->get('angular'); - $modules = $angular->getModules(); + $moduleNames = $this->parseModuleNames(\CRM_Utils_Request::retrieve('modules', 'String'), $angular); + + switch (\CRM_Utils_Request::retrieve('format', 'String')) { + case 'json': + case '': + $this->send( + 'application/javascript', + json_encode($this->getMetadata($moduleNames, $angular)) + ); + break; + + case 'js': + $this->send( + 'application/javascript', + \CRM_Utils_File::concat($angular->getResources($moduleNames, 'js', 'path'), "\n") + ); + break; + + case 'css': + $this->send( + 'text/css', + \CRM_Utils_File::concat($angular->getResources($moduleNames, 'css', 'path'), "\n") + ); + break; + + default: + \CRM_Core_Error::fatal("Unrecognized format"); + } + + \CRM_Utils_System::civiExit(); + } - $modulesExpr = \CRM_Utils_Request::retrieve('modules', 'String'); + /** + * @param string $modulesExpr + * Comma-separated list of module names. + * @param \Civi\Angular\Manager $angular + * @return array + * Any well-formed module names. All if moduleExpr is blank. + */ + public function parseModuleNames($modulesExpr, $angular) { if ($modulesExpr) { $moduleNames = preg_grep( '/^[a-zA-Z0-9\-_\.]+$/', explode(',', $modulesExpr) ); + return $moduleNames; } else { - $moduleNames = array_keys($modules); + $moduleNames = array_keys($angular->getModules()); + return $moduleNames; } + } + /** + * @param array $moduleNames + * List of module names. + * @param \Civi\Angular\Manager $angular + * @return array + */ + public function getMetadata($moduleNames, $angular) { + $modules = $angular->getModules(); $result = array(); foreach ($moduleNames as $moduleName) { if (isset($modules[$moduleName])) { $result[$moduleName] = array(); $result[$moduleName]['domain'] = $modules[$moduleName]['ext']; - $result[$moduleName]['js'] = $angular->getScriptUrls($moduleName); - $result[$moduleName]['css'] = $angular->getStyleUrls($moduleName); + $result[$moduleName]['js'] = $angular->getResources($moduleName, 'js', 'rawUrl'); + $result[$moduleName]['css'] = $angular->getResources($moduleName, 'css', 'rawUrl'); $result[$moduleName]['partials'] = $angular->getPartials($moduleName); $result[$moduleName]['strings'] = $angular->getTranslatedStrings($moduleName); } } + return $result; + } - echo json_encode($result); - \CRM_Utils_System::civiExit(); + /** + * Send a response. + * + * @param string $type + * Content type. + * @param string $data + * Content. + */ + public function send($type, $data) { + // Encourage browsers to cache for a long time - 1 year + $ttl = 60 * 60 * 24 * 364; + header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + $ttl)); + header("Content-Type: $type"); + header("Cache-Control: max-age=$ttl, public"); + echo $data; } } -- 2.25.1