Removing Debug Statements from Javascript via compiler

We all know the pain of getting a bug filed against some of your code and having to dig through it later to find the root cause. Especially, since many times we end up having to add back in all of our debug statements in order to track down the issue. There has to be a better way. What if we could write all of our JavaScript with the debug code left in?

The Better way

By now, we are all using some compiler to compile your code. Right? If you haven’t started, grab the Closure Compiler to follow through on this example.

  1. Download the latest Closure Compiler http://closure-compiler.googlecode.com/files/compiler-latest.zip
  2. Unzip
  3. Copy the compiler.jar file into your directory with the JS Files.
  4. follow along below and enjoy!

If you are using Closure Compiler or Uglify already, you are in luck! Both of these compilers allow you to annotate your JavaScript. Annotating your files allows you to describe them to the compiler. Things like @license will make sure that the comment goes to the top of the file after compression. Look through the http://code.google.com/closure/compiler/docs/js-for-compiler.html list to see what each one does. We are going to focus on @define.

Take the following Code

/**
 * @preserve Copyright 2009 SomeThirdParty.
 * Here is the full license text and copyright
 * notice for this file. Note that the notice can span several
 * lines and is only terminated by the closing star and slash:
 * BORROWED FROM THE ANNOTATED DOCS
 */

/** @define {boolean} */
var DEBUG = true;
var x = 1,
        y = 2;

var myNewObj = function(name, item) {
        var shortName = "Mr " + name,
                longName = "Dr. " + name + " M.D.S";

        this.getShortName = function() {
                if (DEBUG){
                        console.log("I am returning short name: " , shortName)
                }
                return shortName;
        }

        this.getLongName = function() {
                if (DEBUG){
                        console.log("I am returning long name: " , longName)
                }
                return longName;
        }

        return this;
}
// a bunch of your other code her

if (DEBUG) {
        var drWho = new myNewObj("Who", "blank");
        console.log(drWho.getLongName());
}

If we run the above code, we get the following output:

I am returning long name:  Dr. Who M.D.S
Dr. Who M.D.S

The above code would allow us to quickly turn on and off the debug statements and see what the JS code was doing. If we use this pattern, we can actually not worry about the value of DEBUG when we are finished with the file. We can actually change the value when we compile the input.

The compiler

If you take the above code and compress it using the following command:

	java -jar compiler.jar --js input.js --define=DEBUG=false --js_output_file output.js --compilation_level SIMPLE_OPTIMIZATIONS --formatting PRETTY_PRINT

It will turn the output into:

/*
 Copyright 2009 SomeThirdParty.
 Here is the full license text and copyright
 notice for this file. Note that the notice can span several
 lines and is only terminated by the closing star and slash:
 BORROWED FROM THE ANNOTATED DOCS
*/
var DEBUG = !1, x = 1, y = 2, myNewObj = function(a) {
  var b = "Mr " + a, c = "Dr. " + a + " M.D.S";
  this.getShortName = function() {
    DEBUG && console.log("I am returning short name: ", b);
    return b
  };
  this.getLongName = function() {
    DEBUG && console.log("I am returning long name: ", c);
    return c
  };
  return this
};
if(DEBUG) {
  var drWho = new myNewObj("Who", "blank");
  console.log(drWho.getLongName())
}
;

The compiled code now outputs the following:

undefined

The return output for the new code is blank

Advanced Compiling

We can actually take this one step further, but be warned this is not for the faint of heart. If you want the statements removed completely you will need to rework your code just a bit.

Lets start with a modified version of the starting code:

	/**
	* @preserve Copyright 2009 SomeThirdParty.
	* Here is the full license text and copyright
	* notice for this file. Note that the notice can span several
	* lines and is only terminated by the closing star and slash:
	* BORROWED FROM THE ANNOTATED DOCS
	*/                

	/** @define {boolean} */
	var DEBUG = true;
	var x = 1,              
	y = 2;          

	(function() {
	        /**
	          * A new Obj
	          * @constructor 
	          */            
	        myNewObj = function (name, item) {
	                this.shortName = "Mr " + name,
	                this.longName = "Dr. " + name + " M.D.S";

	                return this;
	        }

	        myNewObj.prototype.getLongName = function() {
	                /** 
	                  * @this {myNewObj}
	                  * @return {string}
	                  */
	                if (DEBUG){
	                        console.log("I am returning long name: " , this.longName)
	                }
	                return this.longName;
	        }

	        myNewObj.prototype.getShortName = function() { 
	                if (DEBUG){
	                        console.log("I am returning short name: " , this.shortName)
	                }
	                return this.shortName;
	        }

	        myNewObj.prototype['getLongName'] = myNewObj.prototype.getLongName;
	        myNewObj.prototype['getShortName'] = myNewObj.prototype.getShortName;

	        if (DEBUG) {
	                var drWho = new myNewObj("Who", "blank");
	                console.log(drWho.getLongName());
	        }
	})();

Here we moved the code into a closure function.

	(function() {
	})();

Next we split the myNewObj into a constructor and prototypical inheritance. We also added a new annotation: @constructor. This tells the compiler that it is able to be invoked from other areas of the code.

	/**
	  * A new Obj
	  * @constructor 
	  */            
	myNewObj = function (name, item) {
	        this.shortName = "Mr " + name,
	        this.longName = "Dr. " + name + " M.D.S";

	        return this;
	}

	myNewObj.prototype.getLongName = function() {
	        /** 
	          * @this {myNewObj}
	          * @return {string}
	          */
	        if (DEBUG){
	                console.log("I am returning long name: " , this.longName)
	        }
	        return this.longName;
	}

	myNewObj.prototype.getShortName = function() { 
	        /** 
	          * @this {myNewObj}
	          * @return {string}
	          */
	        if (DEBUG){
	                console.log("I am returning short name: " , this.shortName)
	        }
	        return this.shortName;
	}

Next, we need to make sure that the functions are available to everyone else. Since the advanced compiler will change all variable names, and delete portions of code that aren’t reachable, we will make them reachable again. We do this by attaching each of the available functions onto the objects prototype again. For more information on this, check out Advanced Compilation and Externs tutorial on Closure Compiler.


	myNewObj.prototype['getLongName'] = myNewObj.prototype.getLongName;
	myNewObj.prototype['getShortName'] = myNewObj.prototype.getShortName;	

Now that we have the JavaScript setup properly, we can compile to see what the output becomes. We compile the code with the following options

	java -jar compiler.jar --js input.js --define=DEBUG=false --js_output_file output.js --compilation_level ADVANCED_OPTIMIZATIONS --formatting PRETTY_PRINT --generate_exports

It will turn the output into:

/*
 Copyright 2009 SomeThirdParty.
 Here is the full license text and copyright
 notice for this file. Note that the notice can span several
 lines and is only terminated by the closing star and slash:
 BORROWED FROM THE ANNOTATED DOCS
*/
myNewObj = function(a) {
  this.d = "Mr " + a;
  this.c = "Dr. " + a + " M.D.S";
  return this
};
myNewObj.prototype.a = function() {
  return this.c
};
myNewObj.prototype.b = function() {
  return this.d
};
myNewObj.prototype.getLongName = myNewObj.prototype.a;
myNewObj.prototype.getShortName = myNewObj.prototype.b;

Summary

Don’t feel overwhelmed trying out the advanced optimizations. Sometimes it just takes some playing around with the code to get it into the best possible outcome. If you aren’t that familiar with JavaScript, I would highly recommend sticking with the SIMPLE_OPTIMIZATIONS and just having a slightly bigger file to content with.

One thought on “Removing Debug Statements from Javascript via compiler

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>