Introducing Closure Stylesheets
Saturday, November 12, 2011 | 2:58 PM
Saturday, November 12, 2011 | 2:58 PM
Tuesday, January 25, 2011 | 2:15 PM
This is the last in a series of blog posts on how Closure Compiler decides what properties. Part 1 was about the --compilation_level=ADVANCED_OPTIMIZATIONS flag and part 2 was about renaming policies that didn't work. This blog post will be a bit of a grab bag of newer tools for better property renaming.
--warning_level=VERBOSE --jscomp_warning=missingProperties--jscomp_warning=checkTypesfoo.bar) to read a property that can't possibly be defined on that object, perhaps because you've forgotten to declare it in your externs.bar must be declared on all possible values of foo (except null), or it will be a compiler error. Closure Compiler uses a "may-define" approach. It only requires that some possible value of foo has a property bar. For example, if you have:function f(x) {
return x.apartment;
}apartment property is not assigned anywhere in the program. Similarly, if you have:/** @param {Element} x */
function f(x) {
return x.apartment;
}apartment property is not assigned on any object that could possibly be an Element. It will not emit a warning if apartment is assigned on some specific subtype of Element, like HTMLTextAreaElement.disambiguateProperties and ambiguateProperties.x in the program. If two types have a property xx// externs file/** @constructor */
function MyWidget() {
this.MyWidget$id = 3;
}
ambiguateProperties to minify the number of unique properties. Ambiguate properties will look at two property names on different objects such that there's no chance those objects will appear in the same variable. Then it will give those properties the same name.--variable_map_input_file
--variable_map_output_file
--property_map_input_file
--property_map_output_file
Friday, January 21, 2011 | 1:52 PM
This is the second in a series of blog posts on how Closure Compiler decides what properties to rename with the --compilation_level=ADVANCED_OPTIMIZATIONS flag. Part 1 talks about the current algorithm. This blog post will focus on property renaming policies that we tried that didn't work so well.
In the beginning, we tried to use coding conventions to decide when to rename things. Uppercase property names (foo.MyMethod) were renamed, and lowercase property names were not renamed. This didn't work well. One man's internal code is another man's external library. Sometimes you really didn't want uppercase property names to be renamed. Changing your code to use this convention meant breaking your API.
Later, we tried to move towards "smarter" algorithms, ones that did not require the programmer to be aware of renaming policies. These were called the "heuristic" property renaming policies. These algorithms looked at the entire input to the compiler, and tried to find all writes and reads to a specific property. If it saw at least one write to the property, and was reasonably sure that all the reads of that property came from those writes, then it renamed them all.
In small apps, heuristic renaming policies worked well. They were not very powerful, but they were easy to migrate to. Even when you didn't declare all the properties on external objects in the externs file, you'd usually still be ok. There would be no property writes to that property name, so the compiler wouldn't try to rename it.
But for medium to large apps, these advantages were a curse. Consider the following code:
/** @param {Object} json Some external JSON.
function f(json) {
return json.estate;
}
window['__receive_json'] = f;
// ...
// in some other code base
Foo.prototype.estate = 3;
f(new Foo());estate in your binary, the compiler would rename it. The compiler can't tell that you're calling f from external code, and that you expect estate to be preserved.json.estate to something like json['estate'] might break other projects that depend on it, because their binaries do expect estate to get renamed.Tuesday, January 18, 2011 | 1:47 PM
When you use Closure Compiler's --compilation_level=ADVANCED_OPTIMIZATIONS flag, the compiler will try to rename properties on your objects. For example, it may rename x.longPropertyName to x.a.
Because property renaming is a complex topic, we're going to split this discussion up into three blog posts. Part 1 is about the property renaming that you get with ADVANCED_OPTIMIZATIONS. Part 2 will be about other property renaming algorithms we've tried that didn't work so well. Part 3 will be about property renaming algorithms that we're currently experimenting on and are available from the Java API.
If you're using Closure Compiler's Java API, you have more fine-grained control over what renaming the compiler does. The API treats variable renaming (foo.bar -> a.bar) and property renaming (foo.bar -> foo.a) as completely independent optimizations. You can choose a variable renaming policy and a property renaming policy. The best property renaming policy, "All Unquoted," is what you get when you use ADVANCED_OPTIMIZATIONS. Most large Google projects use it. It significantly changes how we write JavaScript.
In the general case, a compiler can't rename properties at compile-time. You simply don't have enough information to try. There will always be objects that come from external sources that the compiler can't see (like JSON responses from the server), and property names that are undecidable. (Consider the expression foo[undecidableFunction()] = function(){};.) So property renaming can never be perfect. There will always be rules and gotchas.
Before we talk about the best property renamer, "All Unquoted," we have to define what we mean by "best." Usually, we use three criteria.
var obj = {
alice: true,
'bob': true
};
obj.claire = true;
obj.document = true;
window['obj'] = obj;
var a = {
a: true, // alice was not in quotes or in the externs file
bob: true // bob was in quotes
};
a.b = true; // claire was not in quotes or in the externs file
a.document = true; // document was in the externs file
window.obj = a; // obj was in quotes
--debug flag, the same properties still get renamed, but now it will be much easier to see what the original names were:var $obj$$ = {
$alice$: true,
'bob': true
};
$obj$$.$claire$ = true;
$obj$$.document = true;
window['obj'] = $obj$$;
obj.claire to obj['claire'].Foo.prototype.methodA = fn, then Alice can easily search for all calls to methodA in the codebase. She doesn't have to worry about opaque accesses to the method, like obj['method' + 'A'], because she knows that the compiler will break those accesses anyway.obj.alice) for compile-time property lookups, and array access (obj['alice']) for run-time property lookups.Tuesday, October 12, 2010 | 2:04 PM
In a past blog post, we talked about how Closure Compiler makes it easier to share common JavaScript by ensuring that everybody declares what variables they own.
Global variable uniqueness is something that we added checks for fairly early on. Some restrictions are less obvious. In this blog post we’ll talk about the this keyword.
Suppose Nathan wants to outsource hot dog eating contests, so that we don’t need an instance of Nathan to run one. To do so, he takes runHotDogContest and unpackHotDogs off the prototype, and makes them into static methods. The code now looks like this:
Nathan.runHotDogContest = function(player, boxes) {
for (var i = 0; i < boxes.length; i++) {
player.eatHotDogs(this.unpackHotDogs(boxes[i]));
}
};var run = Nathan.runHotDogContest;
run(joey, crateA);
run(kobayashi, crateB);
unpackHotDogs is not defined on this. The reason is subtle. To the person writing Nathan.runHotDogContest, this and Nathan seem like interchangeable variables. And as long as you’re testing them by invoking them as:Nathan.runHotDogContest(joey, crateA);
Nathan.runHotDogContest(kobayashi, crateB);
this is a secret argument to the function. It’s much like any other function argument, except that it appears on the left side of the function call instead of on the right side. So Nathan.runHotDogContest(...) passes Nathan as the this argument, and run(...) passes null as the this argument (which JavaScript helpfully coerces to window).secret arguments like this. So we’ve agreed that only constructors and prototype methods may use this, and the this argument must be an instanceof the relevant constructor. Closure Compiler enforces this in --warning_level VERBOSE, and will often emit a warning if it sees this used in a dangerous place.this context, you can explicitly document it with the @this annotation, and the warning will go away.Tuesday, August 31, 2010 | 3:45 PM
When collaborating with lots of engineers as we do here at Google, it’s important to keep a consistent coding style. To this end, we recently open sourced the Google JavaScript Style Guide.
Today we’re happy to announce we’re open sourcing a tool that will help you follow that style guide with minimal manual effort - the Closure Linter.
Take for example, this code:
var x = 10
var y=20;
for(var i = 0;i < 10; i++ ) {
x += i;
y -= i;
}
var z = [10, 20,];
x = y + z[0]
+ 10;
gjslint --strict fixme.js we getLine 1, E:0010: (New error) Missing semicolon at end of line
Line 2, E:0002: Missing space before "="
Line 2, E:0002: Missing space after "="
Line 4, E:0002: Missing space before "("
Line 4, E:0002: Missing space after ";" in for statement
Line 4, E:0001: Extra space before ")"
Line 6, E:0006: (New error) Wrong indentation: expected any of {2} but got 3
Line 9, E:0121: Illegal comma at end of array literal
Line 12, E:0120: Binary operator should go on previous line "+"
Found 9 errors, including 2 new errors, in 1 files (0 files OK).
fixjsstyle --strict fixme.js, 7 of the 9 errors are automatically fixed for us!Monday, August 30, 2010 | 1:34 PM
The Closure Compiler you know is very different than the Closure Compiler that’s familiar to Google engineers. The defaults at Google are much stricter. It forbids duplicate global variable declarations, performs type-checking, and restricts a number of other patterns that are common in non-Closure-style JavaScript. By default, the “Google build” of Closure Compiler is like running the "open source build" of Closure Compiler with the --warning_level VERBOSE flag.
Why? Almost every contributor to Closure Tools works on web applications. We want to be able to share code. Part of that means we should be able to fix the shared code, and integrate it into all our apps quickly so that, for example, fixes for IE9 will make it out to production within a reasonable time. To integrate those changes safely, we need to be confident that they won’t break anything. Compiler restrictions help us do that. Sometimes those restrictions are obvious...sometimes less so.
Suppose Joey writes some library code:
Joey.prototype.eatHotDogs = function(hotDogs) {
for (i = 0; i < hotDogs.length; i++) {
this.eat(hotDogs[i]);
}
};Nathan.prototype.runHotDogContest = function(player, boxes) {
for (i = 0; i < boxes.length; i++) {
player.eatHotDogs(this.unpackHotDogs(boxes[i]));
}
};i, so a variable i got magicked into the global scope. When Joey originally wrote his code, he tested it and it worked fine. But when Nathan tried to use it, he found that eatHotDogs() was "resetting" the i in his loop every time he called it.var in one file may trigger a bug in an unrelated file that hasn’t been touched in years. Or it may cause a bug years later in unrelated new code. For a library shared across many codebases, non-localized bugs can stop progress quickly because it’s so hard to know where to start looking.VERBOSE mode, Closure Compiler enforces that all variables must be declared with the var or function or catch keywords. Global variables may only be declared once. The Google JS style guide has naming conventions to make these collisions even less likely.Node because it will conflict with DOM Nodes.--warning_level=VERBOSE mode and see what you find in your code!©2010 Google - Privacy Policy - Terms of Service