Home arrow AJAX & Prototype arrow 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.

TABLE OF CONTENTS:
  1. Google's Closure Compiler Service API: the ADVANCED_OPTIMIZATIONS Option
  2. Shortening JavaScript files with the ADVANCED OPTIMIZATIONS option
By: Alejandro Gervasio
Rating: starstarstarstarstar / 0
October 06, 2010

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

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.



 
 
>>> More AJAX & Prototype Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

AJAX & PROTOTYPE ARTICLES

- Best AJAX Tutorials for Forms
- The Best AJAX Tutorials
- 8 Great Ajax Tutorials
- Using Ajax and jQuery
- Using Ajax and jQuery with HTML Forms
- Ajax.org Offers Cloud9 IDE for JavaScript
- Java Technologies Provider ICEsoft Releases ...
- Using Recaptcha in AJAX Prototype Framework ...
- Google's Closure Compiler Service API: Addit...
- Installing Google Web Toolkit: Introducing t...
- Google's Closure Compiler Service API: Displ...
- Google's Closure Compiler Service API: Deliv...
- Google's Closure Compiler Service API: the A...
- Google's Closure Compiler Service API: the S...
- Google's Closure Compiler Service API: Optim...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: