Added JavaScript minification feature (new function t3lib_div::minifyJavaScript)
authorDmitry Dulepov <dmitry.dulepov@gmail.com>
Mon, 24 Sep 2007 10:02:13 +0000 (10:02 +0000)
committerDmitry Dulepov <dmitry.dulepov@gmail.com>
Mon, 24 Sep 2007 10:02:13 +0000 (10:02 +0000)
Added minification of FE javascripts (including default js and onload handlers) if config.minifyJS is enabled

git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@2518 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_div.php
typo3/contrib/jsmin/jsmin.php [new file with mode: 0644]
typo3/sysext/cms/tslib/class.tslib_pagegen.php

index bb07891..c47bd13 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2007-09-24     Dmitry Dulepov  <dmitry@typo3.org>
+
+       * Added JavaScript minification feature (new function t3lib_div::minifyJavaScript)
+       * Added minification of FE javascripts (including default js and onload handlers) if config.minifyJS is enabled
+
 2007-09-23  Oliver Hader  <oh@inpublica.de>
 
        * (minor) Fixed hanging recordset in tslib_cObj
index f618aac..9c1741b 100755 (executable)
@@ -2330,8 +2330,26 @@ class t3lib_div {
                }
        }
 
-
-
+       /**
+        * Minifies JavaScript
+        *
+        * @param       string  $script Script to minify
+        * @param       string  $error  Error message (if any)
+        * @return      string  Minified script or source string if error happened
+        */
+       function minifyJavaScript($script, &$error = '') {
+               require_once(PATH_typo3 . 'contrib/jsmin/jsmin.php');
+               try {
+                       $error = '';
+                       $script = trim(JSMin::minify(str_replace(chr(13), '', $script)));
+               }
+               catch(JSMinException $e) {
+                       $error = 'Error while minifying JavaScript: ' . $e->getMessage();
+                       t3lib_div::devLog($error, 't3lib_div', 2,
+                               array('JavaScript' => $script, 'Stack trace' => $e->getTrace()));
+               }
+               return $script;
+       }
 
 
 
diff --git a/typo3/contrib/jsmin/jsmin.php b/typo3/contrib/jsmin/jsmin.php
new file mode 100644 (file)
index 0000000..050b7a4
--- /dev/null
@@ -0,0 +1,296 @@
+<?php\r
+/**\r
+ * jsmin.php - PHP implementation of Douglas Crockford's JSMin.\r
+ *\r
+ * This is pretty much a direct port of jsmin.c to PHP with just a few\r
+ * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and\r
+ * outputs to stdout, this library accepts a string as input and returns another\r
+ * string as output.\r
+ *\r
+ * PHP 5 or higher is required.\r
+ *\r
+ * Permission is hereby granted to use this version of the library under the\r
+ * same terms as jsmin.c, which has the following license:\r
+ *\r
+ * --\r
+ * Copyright (c) 2002 Douglas Crockford  (www.crockford.com)\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of\r
+ * this software and associated documentation files (the "Software"), to deal in\r
+ * the Software without restriction, including without limitation the rights to\r
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\r
+ * of the Software, and to permit persons to whom the Software is furnished to do\r
+ * so, subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be included in all\r
+ * copies or substantial portions of the Software.\r
+ *\r
+ * The Software shall be used for Good, not Evil.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+ * SOFTWARE.\r
+ * --\r
+ *\r
+ * @package JSMin\r
+ * @author Ryan Grove <ryan@wonko.com>\r
+ * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)\r
+ * @copyright 2007 Ryan Grove <ryan@wonko.com> (PHP port)\r
+ * @license http://opensource.org/licenses/mit-license.php MIT License\r
+ * @version 1.1.0 (2007-06-01)\r
+ * @link http://code.google.com/p/jsmin-php/\r
+ */\r
+\r
+class JSMin {\r
+  const ORD_LF    = 10;\r
+  const ORD_SPACE = 32;\r
+\r
+  protected $a           = '';\r
+  protected $b           = '';\r
+  protected $input       = '';\r
+  protected $inputIndex  = 0;\r
+  protected $inputLength = 0;\r
+  protected $lookAhead   = null;\r
+  protected $output      = array();\r
+\r
+  // -- Public Static Methods --------------------------------------------------\r
+\r
+  public static function minify($js) {\r
+    $jsmin = new JSMin($js);\r
+    return $jsmin->min();\r
+  }\r
+\r
+  // -- Public Instance Methods ------------------------------------------------\r
+\r
+  public function __construct($input) {\r
+    $this->input       = str_replace("\r\n", "\n", $input);\r
+    $this->inputLength = strlen($this->input);\r
+  }\r
+\r
+  // -- Protected Instance Methods ---------------------------------------------\r
+\r
+  protected function action($d) {\r
+    switch($d) {\r
+      case 1:\r
+        $this->output[] = $this->a;\r
+\r
+      case 2:\r
+        $this->a = $this->b;\r
+\r
+        if ($this->a === "'" || $this->a === '"') {\r
+          for (;;) {\r
+            $this->output[] = $this->a;\r
+            $this->a        = $this->get();\r
+\r
+            if ($this->a === $this->b) {\r
+              break;\r
+            }\r
+\r
+            if (ord($this->a) <= self::ORD_LF) {\r
+              throw new JSMinException('Unterminated string literal.');\r
+            }\r
+\r
+            if ($this->a === '\\') {\r
+              $this->output[] = $this->a;\r
+              $this->a        = $this->get();\r
+            }\r
+          }\r
+        }\r
+\r
+      case 3:\r
+        $this->b = $this->next();\r
+\r
+        if ($this->b === '/' && (\r
+            $this->a === '(' || $this->a === ',' || $this->a === '=' ||\r
+            $this->a === ':' || $this->a === '[' || $this->a === '!' ||\r
+            $this->a === '&' || $this->a === '|' || $this->a === '?')) {\r
+\r
+          $this->output[] = $this->a;\r
+          $this->output[] = $this->b;\r
+\r
+          for (;;) {\r
+            $this->a = $this->get();\r
+\r
+            if ($this->a === '/') {\r
+              break;\r
+            }\r
+            elseif ($this->a === '\\') {\r
+              $this->output[] = $this->a;\r
+              $this->a        = $this->get();\r
+            }\r
+            elseif (ord($this->a) <= self::ORD_LF) {\r
+              throw new JSMinException('Unterminated regular expression '.\r
+                  'literal.');\r
+            }\r
+\r
+            $this->output[] = $this->a;\r
+          }\r
+\r
+          $this->b = $this->next();\r
+        }\r
+    }\r
+  }\r
+\r
+  protected function get() {\r
+    $c = $this->lookAhead;\r
+    $this->lookAhead = null;\r
+\r
+    if ($c === null) {\r
+      if ($this->inputIndex < $this->inputLength) {\r
+        $c = $this->input[$this->inputIndex];\r
+        $this->inputIndex += 1;\r
+      }\r
+      else {\r
+        $c = null;\r
+      }\r
+    }\r
+\r
+    if ($c === "\r") {\r
+      return "\n";\r
+    }\r
+\r
+    if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {\r
+      return $c;\r
+    }\r
+\r
+    return ' ';\r
+  }\r
+\r
+  protected function isAlphaNum($c) {\r
+    return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;\r
+  }\r
+\r
+  protected function min() {\r
+    $this->a = "\n";\r
+    $this->action(3);\r
+\r
+    while ($this->a !== null) {\r
+      switch ($this->a) {\r
+        case ' ':\r
+          if ($this->isAlphaNum($this->b)) {\r
+            $this->action(1);\r
+          }\r
+          else {\r
+            $this->action(2);\r
+          }\r
+          break;\r
+\r
+        case "\n":\r
+          switch ($this->b) {\r
+            case '{':\r
+            case '[':\r
+            case '(':\r
+            case '+':\r
+            case '-':\r
+              $this->action(1);\r
+              break;\r
+\r
+            case ' ':\r
+              $this->action(3);\r
+              break;\r
+\r
+            default:\r
+              if ($this->isAlphaNum($this->b)) {\r
+                $this->action(1);\r
+              }\r
+              else {\r
+                $this->action(2);\r
+              }\r
+          }\r
+          break;\r
+\r
+        default:\r
+          switch ($this->b) {\r
+            case ' ':\r
+              if ($this->isAlphaNum($this->a)) {\r
+                $this->action(1);\r
+                break;\r
+              }\r
+\r
+              $this->action(3);\r
+              break;\r
+\r
+            case "\n":\r
+              switch ($this->a) {\r
+                case '}':\r
+                case ']':\r
+                case ')':\r
+                case '+':\r
+                case '-':\r
+                case '"':\r
+                case "'":\r
+                  $this->action(1);\r
+                  break;\r
+\r
+                default:\r
+                  if ($this->isAlphaNum($this->a)) {\r
+                    $this->action(1);\r
+                  }\r
+                  else {\r
+                    $this->action(3);\r
+                  }\r
+              }\r
+              break;\r
+\r
+            default:\r
+              $this->action(1);\r
+              break;\r
+          }\r
+      }\r
+    }\r
+\r
+    return implode('', $this->output);\r
+  }\r
+\r
+  protected function next() {\r
+    $c = $this->get();\r
+\r
+    if ($c === '/') {\r
+      switch($this->peek()) {\r
+        case '/':\r
+          for (;;) {\r
+            $c = $this->get();\r
+\r
+            if (ord($c) <= self::ORD_LF) {\r
+              return $c;\r
+            }\r
+          }\r
+\r
+        case '*':\r
+          $this->get();\r
+\r
+          for (;;) {\r
+            switch($this->get()) {\r
+              case '*':\r
+                if ($this->peek() === '/') {\r
+                  $this->get();\r
+                  return ' ';\r
+                }\r
+                break;\r
+\r
+              case null:\r
+                throw new JSMinException('Unterminated comment.');\r
+            }\r
+          }\r
+\r
+        default:\r
+          return $c;\r
+      }\r
+    }\r
+\r
+    return $c;\r
+  }\r
+\r
+  protected function peek() {\r
+    $this->lookAhead = $this->get();\r
+    return $this->lookAhead;\r
+  }\r
+}\r
+\r
+// -- Exceptions ---------------------------------------------------------------\r
+class JSMinException extends Exception {}\r
+?>
\ No newline at end of file
index 5bb450c..61f22fd 100755 (executable)
@@ -317,14 +317,7 @@ See <a href="http://wiki.typo3.org/index.php/TYPO3_3.8.1" target="_blank">wiki.t
                        }
                }
 
-               return Array(count($functions)?'
-<script type="text/javascript">
-       /*<![CDATA[*/
-'.implode(chr(10),$functions).'
-'.implode(chr(10),$setEvents).'
-       /*]]>*/
-</script>
-                       ':'',$setBody);
+               return array(count($functions)? implode(chr(10), $functions) . chr(10) . implode(chr(10), $setEvents) : '', $setBody);
        }
 
        /**
@@ -473,7 +466,7 @@ See <a href="http://wiki.typo3.org/index.php/TYPO3_3.8.1" target="_blank">wiki.t
                $GLOBALS['TSFE']->content.='
        <meta http-equiv="Content-Type" content="text/html; charset='.$theCharset.'" />';
 
-$GLOBALS['TSFE']->content.='
+               $GLOBALS['TSFE']->content.='
 
 <!-- '.($customContent?$customContent.chr(10):'').'
        This website is powered by TYPO3 - inspiring people to share!
@@ -729,7 +722,7 @@ $GLOBALS['TSFE']->content.='
                function blurLink(theObject)    {       //
                        if (msie4)      {theObject.blur();}
                }
-               ';
+               ' . $JSef[0];
 
                if ($GLOBALS['TSFE']->spamProtectEmailAddresses && $GLOBALS['TSFE']->spamProtectEmailAddresses !== 'ascii') {
                        $_scriptCode.= '
@@ -768,8 +761,15 @@ $GLOBALS['TSFE']->content.='
                ';
                }
 
+               // Should minify?
+               if ($GLOBALS['TSFE']->config['config']['minifyJS']) {
+                       $minifyError = '';
+                       $_scriptCode = t3lib_div::minifyJavaScript($_scriptCode,$minifyError);
+                       if ($minifyError) {
+                               $GLOBALS['TT']->setTSlogMessage($minifyError, 3);
+                       }
+               }
                if (!$GLOBALS['TSFE']->config['config']['removeDefaultJS']) {
-                               // NOTICE: The following code must be kept synchronized with "tslib/default.js"!!!
                        $GLOBALS['TSFE']->content.='
        <script type="text/javascript">
                /*<![CDATA[*/
@@ -782,7 +782,6 @@ $GLOBALS['TSFE']->content.='
                }
 
                $GLOBALS['TSFE']->content.= chr(10).implode($GLOBALS['TSFE']->additionalHeaderData,chr(10)).'
-'.$JSef[0].'
 </head>';
                if ($GLOBALS['TSFE']->pSetup['frameSet.'])      {
                        $fs = t3lib_div::makeInstance('tslib_frameset');