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:

(HttpRequestHandlerAbstract.php)

<?php

abstract class HttpRequestHandlerAbstract
{
    protected $_data = array();
    protected $_url;
    protected $_port;
    protected $_method;
    protected $_headers = array();
    protected $_responseHeader;
    protected $_responseContent;
   
    // 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);
        }
    } 
}

 

(ClosureCompilerHandler.php)

<?php

class ClosureCompilerHandler extends HttpRequestHandlerAbstract
{
    protected $_url = ‘http://closure-compiler.appspot.com/compile’;
    protected $_port = 80;
    protected $_method = ‘POST’;
    protected $_responseHeader;
    protected $_responseContent;
    protected $_headers = array(
                  ‘js’ => ‘Content-type: text/javascript’,
                  ‘xml’ => ‘Content-type: text/xml’,
                  ‘json’ => ‘Content-type: application/json’
              ); 
   
    // 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
   
    function createDiv(b,c){var a=document.createElement("div");a.setAttribute("id",b);a.setAttribute("class",c);document.getElementsByTagName("body")[0].appendChild(a)}createDiv("divid","divclass");
    */  
}
catch (Exception $e) {
    echo $e->getMessage();
    exit();
}

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.

Now, jump forward and read the lines to come.

{mospagebreak title=Shortening JavaScript files with the ADVANCED OPTIMIZATIONS option}

To be frank, optimizing JavaScript code via the “ADVANCED_OPTIMIZATIONS” option is a straightforward process, reduced to assigning this value to the “compilation_level” argument accepted by the Closure Compiler and nothing else. Naturally, the most efficient way to understand the internals of this process is by example, so below I created a new one which passes this option to the compiler’s API. Here it is:

<?php

// example using optimization level = ADVANCED_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’ => ‘ADVANCED_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 compíled JavaScript function
    $response = $compilerHandler->sendRequest();
    $compilerHandler->sendHeader(‘js’);
    // print to screen the compiled JavaScript function
    echo $response;
}
catch (Exception $e) {
    echo $e->getMessage();
    exit();
}

Definitely, applying more sophisticated optimizations to a JavaScript snippet via the mentioned “ADVANCED_OPTIMIZATIONS” option is a breeze. As you can see above, the task is as easy as assigning this option to the “compilation_level” argument of the compiler and finally passing the required parameters to the corresponding API. It’s all that it takes.

Logically, the previous example would be somewhat incomplete if I don’t show you how the target JavaScript file looks after it is parsed by the Closure Compiler. In the segment below I’m going to list the file’s compiled version, so you’ll be able to see more clearly the work that the compiler does behind the scenes.

Now, hurry up and read the next few lines.

The compiled version of the JavaScript file

As I promised in the section that you just read, below there’s the output generated by the Closure Compiler Service API after optimizing the previous JavaScript file. Take a look at it, please:

var a=document.createElement("div");a.setAttribute("id","divid");a.setAttribute("class","divclass");document.getElementsByTagName("body")[0].appendChild(a);

Well, that looks pretty interesting, doesn’t it? As you can see, the compiler has been more strict (if the term is really applicable in this case); not only has stripped the file’s white space and comments, but it has also shortened its variable names, and even completely removed the function’s declaration! Nevertheless, the script remains fully functional and performs exactly the same original task. Give it a try and see it for yourself.

This shows you the remarkable abilities of the Closure Compiler Service API when it’s instructed to apply more advanced algorithms to supplied JavaScript code. As usual, the best way to see how the “ADVANCED_OPTIMIZATIONS” option works is by practice, therefore I suggest that you develop your own set of examples and see the results that you get in each case.    
 
Final thoughts

In this fifth part of the series, you learned how to work with the “ADVANCED_OPTIMIZATIONS” option provided by Google’s Closure Compiler Service API, which can be used for performing 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. 

In addition, it’s worth noting that all of the examples developed so far have outputted compiled files in “text/javascript” format, which covers most use cases. However, the Closure Compiler Service API is capable of delivering compiled code in  XML and JSON as well with the same ease. Therefore, in the forthcoming tutorial I’m going to explore this feature in depth, so that you can start using it within your own web applications.

Don’t miss the next part!

[gp-comments width="770" linklove="off" ]

chat sex hikayeleri Ensest hikaye