0

I am compiling the following code with ADVANCED_OPTIMIZATIONS using Google Closure Compile :

(function() {
  /** @const */
  var DEBUG = false;

  var Namespace = {};
  window['Namespace'] = Namespace;

  (function() {
     /**
      * @constructor
      */
    function Test(tpl) {
      Helper.debug('Test');
    }

    Namespace['Test'] = Test;

  })();

  var Helper = 
    (function(){
       /**
        * @constructor
        */
      function Helper(){
        this.debug = function(arg){
          if(DEBUG){
            console.log(arg);
          }
        }
      };

      return new Helper;
    })();

})();

My intention was for the compiler to strip all Helper.debug messages when DEBUG == false, and to rename the debug function to a short name when DEBUG == true. I'm hoping for something like this from the compiler:

DEBUG == false:

var a={};window.Namespace=a;a.Test=function(){};

DEBUG == true:

var a={};window.Namespace=a;a.Test=function(){console.log("Test")};

I end up with this instead:

DEBUG == false:

 var a={};window.Namespace=a;a.Test=function(){b.debug("Test")};var b=new function(){this.debug=function(){}};

DEBUG == true:

var a={};window.Namespace=a;a.Test=function(){b.debug("Test")};var b=new function(){this.debug=function(c){console.log(c)}};

In neither case is the debug function renamed. I figure it should be, since it is not exported, nor accessible (as far as I can tell) from Namespace. It's only called from the Namespace.Test() constructor. If I don't call it from there, Closure strips the debug function (because it is not used anywhere), but I want to be able to call it through the functions in Namespace, and still have it be renamed.

I've tried various versions of the above code. Using prototype.debug on Helper, moving the Helper constructor to the same scope as Namespace, etc. As long as the debug function is attached to my Helper object though, I can't find a way to get my desired output from the compiler.

If I don't use the Helper object, and just declare debug as a function, I get exactly my desired output, however this is just an example and I really have many functions which are attached to the Helper object and I would like them all to be renamed to short names. Example code which gives me my desired output:

(function() {
  /** @const */
  var DEBUG = false;

  var Namespace = {};
  window['Namespace'] = Namespace;

  (function() {
     /**
      * @constructor
      */
    function Test(tpl) {
      debug('Test');
    }

    Namespace['Test'] = Test;

  })();

  function debug(arg){
    if(DEBUG){
      console.log(arg);
    }
  }

})();
Paul
  • 139,544
  • 27
  • 275
  • 264
  • You can have conditional code using goog.DEBUG; if(goog.DEBUG){..code not compiled when set compiler --define goog.DEBUG=false ...} – HMR Jun 01 '13 at 09:29
  • I'm not sure if closure compiler works well with objects crated in the manner you create them. Will debug be renamed if you define helper with var Helper = function(){};Helper.prototype.debug=function(){}? or: var Helper ={debug:function(){}} Your pattern looks like something used for creating private variables through closure but don't see that anywhere in closure library code, they would use this.private_ with @private annotation instead. – HMR Jun 01 '13 at 09:41
  • Hi @HMR Thanks for your input. I didn't know about `good.DEBUG`, but I have many other functions attached to Helper that I am wanting renamed to short names and none are being renamed. I tried both of those other ways of initializing Helper and I have the same problem still. – Paul Jun 03 '13 at 16:58
  • I would recommend you replace some of your constants with @define https://developers.google.com/closure/compiler/docs/js-for-compiler now you don't have to change your code when you have a test and production built script you can set these constants with compiler options. The compiler will then remove code that becomes dead. Basically `goog.DEBUG` is one of those constants you change with compiler options. – HMR Jun 04 '13 at 01:01
  • Updated the answer, reason `debug` isn't renamed is because it's used in the default externs file (where document, window, document.getElementById ... is defined) – HMR Jun 06 '13 at 01:40

1 Answers1

1

I tried your code and found that the name debug is not converted but other names will be. Replaced console.log with alert because I don't have an externs file for console.log. Here is your modified code (only renamed debug to 'something' and console.log to alert):

(function() {
  /** @const */
  var DEBUG = false;

  var Namespace = {};
  window['Namespace'] = Namespace;

  (function() {
     /**
      * @constructor
      */
    function Test(tpl) {
      Helper.something('Test');
    }

    Namespace['Test'] = Test;

  })();

  var Helper = 
    (function(){
       /**
        * @constructor
        */
      function Helper(){
        this.something = function(arg){
          if(DEBUG){
            alert(arg);
          }
        }
      };

      return new Helper;
    })();

})();

Compiling your code now has more or less the expected output (no constructor is used):

java -jar compiler.jar --js helper.js --js_output_file out.js --compilation_level=ADVANCED_OPTIMIZATIONS --formatting=PRETTY_PRINT --warning_level=VERBOSE

(function() {
  var a = {};
  window.Namespace = a;
  (function() {
    a.Test = function() {
    }
  })()
})();

Setting DEBUG to true gives me:

(function() {
  var a = {};
  window.Namespace = a;
  (function() {
    a.Test = function() {
      b.a()
    }
  })();
  var b = function() {
    return new function() {
      this.a = function() {
        alert("Test")
      }
    }
  }()
})();

Please note that quoted properties will not be renamed. So using this['something'] = function(arg){ and Helper['something']('Test'); will cause something not to be renamed. My guess is you already knew that because you're using window['namespase']['Test']

[update]

Closure compiler does not rename methods that are defined in externs. For example "document" is defined in an extern used by the compiler (if it wasn't then every time you use document would cause an error). So if you were to rename Helper.debug to Helper.getElementById it still would not rename it (getElementById is defined in the externs file that the compiler uses by default). Here is the source from Oreilly.Closure.The.Definitive.Guide.Sep.2010 page 391:

var mystery = function(obj) {
alert(obj.max());
};

Because mystery does not have any type information, obj could be either the built-in Math object or an example.NumSet. Because it is possible that Math is supplied as the argument to mystery, the Compiler is unable to rename the max() method. For this reason, the Compiler takes a conservative approach to renaming variables by never renaming a variable if it also appears in the externs. This is one of the main reasons why the Compiler does not include additional externs files (such as those in contrib/ externs) by default: adding more names to the externs pool may unnecessarily reduce the amount of variable renaming done by the Compiler.

Even if you provide type information the compiler won't rename without some extra flags --use_types_for_optimization. Your code would still won't rename debug but that's maybe because you need a typedef for it. Renaming debug to myDebug would cause the function to be renamed.

Why does Closure Compiler not rename objects with certain names?

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • Thanks a ton HMR! I had tried a lot of ways of rewriting this and I even had tried `Helper.test` which wasn't renamed for the exact same reason. I appreciate all the effort you put into answering this for me. – Paul Jun 06 '13 at 16:24
  • You're welcome. Stumbled upon the reason why these functions aren't renamed when I finished reading the book so thought I should update the answer. – HMR Jun 06 '13 at 23:54