return array_values($strings);
}
+ /**
+ * Identify duplicate, adjacent, identical closures and consolidate them.
+ *
+ * Note that you can only dedupe closures if they are directly adjacent and
+ * have exactly the same parameters.
+ *
+ * @param array $scripts
+ * Javascript source.
+ * @param array $localVars
+ * Ordered list of JS vars to identify the start of a closure.
+ * @param array $inputVals
+ * Ordered list of input values passed into the closure.
+ * @return string
+ * Javascript source.
+ */
+ public static function dedupeClosures($scripts, $localVars, $inputVals) {
+ // Example opening: (function (angular, $, _) {
+ $opening = '\s*\(\s*function\s*\(\s*';
+ $opening .= implode(',\s*', array_map(function ($v) {
+ return preg_quote($v, '/');
+ }, $localVars));
+ $opening .= '\)\s*\{';
+ $opening = '/^' . $opening . '/';
+
+ // Example closing: })(angular, CRM.$, CRM._);
+ $closing = '\}\s*\)\s*\(\s*';
+ $closing .= implode(',\s*', array_map(function ($v) {
+ return preg_quote($v, '/');
+ }, $inputVals));
+ $closing .= '\);\s*';
+ $closing = "/$closing\$/";
+
+ $scripts = array_values($scripts);
+ for ($i = count($scripts) - 1; $i > 0; $i--) {
+ if (preg_match($closing, $scripts[$i - 1]) && preg_match($opening, $scripts[$i])) {
+ $scripts[$i - 1] = preg_replace($closing, '', $scripts[$i - 1]);
+ $scripts[$i] = preg_replace($opening, '', $scripts[$i]);
+ }
+ }
+
+ return $scripts;
+ }
+
}
case 'js':
$this->send(
'application/javascript',
- \CRM_Utils_File::concat($angular->getResources($moduleNames, 'js', 'path'), "\n")
+ $this->digestJs($angular->getResources($moduleNames, 'js', 'path'))
);
break;
\CRM_Utils_System::civiExit();
}
+ /**
+ * @param array $files
+ * File paths.
+ * @return string
+ */
+ public function digestJs($files) {
+ $scripts = array();
+ foreach ($files as $file) {
+ $scripts[] = file_get_contents($file);
+ }
+ $scripts = \CRM_Utils_JS::dedupeClosures(
+ $scripts,
+ array('angular', '$', '_'),
+ array('angular', 'CRM.$', 'CRM._')
+ );
+ return implode("\n", $scripts);
+ }
+
/**
* @param string $modulesExpr
* Comma-separated list of module names.
$this->assertEquals($expectedStrings, $actualStrings);
}
+ public function dedupeClosureExamples() {
+ // Each example string here is named for its body, eg the body of $a calls "a()".
+ $a = "(function (angular, $, _) {\na();\n})(angular, CRM.$, CRM._);";
+ $b = "(function(angular,$,_){\nb();\n})(angular,CRM.$,CRM._);";
+ $c = "(function( angular, $,_) {\nc();\n})(angular,CRM.$, CRM._);";
+ $d = "(function (angular, $, _, whiz) {\nd();\n})(angular, CRM.$, CRM._, CRM.whizbang);";
+ $m = "alert('i is the trickster (function( angular, $,_) {\nm();\n})(angular,CRM.$, CRM._);)'";
+ // Note: $d has a fundamentally different closure.
+
+ // Each example string here is a deduped combination of others,
+ // eg "$ab" is the deduping of $a+$b.
+ $ab = "(function (angular, $, _) {\na();\n\nb();\n})(angular,CRM.$,CRM._);";
+ $abc = "(function (angular, $, _) {\na();\n\nb();\n\nc();\n})(angular,CRM.$, CRM._);";
+ $cb = "(function( angular, $,_) {\nc();\n\nb();\n})(angular,CRM.$,CRM._);";
+
+ $cases = array();
+ $cases[] = array(array($a), "$a");
+ $cases[] = array(array($b), "$b");
+ $cases[] = array(array($c), "$c");
+ $cases[] = array(array($d), "$d");
+ $cases[] = array(array($m), "$m");
+ $cases[] = array(array($a, $b), "$ab");
+ $cases[] = array(array($a, $m, $b), "$a$m$b");
+ $cases[] = array(array($a, $d), "$a$d");
+ $cases[] = array(array($a, $d, $b), "$a$d$b");
+ $cases[] = array(array($a, $b, $c), "$abc");
+ $cases[] = array(array($a, $b, $d, $c, $b), "$ab$d$cb");
+ return $cases;
+ }
+
+ /**
+ * @param array $scripts
+ * @param string $expectedOutput
+ * @dataProvider dedupeClosureExamples
+ */
+ public function testDedupeClosure($scripts, $expectedOutput) {
+ $actualOutput = CRM_Utils_JS::dedupeClosures(
+ $scripts,
+ array('angular', '$', '_'),
+ array('angular', 'CRM.$', 'CRM._')
+ );
+ $this->assertEquals($expectedOutput, implode("", $actualOutput));
+ }
}