Sencha Inc. | HTML5 Apps

Blog

Using the GWT Compiler for Better Builds

April 02, 2013 | Colin Alworth

GWT CompilerMany of us use Sencha GXT and GWT to help our teams produce structured, powerful, and maintainable web applications. With Dev Mode and Super Dev Mode, we can easily write Java and test how it behaves in the browser. The Compiler then produces optimized code to run in our users’ browsers.

It turns out that the compiler is customizable, and can be instructed to change its output based on your needs, to produce a better build. What is a better build though? This definition will change based on the project, and based on the team member. For many developers, the best build is the one that happens as fast as possible, letting you get straight to testing. For some teams, the best build is often the one that produces the smallest browser download, or sometimes the smallest total compiled size.

In this article, we’ll discuss several different ways to customize what the compiler is building for you, and how it can impact build times and output sizes. We’ll use the Sencha GXT Explorer as an example. While it is not as complex as many applications out there, it does use almost every single widget Sencha GXT has to offer. Using Sencha GWT 2.4.0 and GXT 3.0.4, the Explorer builds in about 183 seconds, and the largest permutation is 2,608,783 bytes.

Permutations

A ‘normal’ build is one where you get 6 basic permutations, one for each category of browser. These have been selected by the GWT team as related enough to always treat as the same. These browsers are:

  • Firefox, all versions
  • All Webkit browsers (Safari, Chrome, Android Browser, etc)
  • IE6 and IE7
  • IE8
  • IE9 (and IE10)
  • Opera

GXT is configured by default to use these same 6 permutations — it is a good middle ground between all possible configurations and lumping everything together. GXT adds several possible browsers that are not normally listed — IE6 and IE7 are kept apart, as are IE9 and IE10. Chrome and Safari are treated as two different browsers, with several distinct versions, where required. Firefox is lumped into two categories — before Gecko 1.9 and after, allowing for some tests of specific cases. GXT also adds another degree of possibility — Operating System. There are unique features to each OS that make it important to allow GXT to tell the difference.

With all of these variations in the build, it’s possible to end up with 52 permutations in 3.0.4! This makes the build times much longer than the normal 6 permutations — but do we gain anything?

To test this question, I made a few changes to the application’s module file. Instead of

 
  <inherits name="com.sencha.gxt.ui.GXT" />
 

which is the standard inherits statement to load GXT, I replaced this statement with the specific modules we were interested in. We can load just the widgets and the State handling code instead, as well as a theme:

 
  <inherits name="com.sencha.gxt.widget.core.Core" />
  <inherits name="com.sencha.gxt.state.State" />
  <inherits name="com.sencha.gxt.theme.blue.Blue" />
 

At present, Charts do not allow for this particular change, so I had to make the same change to the Charts.gwt.xml file to allow it to make this change overall.

This is a massive overkill and doesn’t save us much. The largest permutation is now down to 2,587,780 bytes — a difference of about 21K, or a savings of less than 1%, and the build took 20 minutes, up from about 3 minutes. Interestingly, it also only produced 26 actual permutations — while the OS check allows our code to differentiate between Windows/Mac/Linux/Unknown, it turns out that we rarely need to take advantage of this check, so half of our build time is essentially wasted. We’re still seeing savings though — some teams may find it worthwhile to create very specific builds for supported browsers, then limit which browsers they support when debugging and allow very long builds for production.

We can also take this in the other direction in two different ways by combining browsers into fewer permutations or by limiting which browsers we intend to support.

First, we can restrict which browsers we intend to support:

 
  <inherits name="com.sencha.gxt.ui.GXT" />
  <set-property name="gxt.user.agent" value="ie9, ie10, gecko1_9, safari5, chrome, opera" />
 

By inheriting GXT again, and limiting the browsers we are most interested in, we are limiting the build down to 4 permutations — ie8, ie9/ie10, gecko1_9, and safari5/chrome. These 6 browsers only require 4 permutations because once again, we are condensing similar browsers into the same build. By following the previous step (not inheriting GXT directly), we can expand this back to 6 if we wanted — and be slightly more specialized than we were to begin with. Or, we can keep our smaller set. With this smaller set, we can compile in about 140 seconds, and the largest of our four permutations is 2,608,785 bytes, about the same as we started.

The second option is to further condense supported browsers into fewer permutations. There are two tools for this option and both rely on the idea of merging permutations. First, we can specify that one or more property should be collapsed:

 
  <inherits name="com.sencha.gxt.widget.core.Core" />
  <inherits name="com.sencha.gxt.state.State" />
  <inherits name="com.sencha.gxt.theme.blue.Blue" />
  <set-property name="gxt.user.agent" value="ie9, ie10, gecko1_9, safari5, chrome, opera" />
 
  <!-- merge gecko1_9, safar5, and chrome into one permutation -->
  <collapse-property name="gxt.user.agent" values="gecko1_9, safari5, chrome" />
 
  <!-- merge ie9 and ie10 into one permutation with a wildcard-->
  <collapse-property name="gxt.user.agent" values="ie*" />
  <!-- merge all operating system permutations -->
  <collapse-property name="user.agent.os" values="*" />
 

This gives us a different set of 4 permutations, the largest at 2,608,785 bytes, and completes in 144 seconds — still faster than our baseline, supporting fewer browsers, and at nearly the same size.

Finally we can tell the compiler that every single property should be collapsed into one permutation:

 
  <collapse-all-properties />
 

This is the big one — no matter how many permutations you had configured before this line, only one file will be created. This is especially handy for very fast builds or cases where you want to keep the build size small (such as only one 1.5MB file instead of six 1.0MB files). In the case of the Explorer, we bring the compiled size up to 3,376,120 bytes, about 700K larger, but the compile time drops to just over a minute, from just over three minutes. The total compiled directory also drops in size, from about 22MB to less than 6MB.

If you would like more information, you can learn about properties and permutations on the GWT wiki.

Optimization levels and other flags

When the GWT compiler rewrites Java into JavaScript, it transforms the code in many ways. Some rewrites are necessary to change Java expressions into JavaScript — instanceof operators and casts are turned into method calls, and all classes and inheritance are tracked in a lookup table. It also looks for cases where code is easy to read but hard to execute, and rewrites them — fields left at their default values are turned into constants and sometimes compiled away, among many others. All modifications to the way the optimizer works are set when running the compiler, not in the module files. How you set these modifications may vary based on how you build your project — for command line and Ant users, these are arguments passed to the compiler, while for Maven users these are configuration options.

Faster builds

By default, the compiler runs and runs until it is no longer making a difference in the files it is touching. For many applications, this results in 8 to 15 steps through the optimizer before starting on browser-specific details. This too is configurable, but must always be run at least once to properly transform Java into JavaScript. If we set the optimization level to 0, we are asking for as little optimization to be done as possible, whereas setting it to 9 (the default) asks the compiler to run until it is no longer able to find anything to optimize.

There is also a set of expensive optimizations called ‘Aggressive Optimizations’. By default, these are enabled but can be turned off.

There is also a handy option called ‘draft mode’ — activating this option disables all aggressive optimizations and turns down the optimization level at the same time. This is the easiest way to limit the effort that the compiler is putting into building an app. Just turning on draft mode brings the Explorer build time down to 103 seconds, from 180, though it raises the size of the largest permutation to 3,426,639 bytes, some 800K larger. Combined with the collapse-all-permutations directive in the module, the compile time drops further to 49 seconds — less than a minute! Predictably, the compiled size also grows, now up to 4,459,653 bytes which is not a problem for local debugging, but not something you’d want to serve over the Internet to users’ browsers.

GWT Compiler:

Pass the -draftCompile flag to the compiler

GWT-Maven-Plugin:

Either set <draftCompile>true</draftCompile> in the plugin configuration, or set the gwt.draftCompile property to true

Smaller apps

There are also flags that can decrease the compiled size of the app by overly simplifying code. These flags are not a fit for every project but are worth considering. The first is to skip class metadata. This causes Class.getName() to return some generic string instead of the usual "org.package.to.my.client.Object", thus decreasing the number of strings that need to be baked into the output. Most applications don’t depend on those strings being the same every time, but you’ll need to consider your use cases.

GWT Compiler:

Pass the -XdisableClassMetadata flag to the compiler

GWT-Maven-Plugin:

Either set <disableClassMetaData>true</disableClassMetadata> in the plugin configuration, or set the gwt.disableClassMetadata property to true

This saves Explorer about 3.5%, bringing the size to 2,522,282 bytes. The compile time doesn’t change much, since we’re just removing the strings that are used to describe the packages and classes.

The second flag is somewhat more invasive, by removing all ClassCastExceptions from your compiled code. Where Java has a cast operator, letting you specify that you want to treat an object of type A as if it were type B, JavaScript has no such concept, so all type checks must be added through synthetic code. A block like

 
    if (obj instanceof HasMethod) {
    	HasMethod hasMethod = (HasMethod) obj;
    	hasMethod.doSomething();
    }
 

will by default compile to something like:

 
  if (instanceOf(obj, 5)) {
  	hasMethod = dynamicCast(obj, 5);
  	hasMethod.doSomething();
  }
 

If you enable this feature, instead, the compiled JavaScript will be something more like:

 
  if (instanceOf(obj, 5)) {
  	hasMethod = obj;
  	hasMethod.doSomething();
  }
 

This flag will also remove other cast operations elsewhere in your application. It can often be a good thing — the very fastest code is code that never runs — but you will miss out on any unexpected ClassCastExceptions in your compiled application. Instead, you’ll either see no errors at all, or you’ll see the JavaScript equivalent of a null pointer when a method or field is referenced. If you see such an error in production, you can run in Dev mode or without this flag set, and you will see the correct error.

GWT Compiler:

Pass the -XdisableCastChecking flag to the compiler

GWT-Maven-Plugin:

Either set <disableCastChecking>true</disableCastChecking> in the plugin configuration, or set the gwt.disableCastChecking property to true

By itself, removing cast checking saves less than 2%, though this can also have significant impacts on runtime performance by avoiding useless checks. As with the class metadata, dropping cast checks does not have much impact on compile times.

Enabling both of these features takes roughly the same amount of time to compile, and removing this possibly useless extra data and checks drops the compiled size by about 6%, down to 2,461,611 bytes.

CssResource classnames

Next, we have a configuration change that can be made to the way CssResources are generated by ClientBundle. By default, ClientBundle assumes that there might be more than one application running on the page and care should be taken to generated CSS classes, so they will never conflict. This set of features can be customized in several ways, either to make debugging easier or to make the application size smaller.

GXT 3 makes heavy use of CssResources and ClientBundles. They hold all of the styles and images used to render and update widgets. The appearance pattern lets the compiler include only the CSS that is actually used, making for smaller applications and more optimized styles.

If you know that no other content needs to be rendered except your application, you can remove all of that extra prefixing. This enables ClientBundle to rename all CSS classes to very short strings, usually less than three characters.

To enable this feature, add this line to your module file:

 
<set-configuration-property name="CssResource.obfuscationPrefix" value="empty" />
 

This isn’t a terribly powerful optimization, but every little bit helps. This change has no impact on compile times and brings the Explorer size down to 2,607,442 bytes, a savings of only about 1K.

Keep GWT and GXT up to date

One final thing we can do is keep the compiler up to date. While the main GWT library code isn’t changing too much beyond keeping up with browsers and fixing bugs, the compiler itself is slowly incorporating changes, fixes and improvements.

If you use UiBinder, don’t forget to remove the uibinder-bridge jar from your classpath and the inherits from your module. It is no longer necessary when using GWT 2.5.0 or later.

By itself, switching to GWT 2.5.0 brought the default build down to 2,388,364 bytes — almost 10% savings or about 200K, though it seemed to increase compile times slightly.

The Closure Compiler

The release of GWT 2.5.0 brought an additional compilation step that can be added to the process. The Closure Compiler, a JavaScript optimizing compiler, is able to rewrite JavaScript that follows certain rules to be smaller and more efficient. While many of these same optimizations are available to the GWT compiler, there are some that are not present. Allowing the Closure Compiler to run after GWT has done its work can often take a pretty big chunk out of the compiled size.

While any GWT/Java code is already compatible with the Closure Compiler, some JSNI is not. As part of the GXT 3.0.4 release, we’ve made some internal changes to ensure that this optimization will not break GXT applications.

While this produces smaller downloads, it comes at a cost — compile times can skyrocket, depending on the size of the application. This optimization must be performed on each permutation of the application, after it has been compiled, which can negatively affect multi-permutation apps.

GWT Compiler:

Pass the -XenableClosureCompiler flag to the compiler

GWT-Maven-Plugin:

Either set <enableClosureCompiler>true</enableClosureCompiler> in the plugin configuration, or set the gwt.compiler.enableClosureCompiler property to true

Enabling just the Closure Compiler saved an additional 20K over just switching to 2.5.0, bringing the compiled size to 2,388,364 bytes, though adding over four minutes to the build time. This is less than the combined savings of disabling cast checks, class metadata, and CSS classname prefixes, but on the other hand, it can stack with those optimizations. Combining all of these optimizations brings the compiled size down to just over 2MB, an overall savings of 300K, or 15%.

Combining these optimizations

Based on all of these options, what seems to be the ‘best’ set of optimizations?

Smallest individual permutations (best for production):

  • Switch to GWT 2.5.0
  • Enable Closure Compiler
  • Disable Class cast checks and Class metadata
  • Disable CSS class name prefixes

Rapid compilation:

  • Select only desired browsers (optional)
  • Collapse all permutations into one
  • Enable Draft mode

If you need very small total output (such as for compiling for embedded servers), you can mix and match — enable the compiler to do as much work as possible and leave draft mode off, but collapse all properties into one permutation.

GWT Compiler GWT Compiler

Legend

  • A — draft
  • B — collapse-all
  • C — disableClassMetaData
  • D — disableClassChecking
  • E — empty css classname prefix
  • F — Closure Compiler (GWT 2.5 and above only)

In these charts we can see that there is nearly an inverse relationship between compiled size and time — the longer we let the compiler work, the more productive it can be. At its best, the compiler can either finish the Explorer in under a minute, or just over 2MB. However, it’s important to note that build times are somewhat inconsistent — building with the same settings does not always complete in the same amount of time.

Conclusion

What we get out of a build depends, of course, on the code we put in, but also on how we ask the compiler to produce the output. By specifying what work we need or do not need to have done, we can choose that additional work be completed, or that the build be finished as soon as possible.

What do you need out of your build?

There are 4 responses. Add yours.

Andriy Andrunevchyn

2 years ago

Hi Colin,
I’m lead of JUG Lviv. We are organizing annual java conference at the end of June. Would you like to take part in conference as a speaker?
Our homepage http://www.jday.com.ua/
If you are interested in please drop me mail .(JavaScript must be enabled to view this email address)

Kazuhiro Kotsutsumi

1 year ago

I translated it into Japanese.

http://www.xenophy.com/sencha-blog/6521

Provision: Japan Sencha User Group
http://www.meetup.com/Japan-Sencha-User-Group/about/

Brandon Donnelson

1 year ago

Great job on the translation! smile

Kazuhiro Kotsutsumi

1 year ago

Hi Brandon Donnelson,

Oh, thank you for the compliment.

I continue it more than a half year….

Comments are Gravatar enabled. Your email address will not be shown.

Commenting is not available in this channel entry.