May/June issue of acmqueue


The May/June issue of acmqueue is out now


Web Development

  Download PDF version of this article PDF

Reveling in Constraints

The Google Web Toolkit is an end-run around Web development obstacles.

Bruce Johnson, Google

The Web’s trajectory toward interactivity, which began with humble snippets of JavaScript used to validate HTML forms, has really started to accelerate of late. A new breed of Web applications is starting to emerge that sports increasingly interactive user interfaces based on direct manipulations of the browser DOM (document object model) via ever-increasing amounts of JavaScript. Google Wave, publicly demonstrated for the first time in May 2009 at the Google I/O Developer Conference in San Francisco, exemplifies this new style of Web application. Instead of being implemented as a sequence of individual HTML “pages” rendered by the server, Wave might be described as a client/server application in which the client is a browser executing a JavaScript application, while the server is “the cloud.”

The key browser technologies responsible for enabling this new generation of Web applications are not especially new: JavaScript runs within the browser to manipulate the browser DOM as a means for actually rendering the UI and responding to user events; CSS (cascading style sheets) are used to control the visual style of the UI; and the XHR (XmlHttpRequest) subsystem allows JavaScript application code to communicate asynchronously with a Web server without requiring a full-page refresh, thus making incremental UI updates possible. There are many more browser technologies that read like alphabet soup: XML, VML, SVG, JSON, XHTML, DTD… the list goes on.

Curiously, these browser technologies have been available for many years, yet it has taken until now for mainstream developers to cobble them together to create compellingly interactive Web applications. Why? The opinion of the Google Web Toolkit team—a perspective that can, of course, be endlessly debated—is that the primary obstacle is literally the implementation details. It is simply too hard to code them all to work together in a way that provides quick and reliable performance on the wide range of browsers available.

Our response was to design GWT (Google Web Toolkit), which allows developers to spend most of their time writing and debugging application code using the Java language rather than JavaScript. Working in Java means developers can leverage the productivity of Java IDEs (integrated development environments). Then, once they’re happy with their Java code, developers can use GWT’s cross-compiler to convert Java source code into functionally equivalent, and optimized, JavaScript. The idea of cross-compilation tends to raise eyebrows, and we’ve heard more than our fair share of incredulity about this, so let’s take a step back to describe how we decided on this approach—and how things have actually worked out.

GWT began life as a prototype that Google software engineer Joel Webber and I produced as a way to address what might best be described as the over-constrained problem of Web development. Thanks largely to the success of Google Maps and Gmail, several points had simultaneously become clear to us:

In other words, the browser—in particular, XHR, JavaScript, and the DOM—presented a capable, albeit frustrating, platform for delivering applications.

JavaScript Reservations

At the same time, we had questions about whether JavaScript was a good language in which to write business-critical applications. On the one hand, JavaScript is a flexible, dynamically typed language that makes certain types of code easy to write and pleasantly succinct. On the other hand, that same flexibility can make JavaScript harder to use within a team environment because there is no easy way to enforce the use of consistent conventions automatically across an entire codebase. It is true that, with significant extra work, a JavaScript team could insist that all script be augmented with extra metadata (JSDoc, for example) and then use additional tools to verify that all script complies with the agreed-upon conventions. This would also necessarily restrict the developers to a statically analyzable subset of JavaScript, since some of JavaScript’s most dynamic constructs—eval() and the with statement are good examples—thoroughly defeat static analysis. All this extra stuff—the metadata and verification tools—seemed an awful lot like an ad-hoc static type system and a compiler front end.

Furthermore, we badly wanted an IDE. Our experience had thoroughly convinced us that IDEs are a boon to productivity, quality, and maintenance. Features that are status quo in modern Java IDEs such as code completion, debugging, integrated unit testing, refactoring, and syntax-aware search were virtually nonexistent for JavaScript. The reason for this, again, is related to the dynamism of JavaScript. For example, it isn’t possible to provide sound code completion in a JavaScript editor in the general case because different runtime code paths can produce different meanings for the same symbols. Consider this legal JavaScript:

function foo(m) {
     alert(“You called foo with the message: “ + m);
}
if (dayOfWeek() == “Wednesday”) {
     foo = 3;
}
foo(“Hello?”);     // [1]

At [1], it’s impossible to tell statically whether foo is a function or a variable, so IDE code completion can only provide “potentially correct” suggestions, which is an optimistic way of saying that you must double-check the IDE’s code completion suggestions, which in turn is likely to diminish much of the would-be productivity gain to be realized from a JavaScript IDE. For similar reasons, automated refactoring tools for JavaScript are rarely seen, even while such tools are ubiquitous in the Java world. These observations made JavaScript seem less attractive as a language in which to write large applications.

We finally realized that we wanted to develop our source code in the Java language, yet deploy it as pure JavaScript. By choosing the Java language as the origination language, we could immediately leverage the great ecosystem of Java tools, especially the great Java IDEs out there. The only question was how to produce JavaScript from the Java source input. Our answer was to build a Java-to-JavaScript compiler—an optimizing compiler, in fact, because we figured that since we were going to the trouble of writing a compiler anyway, why not make sure it produced small, efficient JavaScript? Furthermore, we discovered that because Java has a static type system, it allowed for many compile-time optimizations that JavaScript—being dynamically typed—would not. 

As an example of this, consider the interaction between inlining and devirtualization (that is, the removal of polymorphism in a method invocation). In JavaScript, developers often simulate object-oriented constructs such as polymorphism. For example, if you wanted to have a simple Shape hierarchy in JavaScript, you might write this:

function Shape() { }
Shape.prototype.getArea = function() { }

function Circle(radius) { this.radius = radius; }
Circle.prototype = new Shape();
Circle.prototype.getArea = function() {
	return this.radius * this.radius * Math.PI;
}

function Square(length) { this.length = length; }
Square.prototype = new Shape();
Square.prototype.getArea = function() {
	return this.length * this.length;
}

function displayArea(shape) {
	alert(“The area is “ + shape.getArea());
}

function runTest() {
     var shape1 = new Circle(3);
     var shape2 = new Square(2);
     displayArea(shape1);
     displayArea(shape2);
 }

The same thing written in the Java language for use with GWT might look like this:

abstract class Shape {
     public abstract double getArea();
}
class Circle extends Shape {
     private final double radius;
     public Circle(double radius) { this.radius = radius; }
     @Override public double getArea() {
     	return radius * radius * Math.PI;
     }
}
class Square extends Shape {
     private final double length;
     public Square(double length) { this.length = length; }
     @Override public double getArea() { return length * length; }
}
static void displayArea(Shape shape) {
	Window.alert(“The area is “ + shape.getArea());
}
static void runTest() {
     Shape shape1 = new Circle(3);
     Shape shape2 = new Square(2);
     displayArea(shape1);
     displayArea(shape2);
}

The source for the two examples looks nearly identical, except for minor syntax differences, the use of @Override (which is useful for helping to prevent bugs), and the presence of explicit type names sprinkled on fields, methods, and local variables.

Because of the extra type information, the GWT compiler is able to perform some optimizations. The unobfuscated version of the GWT compiler output looks approximately like this:

function runTest() {
     var shape1, shape2;
     shape1 = $Circle(new Circle(), 3);
     shape2 = $Square(new Square(), 2);
     alert(‘The area is ‘ + shape1.radius
          * shape1.radius * 3.141592653589793); // [1]
     alert(‘The area is ‘ + shape2.length * shape2.length); // [2]
}

Note that in [1] and [2] above, a cascade of optimizations was made. 

First, the compiler inlined both calls to the displayArea() method. This proved helpful because it removed the need to generate code for that method. Indeed, displayArea() is completely absent from the compiled script, resulting in a minor size reduction. Even better, the inlined code is amenable to being further optimized in a usage-specific context where more information is available to the optimizer.

Next, the optimizer noticed that the types of shape1 and shape2 could be “tightened” to types more specific than their original declaration. In other words, although shape1 was declared to be a Shape, the compiler saw that it was actually a Circle. Similarly, the type of shape2 was tightened to Square. Consequently, the calls to getArea() in [1] and [2] were made more specific. The former became a call to Circle’s getArea(), and the latter became a call to Square’s getArea(). Thus, all the method calls were statically bound, and all polymorphism was removed.

Finally, with all polymorphism removed, the optimizer inlined Circle’s getArea() into [1] and Square’s getArea() into [2]. Both getArea() methods are absent from the compiled script, having been inlined away. Math.PI is a compile-time constant and was also trivially inlined into [1].

The benefit of all these optimizations is speed. The script produced by the GWT compiler executes more quickly because it eliminates multiple levels of function calls. 

For obvious reasons, large codebases tend to be written with an emphasis on clarity and maintainability rather than just on sheer performance. When it comes to maintainability, abstraction, reuse, and modularity are absolute cornerstones. Yet, in the previous example, maintainability and performance come into direct conflict: the inlined code is faster, yet no software engineer would write it that way. The “maintainability vs. performance” dichotomy isn’t unique to Java code, of course. It is equally true that writing modular, maintainable JavaScript tends to produce slower, larger script than one would prefer. Thus, all developers building complex Web applications have to face the reality of this trade-off. The pivotal question would seem to be just how amenable your codebase is to optimization once you’ve written it. In that regard, the Java type system provides great leverage, and that is how the GWT compiler is able to include many optimizations similar to the ones shown here to help mitigate the “abstraction penalty” you might otherwise end up having to pay in a well-designed object-oriented codebase.

Bringing Together Two Worlds 

Of course, the creation of an environment that allows developers to build browser-based applications in Java addresses only one part of the development cycle. Like most developers, we do not produce perfect code, so we knew we would also have to address the issues involved in debugging GWT programs.

Upon first hearing about GWT, people often assume you use it in the following way:

  1. Write Java source.
  2. Compile to JavaScript with GWT’s compiler.
  3. Run and debug the JavaScript in a browser. 

In fact, that is not the way you work in GWT at all. You spend most of your time in GWT’s hosted mode, which allows you to run and debug your Java code in a normal Java debugger (for example, Eclipse), just as you’re accustomed to doing. Only when the application is written and debugged do you need actually to compile it into JavaScript. Thus, everyone’s reflexive fear of never being able to understand and debug the compiled JavaScript proves to be unfounded.

The secret to making hosted mode an effective debugging environment is that it does not merely simulate the behavior of a browser while debugging in Java. Hosted mode directly combines true Java debugging with a real browser UI and event system. Hosted mode is conceptually simple, and it executes in a single JVM (Java Virtual Machine) process:

  1. Launch an instance of an actual browser, embedded in-process, that can be controlled by Java code via JNI (Java Native Interface). We call this the hosted browser.
  2. Create a CCL (compiling class loader) to load the GWT module’s entry-point classes.
  3. Whenever the CCL is asked to fetch a class, it checks to see if the class has JSNI (JavaScript Native Interface) methods. If not, the class can be used directly. If native methods are found, the class gets compiled from source and the JSNI methods are rewritten. 
  4. Run the bytecode of the entry-point class, which will in turn request other classes be loaded by the CCL, which repeats the process from step 3.

Step 3, rewriting JSNI methods, is the really neat part here. JSNI is the way to implement native Java methods in handwritten JavaScript, such as:

// This is Java!
static native Element createDivElement() /*-{
     // This is JavaScript!
     return document.createElement(“div”);
}-*/;

The CCL knows about JSNI and rewrites it to look something like this:

// A static initializer is introduced in the class.
static {
     hostedBrowser.injectFunc(“createDivElement”,
          “return document.createElement(\”div\”);”);
}
// The method becomes all-Java and is no longer native.
static Element createDivElement() {
     return hostedBrowser.invokeInjectedFunc(this, “createDivElement”);
};

Thus, the hosted-mode CCL turns JSNI methods into thunks that redirect their calls into the hosted browser’s JavaScript engine, which in turn drives the real browser DOM. 

From the JVM’s point of view, everything described here is pure Java bytecode and can therefore be debugged normally using a Java debugger. From the developer’s point of view, he or she can see the true behavior of a real browser being driven by the Java source code without it first having been cross-compiled into pure JavaScript.

Which brings up perhaps the most exciting point about hosted mode: because it works dynamically with Java code and does not depend on invoking the GWT cross-compiler (which can be slow), hosted mode is really fast. This means developers get the same kind of run/tweak/refresh behavior they enjoy whenever working directly with JavaScript.

GWT thus manages to combine the benefits of a traditional optimizing compiler with the quick development turn-around of dynamic languages. Although the compilation technology may appear complex, it is actually fairly standard fare for optimizing compilers. The real technical problems we encountered along the way revolved around our efforts to create UI libraries to simultaneously account for browser-specific quirks without compromising size or speed. In other words, we needed to supply many different implementations of UI functionality—version A for Firefox, version B for Safari, and so forth—without burdening the compiled application with the union of all the variations, thereby forcing each browser to download at least some amount of irrelevant code. Our solution is a unique mechanism we dubbed deferred binding, which arranges for the GWT compiler to produce not one output script, but an arbitrary number of them, each optimized for a particular set of circumstances. 

Each compiled output is a combination of many different implementation choices, such that each script has exactly (and only) the amount of code it requires. It’s worth mentioning that in addition to dealing with browser variations, deferred binding can specialize compilations along other axes as well. For example, deferred binding is used to create per-locale specializations (for example, why should a French user have to download strings localized for English, or vice versa?). In fact, deferred binding is completely open ended, so developers can add axes of specialization based on their needs. 

This approach does create a large number of compiled scripts, but we reasoned it was a welcome trade-off: you end up spending cheap server disk space on many optimized scripts, and, as a result, applications download and run more quickly, making end users happier.

In any event, our experience in developing GWT has thoroughly convinced us that there’s no need to give in to the typical constraints of Web development. That is, with a bit of creativity and some dedicated effort, we now know it is indeed possible to retain the richness of more familiar development environments without compromising the experience application users are ultimately to enjoy. Q

LOVE IT, HATE IT? LET US KNOW
feedback@queue.acm.org

Bruce Johnson founded Google’s engineering office in Atlanta, right next door to his alma mater Georgia Tech, with the goal of producing Google Web Toolkit and a number of related tools intended to make Web development more efficient, effective, and a whole lot more fun.

© 2009 ACM 1542-7730/09/0700 $10.00

acmqueue

Originally published in Queue vol. 7, no. 6
see this item in the ACM Digital Library


Tweet



Related:

Pete Hunt, Paul O'Shannessy, Dave Smith, Terry Coatta - React: Facebook's Functional Turn on Writing JavaScript
A discussion with Pete Hunt, Paul O'Shannessy, Dave Smith and Terry Coatta


Eric Schrock - Debugging AJAX in Production
Lacking proper browser support, what steps can we take to debug production AJAX code?


Jeff Norwalk - Case Study: Making the Move to AJAX
What a software-as-a-service provider learned from using an AJAX framework for RIA development



Comments

(newest first)

Stephen S | Tue, 13 Oct 2009 13:11:50 UTC

"In fact, that is not the way you work in GWT at all. You spend most of your time in GWTs hosted mode, which allows you to run and debug your Java code in a normal Java debugger (for example, Eclipse), just as youre accustomed to doing. Only when the application is written and debugged do you need actually to compile it into JavaScript. Thus, everyones reflexive fear of never being able to understand and debug the compiled JavaScript proves to be unfounded."

The team I worked on implementing an enterprise application in GWT (1.4 then) didn't find this to be true at all. While at first we spent most of our time in hosted mode we eventually started getting bit by subtle bugs that would only present themselves in the compiled javascript code including the performance issues that would be masked as compiled javascript running in IE6 runs very differently than the java being executed in hosted mode. Since these issues only presented themselves once the compiled javascript it meant working in the browser to figure out what was going on.

This presented a situation where you have a team of java developers with little to no experience in javascript trying to decipher the thousands of lines of generated javascript code and research debugging/profiling tools that could be run against ie6. All of this happening very late in a release.


Sakuraba | Thu, 23 Jul 2009 09:23:34 UTC

Very nice summary. Working with GWT for almost 2 years now, I can say that the only thing that holds me back is the fact that the GWT team is aiming at producing solutions that work really well at a very low level. They dont give you a lot of "high level abstractions" for free, you have to be a UI-Patterns (Model-View-Presenter, etc) expert to write a large codebase that remains maintainable and most importantly testable.

The widget set and the "default look" are a little minimal, too. They rewrote Google Adwords with GWT which looks really nice, which means you can write good looking apps with it, you just need CSS and design skills, which forced a lot of developers into third party widget libraries. Those libraries are either a wrapper around traditional javascript or implemented with GWTs DOM abstractions. Those libraries dont have the same amount of 'perfection' associated with them, so you will inherit the same problems as with traditional web applications even though you are working with GWT.

For a better adoption of GWT, I think the focus of the GWT team should shift away from trying to optimize the tiny small bits (like inlining math calculations or rewriting the JS "this" reference into a smaller variable to save some bytes) and focus on real world problems that make web development so cumbersome. That includes e.g. a complete abstraction of browser-specific layouting, additional widgets with a "Store"-like API, to declaratively describe e.g. the contents of a datasource for widgets.

A part of GWTs mission statement is that the "parts of GWT must be independently useful", which means that you should be able to add the e.g. I18N-module to your project, but if you dont want/need to, you should not "pay" for the existence of that module, it should not stand in your way. This principle is great and there is a need to add standard solutions (a GWT-application-framework, a default eventbus, the store-api described above, etc) for real world problems that every web-developer faces.

Dear GWT-team, thank you for your wonderful work, but the above is what we need next to really effectively use GWT. You should not have to become a UI-Patterns-magician first, writing horribly untestable code on the way, to create great, large GWT-applications.


Dan Billings | Wed, 22 Jul 2009 22:16:59 UTC

Very nice summary Bruce. GWT is going to be a major contender in the future, when everyone appreciates its power!


Bill Braasch | Wed, 22 Jul 2009 16:22:31 UTC

GWT is a work in process. I think it's a pretty good work in process. We use it to animate JSON data with declarative programming in the open source itemscript project (itemscript.org).

Thus began the age of compiled javascripts. Microsoft's next generation office project claims to be the 'largest javascript' ever written. They should run it through the GWT compiler, use code-splitting to optimize the packaging, read 'the story of my compile' (a new feature) and weep for the browsers that bear the burden of poorly written but well-intentioned javascript.

The road leads to a lean programming model where the interface designers drag and script widgets, bind them to data and publish an iteration, little aware of the technical components that are being optimized in their own iterations. Decoupling the iterations will increase the flow of solutions.

GWT is still young, has good ideas and a product to support (Google Wave). We like the compiler a lot. It makes the code smaller. We'd like it to make the code even smaller. Code-splitting and "story of your compile" and oophm are the wrenches for that. Turns out YMMV when writing boatloads of Javascript. Slow=death.


Rusco | Wed, 22 Jul 2009 15:31:27 UTC

If Google Engineers would invest all their brainpower in a decent cross browser XForms Engine we could get the same results as with GWT, but with only 20 % of the effort of a GWT Project.

The truth is still to be revealed !


Armchair Pundit | Wed, 22 Jul 2009 11:16:18 UTC

GWT certainly makes sense for some usage patterns for web-apps.

However, it does come along with some leaky abstractions and quirks, more details here:

http://ptrthomas.wordpress.com/2008/09/04/wicket-and-gwt-compared-with-code/


Leave this field empty

Post a Comment:







© 2017 ACM, Inc. All Rights Reserved.