HomeAJAX & Prototype Google's Closure Compiler Service API: the ADVANCED_OPTIMIZATIONS Option
Google's Closure Compiler Service API: the ADVANCED_OPTIMIZATIONS Option
In this fifth part of the series, you will learn how to work with the “ADVANCED_OPTIMIZATIONS” option provided by Google’s Closure Compiler Service API. It can be used to perform a more sophisticated optimization process on JavaScript snippets. If you ever need to minify your client-side scripts to their minimal expression, using this option is undoubtedly the way to go.
Optimizing JavaScript files is usually a straightforward process, at least when performed at a basic level (and by "basic" I mean removing raw white space and comments). The process can quickly turn into a time-consuming and even challenging task, however, when it’s necessary to apply more advanced optimizations. These often involve shortening functions and variable names, and even refactoring large portions of code.
This doesn’t mean that all is lost when you want to make your JavaScript snippets shorter and more efficient. Nowadays there is a respectable number of web applications that will do the hard work for you, in many cases via a graphical user interface. It’d be great, however, to be able to perform the optimization process programmatically by using the server-side programming of your choice, wouldn’t it?
Well, Google seems to think so, too. It has recently launched a whole new web service called Closure Compiler Service API, which will let you apply different levels of optimization to your JavaScript files by means of a bunch of comprehensive parameters, which must be passed to the API via POST HTTP requests.
Since PHP makes it really easy to trigger POST requests via its socket extension, in previous chapters of this series I created some basic examples to demonstrate how to consume the API through a few simple PHP classes. Moreover, I left off the last tutorial explaining how to apply an intermediate optimization to a sample JavaScript file by assigning the “SIMPLE_OPTMIZATIONS” value to the “compilation_level” argument provided by the API. This process not only removed white space and comments from the target file, but easily shortened its functions and variable names.
But as you may have guessed when you read this article’s title, the API has been equipped with the ability to perform even more advanced optimizations, including refactoring functions and methods (when possible, of course). You may be wondering how this can be done, right? Specifying the value of “ADVANCED_OPTIMIZATIONS” to the aforementioned “compilation_level” option will do the trick, so in the next few lines I’m going to set up an example that will show how to apply this level of optimization to the JavaScript file coded previously.
With the theory out of our way, it’s time to explore another handy feature offered by Google's Closure Compiler Service API. Let’s get going!
A quick review: using the “SIMPLE_OPTIMIZATION” option
Just in case you haven’t read the previous article, where I explained how to apply an intermediate level of optimization to a basic JavaScript file via the Closure Compiler API’s “SIMPLE_OPTIMIZATION” option, I've included all of the PHP classes required to perform this task, along with the corresponding optimization script.
First, here are the classes responsible for querying the Closure Compiler Service API and processing the output that it produces by using some simple methods:
// constructor public function __construct(array $data, array $settings = array()) { $this->setData($data); if (array_key_exists('url', $settings)) { $this->setUrl($settings['url']); } if (array_key_exists('port', $settings)) { $this->setPort($settings['port']); } if (array_key_exists('method', $settings)) { $this->setMethod($settings['method']); } }
// set the data that will be passed with the request public function setData(array $data) { if (empty($data)) { throw new HttpRequestHandlerException('The request arguments are not valid.'); } $this->_data = $data; return $this; }
// get the specified request data public function getData() { return $this->_data; }
// set the URL the request will be made to (implemented by subclasses) abstract public function setUrl($url);
// get the given URL public function getUrl() { return $this->_url; }
// set the TCP port the request will be made on (implemented by subclasses) abstract public function setPort($port);
// get the given port public function getPort() { return $this->_port; }
// set the request method (GET or POST) public function setMethod($method = 'GET') { $method = strtoupper($method); if (!in_array($method, array('GET', 'POST'), TRUE)) { throw new HttpRequestHandlerException('The request method is not valid.'); } $this->_method = $method; return $this; }
// get the request method public function getMethod() { return $this->_method; }
// add a new request header public function addHeader($key, $header) { $key = strtolower($key); if (!array_key_exists($key, $this->_headers)) { $this->_headers[$key] = $header; } return $this; }
// remove a specified request header public function removeHeader($key) { $key = strtolower($key); if (array_key_exists($key, $this->_headers)) { unset($this->_headers[$key]); } return $this; }
// get a specified request header public function getHeader($key) { $key = strtolower($key); if (array_key_exists($key, $this->_headers)) { return $this->_headers[$key]; } }
// get the header included in the response public function getResponseHeader() { return $this->_responseHeader; }
// get the content included in the response public function getReponseContent() { return $this->_responseContent; }
// send an HTTP request to the specified URL and TCP port public function sendRequest() { // parse and urlencode the request data $data = ''; foreach ($this->_data as $key => $value) { $data .= '&' . $key . '=' . urlencode($value); } $data = trim($data, '&'); // parse the given URL $url = parse_url($this->_url); if (!isset($url['host']) OR !isset($url['path'])) { throw new HttpRequestHandlerException('No host or path was specified.'); } $host = $url['host']; $path = $url['path']; // open a socket connection on the specified TCP port if (!$fp = fsockopen($host, $this->_port)) { throw new HttpRequestHandlerException('Error opening socket connection to the URL '. $this->_url . ' on port.' . $this->_port); } fputs($fp, "$this->_method $path HTTP/1.0rn"); fputs($fp, "Host: $hostrn"); fputs($fp, "Content-type: application/x-www-form-urlencodedrn"); fputs($fp, "Content-length: ". strlen($data) . "rn"); fputs($fp, "Connection: closernrn"); fputs($fp, $data); // get the response of the request $response = ''; while(!feof($fp)) { $response .= fgets($fp, 128); } // close the socket connection: fclose($fp); // process the response $response = explode("rnrn", $response, 2); $this->_responseHeader = $response[0]; $this->_responseContent = $response[1]; return $this->_responseContent; }
// send the specified header public function sendHeader($key) { if ($header = $this->getHeader($key)) { header($header); } } }
// set the URL of the closure compiler API public function setUrl($url) { if (strpos('http://closure-compiler.appspot.com', $url) !== 0) { throw new HttpRequestHandlerException('The specified URL for the closure compiler API is not valid.'); } $this->_url = $url; return $this; }
// set the TCP port of the closure compiler API (throws an exception, as it's been already set) public function setPort($port) { throw new HttpRequestHandlerException('The specified TCP port for the closure compiler API has been already set.'); } }
As you can see above, the abstract “HttpRequestHandlerAbstract” class defines the structure and generic functionality required to make an HTTP request to a given host on a specific TCP port, while its subclass “ClosureCompilerHandler” is tasked with interacting directly with the Closure Compiler Service API. This is basically Inheritance 101, so you shouldn’t have major problems understanding how the previous classes do their respective business.
Now that I have explained how the two request handling classes function, please look at the following ones. They are tasked with reading and writing data to a given file and lazy-loading classes by means of the built-in PHP autoloading mechanism. Check them out:
(FileHandler.php)
<?php
class FileHandler { protected $_path = 'function.js'; protected $_data;
// constructor public function __construct($path = '') { if ($path !== '') { $this->setPath($path); } }
// set the path to read/write data public function setPath($path) { if (!file_exists($path)) { throw new FileHandlerException('The specified path is not valid.'); } $this->_path = $path; }
// get the specified path public function getPath() { return $this->_path; }
// set new data to be written public function setData($data) { $this->_data = $data; }
// get the stored data public function getData() { return $this->_data; }
// read data from the specified path public function read() { if (!$this->_data = file_get_contents($this->_path)) { throw new FileHandlerException('Error reading from the target file.'); } return $this->_data; }
// write data to the specified path public function write($data) { if (!file_put_contents($this->_path, $data)) { throw new FileHandlerException('Error reading from the target file.'); } $this->_data = $data; } }
(Autoloader.php)
<?php
class Autoloader { private static $_instance;
// get the Singleton instance of the autoloader public static function getInstance() { if (!self::$_instance) { self::$_instance = new self; } return self::$_instance; }
// constructor private function __construct() { spl_autoload_register(array($this, 'load')); }
// prevent cloning instance of the autoloader private function __clone(){}
// load a given class or interface public static function load($class) { $file = $class . '.php'; if (!file_exists($file)) { throw new ClassNotFoundException('The file containing the requested class or interface ' . $class . ' was not found.'); } require $file; unset($file); if (!class_exists($class, FALSE) AND !interface_exists($class, FALSE)) { throw new ClassNotFoundException('The requested class or interface ' . $class . ' was not found.'); } } }
As I said before, the functionality of the previous “FileHandler” class is limited to reading and writing data to a given file. If you’re wondering why I decided to define a class like this, my answer would be simply to implement a mechanism that reads JavaScript files via an object-oriented API. Bear in mind, though, that similar functionality can be achieved by using a procedural approach, so feel free to use your custom functions if you feel more comfortable with them.
On the other hand, the implementation of the pertinent autoloader class is quite easy to follow. As its name suggests, it comes in handy for loading classes on demand via the PHP SPL stack, thus avoiding the annoyance of dealing with multiple includes.
Finally, to get things completed, here’s the definition of the sample JavaScript file that will be passed to the Closure Compiler Service API for further optimization:
(function.js)
function createDiv(idAttr, classAttr) { // create the div var div = document.createElement('div'); // assign the 'id' attribute to the div element div.setAttribute('id', idAttr); // assign the 'class' attribute to the div element div.setAttribute('class', classAttr); // append the div element to the document document.getElementsByTagName('body')[0].appendChild(div); }; // call the previous function createDiv('divid', 'divclass');
If you’re familiar with writing plain JavaScript (because you are still doing that, despite jQuery, right?), surely you’ll find the earlier code snippet easy to understand. All that it does is define a function that dynamically appends a div element to the existing web page via some common DOM methods. Its functionality is actually that simple to grasp.
So far, so good. Having shown all the PHP classes required to properly query the Closure Compiler Service API, here’s an example that shows in a nutshell how to optimize the previous JavaScript file via the API’s “SIMPLE_OPTIMIZATIONS” option:
<?php
// example using optimization level = SIMPLE_OPTIMIZATIONS try {
// include the autoloader class require_once 'Autoloader.php'; Autoloader::getInstance();
// create an instance of the file handler class and read the specified JavaScript file $fileHandler = new FileHandler; $js = $fileHandler->read();
// build the array of arguments that will be passed to the closure compiler API $data = array( 'js_code' => $js, 'compilation_level' => 'SIMPLE_OPTIMIZATIONS', 'output_format' => 'text', 'output_info' => 'compiled_code' );
// create an instance of the ClosureCompilerHandler class $compilerHandler = new ClosureCompilerHandler($data); // query the closure compiler API and get the compiled JavaScript function $response = $compilerHandler->sendRequest(); $compilerHandler->sendHeader('js'); // print to screen the compiled JavaScript function echo $response; /* displays the following
if you take a close look at the earlier PHP script, you’ll realize that all of the upfront work is fully justified. Thanks to the existence of the previous PHP classes, it’s possible to query the Closure Compiler Service API through simple client code and compile the target JavaScript file via the “SIMPLE_OPTIMIZATIONS” setting. This process not only strips the white space and comments from the file, but renames the declared variables to their shortest expressions. Not too bad, huh?
It’s valid to notice, though, that the Closure Compiler Service API is capable of performing more advanced and thorough optimizations via another handy option, not surprisingly called “ADVANCED_OPTIMIZATIONS.” Thus, in the upcoming segment, I’m going to build another approachable example that will demonstrate how to put this option to work for you.