user1127813
user1127813

Reputation: 299

Google Closure Compiler's ADVANCED_OPTIMIZATIONS option

I've been checking out the Google Closure Compiler recently. I downloaded the .jar file and gave it a test drive. So far, I must say that I've been very impressed. I can certainly see its usefulness beyond minimization. Props to the Google team!

I do have one small gripe though. It seems to me that you only get two options as far as optimization goes. It's either SIMPLE_OPTIMIZATIONS or ADVANCED_OPTIMIZATIONS. The former, although adequate, is very simple IMHO. For one thing, unless I'm missing something, it leaves all property names untouched. It also does not remove unreachable code. On the other hand, the latter option is simply too destructive.

Now, I'm fairly new to JavaScript, so it's very probable that I'm missing something. If I say something stupid, feel free to school me. That said, I can understand the issues with renaming in JavaScript. The Google team recommends using the bracket notation (object['property']) instead of the dot notation (object.property) to access the properties that you do not want changed and never mixing the two uses. They also suggest 'exporting' methods by using the following pattern:

MyClass = function(name) {
  this.myName = name;
};

MyClass.prototype.myMethod = function() {
  alert(this.myName);
};

window['MyClass'] = MyClass; // <-- Constructor
MyClass.prototype['myMethod'] = MyClass.prototype.myMethod;

However, there are legitimate cases that you want to mix the two notations. Let's say we are building a game. The game's code is completely isolated inside a closure. It does not 'export' anything to the global scope, nor does it need to. In fact, it really should not touch the window object. However, it does need to read some in-game properties from XML configuration files.

Sample JavaScript:

var TheGreatAdventure = (function(window) {

    function Fighter() {
        // Private to application
        this.id        = 42;
        // Accessible to XML configuration system
        this.name      = 'Generic Jen';
        this.hitPoints = 100;
        this.onAttack  = genericFighterAttack;
        this.onSpeak   = genericFighterSpeak;
        ...
    }
    Fighter.publishedProperties = ['name', 'hitPoints', 'onAttack', 'onSpeak']

    function genericFighterAttack() {...}
    function genericFighterSpeak() {...}

    function cassieAttack() {...}
    function cassieSpeak() {...}

    ...

    EntityReader = {
        ...
        function readFromXMLNode(attributes, entityClass, entityInstance) {
            for (var i = 0; i < attributes.length; i++) {
                var attribute = attributes[i];
                if (attribute.nodeName in entityClass.publishedProperties)
                    entityInstance[attribute.nodeName] = bindContext[attribute.value];
            }
        }
        ...
    }

}(window));

Sample XML configuration file:

<Fighter name='Custom Cassie' onAttack='cassieAttack' onSpeak='cassieSpeak'/>

Not only would the above system fail to assign the properties, the functions cassieAttack and cassieSpeak would have been eliminated during minimization as dead code!

Now, there's no way I'm accessing all of the 'published' properties using the bracket notation throughout the game's code. Even if there's no run-time penalty in doing so (there should not be any), there's still a lot of extra typing involved and it's (IMO) an eyesore. With such common properties, everything would show up as a string inside a text editor, defeating the purpose of syntax highlighting!

It seems to me that a simple @preserve (or something similar) directive over those properties would allow ADVANCED_OPTIMIZATIONS to be used with minimum cost in final program size. Am I missing something?

Upvotes: 2

Views: 2026

Answers (2)

John
John

Reputation: 5468

The compiler has support for this, but it is awkward, and depends on a "primitive" from the Closure Library called "goog.reflect.object":

/** @nocollapse */
Fighter.publishedProperties = goog.object.transpose(goog.reflect.object(
    Fighter, {fullName:1, hitPoints:2}));

This avoid the use of quoted properties. If this is the only thing that you use from the Closure Library everything but the "goog.object.transpose" function will be compiled out (goog.object.transpose isn't special so you are free to use an alternate implementation). This is type safe and can be used with the compiler's type based optimizations (see a discription of that here: http://code.google.com/p/closure-compiler/wiki/ExperimentalTypeBasedPropertyRenaming ).

The key is that goog.reflect.object must be used directly, taking the constructor and an object literal with the keys of the properties you need to preserve.

The other thing that you will want to be aware of this that in ADVANCED mode, if Fighter.publishedProperties is defined in global scope, it will be removed from the constructor due to namespace collapsing. @nocollapse prevents this. An alternative would be to use a helper method to add the property:

function addPublishedProperties(obj, value) {
  obj.publishedProperties = goog.object.transpose(value);
}

Ok, I've covered a lot of ground here so be sure to let me know if you would like clarification.

Upvotes: 3

jjrv
jjrv

Reputation: 4345

This answer was completely rewritten, turns out there's a way to do what user1127813 wants.

You need to provide a property mapping file that maps some names to themselves, using the --property_map_input_file flag. Suppose you have the following original code in test.js:

/** @constructor */
function Fighter() {
    this.ID        = 42;
    this.fullName  = 'Generic Jen';
    this.hitPoints = 100;
}
Fighter.publishedProperties = ['fullName', 'hitPoints'];

var jen = new Fighter();
var bob = new Fighter();

bob.ID = 54;
bob.fullName = 'Bob the Destructor';
bob.hitPoints = 1337;

for(i = 0; i < Fighter.publishedProperties.length; i++) {
    prop = Fighter.publishedProperties[i];
    alert(prop + ' = ' + bob[prop]);
}

Compile it like so:

java -jar closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js test.js --property_map_output_file testprop.txt --js_output_file test2.js

You will get a new file test2.js (with contents that don't work) and another file testprop.txt that contains:

ID:a
hitPoints:c
fullName:b

Change testprop.txt so it looks like this:

ID:ID
hitPoints:hitPoints
fullName:fullName

Then recompile with testprop.txt as input instead of output:

java -jar closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js test.js --property_map_input_file testprop.txt --js_output_file test2.js

Observe the contents of test2.js:

var a=["fullName","hitPoints"],b=new function(){};b.ID=54;b.fullName="Bob the Destructor";b.hitPoints=1337;for(i=0;i<a.length;i++)prop=a[i],alert(prop+" = "+b[prop]);

The desired properties are now accessed with their original name using the dot notation and the program will correctly show popups with the published properties of bob.

Upvotes: 3

Related Questions