diff --git a/HISTORY.txt b/HISTORY.txt index beee56f..94606ed 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,6 +1,7 @@ Minify Release History Version 2.1.4 + * Option to minify JS with Closure Compiler API w/ JSMin failover * Cookie/bookmarklet-based debug mode. No HTML editing! * Allows 1 file to be missing w/o complete failure * Combine multiple groups and files in single URI diff --git a/min/config.php b/min/config.php index b95bd98..c436c86 100644 --- a/min/config.php +++ b/min/config.php @@ -102,6 +102,18 @@ $min_serveOptions['bubbleCssImports'] = false; $min_serveOptions['maxAge'] = 1800; +/** + * To use Google's Closure Compiler API (falling back to JSMin on failure), + * uncomment the following lines: + */ +/*function closureCompiler($js) { + require_once 'Minify/JS/ClosureCompiler.php'; + return Minify_JS_ClosureCompiler::minify($js); +} +$min_serveOptions['minifiers']['application/x-javascript'] = 'closureCompiler'; +//*/ + + /** * If you'd like to restrict the "f" option to files within/below * particular directories below DOCUMENT_ROOT, set this here. diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index ae4ff92..75e8e10 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -308,7 +308,7 @@ class JSMin { $this->get(); // if comment preserved by YUI Compressor if (0 === strpos($comment, '!')) { - return "\n/*" . substr($comment, 1) . "*/\n"; + return "\n/*!" . substr($comment, 1) . "*/\n"; } // if IE conditional comment if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { diff --git a/min/lib/Minify/JS/ClosureCompiler.php b/min/lib/Minify/JS/ClosureCompiler.php new file mode 100644 index 0000000..fa4d394 --- /dev/null +++ b/min/lib/Minify/JS/ClosureCompiler.php @@ -0,0 +1,109 @@ + + */ +class Minify_JS_ClosureCompiler { + const URL = 'http://closure-compiler.appspot.com/compile'; + + /** + * Minify Javascript code via HTTP request to the Closure Compiler API + * + * @param string $js input code + * @param array $options unused at this point + * @return string + */ + public static function minify($js, array $options = array()) + { + $obj = new self($options); + return $obj->min($js); + } + + /** + * + * @param array $options + * + * fallbackFunc : default array($this, 'fallback'); + */ + public function __construct(array $options = array()) + { + $this->_fallbackFunc = isset($options['fallbackMinifier']) + ? $options['fallbackMinifier'] + : array($this, '_fallback'); + } + + public function min($js) + { + $content = $this->_getPostContent($js); + $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) + ? mb_strlen($content, '8bit') + : strlen($content); + if ($bytes > 200000) { + throw new Minify_JS_ClosureCompiler_Exception( + 'POST content larger than 200000 bytes' + ); + } + $response = $this->_getResponse($content); + if (preg_match('/^Error\(\d\d?\):/', $response)) { + if (is_callable($this->_fallbackFunc)) { + $response = "/* Received errors from Closure Compiler API:\n$response" + . "\n(Using fallback minifier)\n*/\n"; + $response .= call_user_func($this->_fallbackFunc, $js); + } else { + throw new Minify_JS_ClosureCompiler_Exception($response); + } + } + if ($response === '') { + $errors = $this->_getResponse($this->_getPostContent($js, true)); + throw new Minify_JS_ClosureCompiler_Exception($errors); + } + return $response; + } + + protected $_fallbackFunc = null; + + protected function _getResponse($content) + { + $contents = file_get_contents(self::URL, false, stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => $content, + 'max_redirects' => 0, + 'timeout' => 15, + ) + ))); + if (false === $contents) { + throw new Minify_JS_ClosureCompiler_Exception( + "No HTTP response from server" + ); + } + return trim($contents); + } + + protected function _getPostContent($js, $returnErrors = false) + { + return http_build_query(array( + 'js_code' => $js, + 'output_info' => ($returnErrors ? 'errors' : 'compiled_code'), + 'output_format' => 'text', + 'compilation_level' => 'SIMPLE_OPTIMIZATIONS' + )); + } + + protected function _fallback($js) + { + require_once 'JSMin.php'; + return JSMin::minify($js); + } +} + +class Minify_JS_ClosureCompiler_Exception extends Exception {} diff --git a/min_unit_tests/_test_files/js/before.min.js b/min_unit_tests/_test_files/js/before.min.js index 6f06c6e..e89ccff 100644 --- a/min_unit_tests/_test_files/js/before.min.js +++ b/min_unit_tests/_test_files/js/before.min.js @@ -1,10 +1,10 @@ -/* is.js +/*! is.js (c) 2001 Douglas Crockford 2001 June 3 */ var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',ua:navigator.userAgent.toLowerCase(),version:parseFloat(navigator.appVersion.substr(21))||parseFloat(navigator.appVersion),win:navigator.platform=='Win32'} -/** +/*!* * preserve this comment, too */ is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;} diff --git a/min_unit_tests/test_Minify_JS_ClosureCompiler.php b/min_unit_tests/test_Minify_JS_ClosureCompiler.php new file mode 100644 index 0000000..74df047 --- /dev/null +++ b/min_unit_tests/test_Minify_JS_ClosureCompiler.php @@ -0,0 +1,49 @@ +getMessage(), 1) . "\n\n\n"; + } +} + +test_Minify_JS_ClosureCompiler(); diff --git a/min_unit_tests/test_all.php b/min_unit_tests/test_all.php index 3206313..ff26b51 100644 --- a/min_unit_tests/test_all.php +++ b/min_unit_tests/test_all.php @@ -9,6 +9,7 @@ require 'test_Minify_Cache_Memcache.php'; require 'test_Minify_Cache_ZendPlatform.php'; require 'test_Minify_CSS.php'; require 'test_Minify_CSS_UriRewriter.php'; +require 'test_Minify_JS_ClosureCompiler.php'; require 'test_Minify_CommentPreserver.php'; require 'test_Minify_HTML.php'; require 'test_Minify_ImportProcessor.php';