CRM-16145 - Civi/Angular - Aggregate JS and CSS files
authorTim Otten <totten@civicrm.org>
Thu, 19 Mar 2015 23:31:48 +0000 (16:31 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 7 Apr 2015 06:32:48 +0000 (23:32 -0700)
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
Civi/Angular/Page/Main.php
Civi/Angular/Page/Modules.php

index a92ecd9c18f1095a6025b14d7fe14d504218045c..60bfc371701c354384433a5ed54055a60dcb026b 100644 (file)
@@ -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;
index 795120f10e5695c7039d411aa061454eead03080..854f0225836888136e7373e0dd4249bde92db938 100644 (file)
@@ -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');
+    }
   }
 
 }
index 8141b77e540bad1e802591632680536c734b0bce..70a7a20837a90b32624d6b71d68456f53e36844d 100644 (file)
 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;
   }
 
 }