Using Code Splitting in GWT

Using Code Splitting in GWT

Users now judge the speed of your application in hundreds of milliseconds and not seconds. If your application takes a full second to start up, it may be considered as being sluggish. So how can you decrease the start time? This article from the book GWT in Action, Second Edition discusses a coding pattern that has proven useful for segmenting your application to enable a smaller initial download and loading less-often accessed code.

This article is based on GWT in Action, Second Edition, to be published in Fall 2012. This eBook is available through the Manning Early Access Program (MEAP). Download the eBook instantly from manning.com. All print book purchases include free digital formats (PDF, ePub and Kindle). Visit the book’s page for more information based on GWT in Action, Second Edition. This content is being reproduced here by permission from Manning Publications.

Authors: Adam Tacy, Robert Hanson, Jason Essington, Ian Bambury, and Christopher Ramsdale

GWT in Action, Second Edition Back in 2006 when GWT was released, it solved a lot of the traditional problems for developing large JavaScript applications. So great was the promise that developers began to develop massive GWT applications—so massive, in fact, that they started hitting a wall. The wall they hit had to do with the size of the application. If you think about your traditional desktop application like Microsoft Word, there are so many features of that application that you don’t normally use. For instance, the Mail Merge tool within Microsoft Word is extremely useful when you need that functionality, but most users rarely use it if at all. So the first question is how do we load only the functionality that will be used?

Another common engineering issue was how to decrease the load time of the application. It is common for a feature full GWT application to approach a megabyte in size. With broadband, this is generally a fast download, but at the same time users have higher expectations that they did with their 56K modem. Users now judge the speed of your application in hundreds of milliseconds and not seconds. If your application takes a full second to start up, it may be considered as being sluggish. So how can you decrease the start time?

This is where code splitting comes in. If you can cut your code into multiple segments, you can kill two birds with one stone. A smaller initial download means faster startup, and loading less-often accessed code only when it loads means smaller total downloads.

We begin our discussion by explaining the basics of using code splitting, then delve deeper and explore a coding pattern that has proven useful for segmenting your application.

Understanding code splitting basics

Code splitting in theory is really very simple to understand. You wrap a piece of code in an asynchronous block, much like you would do with an RPC call, and let the compiler handle the rest. Let’s start with an example, listing 1, so that we can see what we are talking about.

Listing 1 An extremely simple example of code splitting
RunAsyncCallback callback = new RunAsyncCallback() {                   |#1
public void onSuccess () {                                           |#2
doStuff();
}
public void onFailure (Throwable reason) {                           |#3
Window.alert("Error: " + reason.getMessage());
}
};
GWT.runAsync(callback);                                                |#4
#1 Creates callback
#2 Defines success handler
#3 Defines error handler
#4 Loads and executes

This example literally covers the entire code splitting API. Even though it is extremely simple, let’s walk through the example and provide some commentary. As shown in the example, we first create a RunAsyncCallback instance (#1). RunAsyncCallback is an interface that is very similar to the asynchronous interfaces used by RPC, like AsyncCallback and RequestCallback, in that it has methods for handling success and errors.

The success handler onSuccess() (#2) is called when the JavaScript fragment, called a split point, is successfully loaded from the server and is ready to be run. This is also where the compiler determines where to split your code. Of course it, isn’t quite that cut and dry due to dependencies, which we will discuss shortly.

The error handler onFailure() [#3] is called when the split point fails to load. The typical cause of this would be because the network is not available or the site is not reachable. This could occur frequently for mobile users, where they might start using your application from a Wi-Fi hot spot, then trigger a split point after leaving the hot spot. Here, we simply show an alert pop-up, but it is probably a good idea to think about how your application should handle these errors.

Once the callback is defined, all we need to do is call GWT.runAsync() (#4) to trigger the loading and execution of the split point. In addition to loading the split point, there may also be some leftover code that also needs to be loaded. During compiling, there may be some code that doesn’t fit in a single split point and instead is shared across more than one. This is called leftover code and will be loaded prior to any split point code.

To visualize this, add a few split points to a GWT application then compile the code and generate a Compile Report. You can turn on generation of the Compile Report add the compiler switch –compileReport.

An example of what you might see on the Compile Report if you had two split points is shown in figure 1.

Example of a Compile Report
Example of a Compile Report

Figure 1 Here is an example of a Compile Report showing two split points. It shows the size of the initial download, the two split points, and leftover code that is shared between the split points.

The initial download size for this permutation is only about 24 KB, which, compared to the total code size of 292 KB, is a significant improvement. In addition, you will see that there is 62 KB worth of leftover code. The leftover code is code that is shared by more than one split point and isn’t needed in the initial download. And, finally, there are two split points, one of 141 KB and another of 64 KB. So there are four total JavaScript files, each containing some portion of the total application.

If you just drop this into one of your own applications, it is likely you won’t see results quite as good as these. These results are from a simple test application where the initial download has relatively no functionality, and the code within the split points have no dependencies on each other. In fact, what you are more likely to see is something in figure 2.

Example of a Compile Report

Figure 2 Another example of a Compile Report, but this time interclass dependencies have caused the full application to be included in the initial download.

Unless you planned ahead you will likely see something closer to figure 2 where essentially the entire application is in the initial download. But why does this happen? You may have guessed that the culprit is interclass dependencies. Most developers don’t think about interclass and interpackage dependencies so much. Usually the focus is on interlibrary dependencies (aka jars).

Tracking down dependencies can be done using the Compile Report, but there is also a pattern we can rely on, the Async Package pattern.

Using the Async Package pattern

The intent of the Async Package pattern is to place collaborating classes within a Java package, then restrict access to classes within that package so that they can only be used asynchronously as a split point.

The motivation is that we want to create a split point that contains a particular segment of our code base, and we want to do so in a way that makes it difficult or impossible to create code dependencies that will cause the segmented code to be required by other segments. This is useful for development teams with multiple programmers, where you want to ensure that one developer can’t undo the work done by another. This is also useful for a single developer who wants to keep the application easy to maintain, so that enhancements added months from now don’t cause your split points to become dependent on each other.

If we were to boil this pattern to a recipe, it would consist of these bullet points.
* Isolate collaborating classes within a package.
* Remove all static methods.
* Create a single gateway class with a private constructor.
* Instantiate the gateway only within a GWT.runAsync() call.

The general idea is that all access to that package must first require the creation of the gateway class using GWT.runAsync(). By doing this, you ensure that your split point can’t be broken from users calling into the package. The first step in implementing this pattern is to isolate classes within a package from the outside world.

Isolating collaborating classes

Of course, we do this all the time in Java. We make use of keywords like protected and private to hide methods and fields from unrestricted use. By providing access where it is really needed, our code becomes inherently easier to maintain. A private method can always be renamed with the assurance that it won’t affect anything outside of the class. And the type of a protected field can be altered knowing that it only affects classes in the same package and subclasses.

The Async Package pattern makes use of Java’s access tools by restricting access to the set of classes that will become our split point. Locking down the classes in your package should be fairly straightforward for Java developers, so we won’t say much about that other than that you should make use of protected and default access to prevent outside access.

Ideally, you would plan out your split points ahead of time, but as with many things it isn’t always predictable ahead of time where it makes sense to split your code. So, if you have an application, you may wish to make use of a tool like JDepend , which will analyze your application and report on dependencies between packages. If you are using Eclipse for your development, we recommend using JDepend4Eclipse , which is shown in figure 3.

Depend4Eclipse analyzes your Java code

Figure 3 JDepend4Eclipse will analyze your Java code and allows you to review dependencies between packages and classes.

JDepend4Eclipse makes it very easy to see dependencies, both incoming and outgoing, for both packages and classes. If you aren’t familiar with JDepend, it will be worth the effort to read the overview on the JDepend website. The overview provides definitions for the terms used in the report; for example, afferent vs. efferent coupling and dependency cycles.
Now, once you have your grouped your collaborating classes in a package, it is time to create the gateway class.

Creating the gateway

Creating the gateway class involves creating a private constructor, creating an asynchronous factory method, and implementing any methods needed by the world outside of our package.

We present our example gateway class for a package that encapsulated functionality for driving a car in listing 2.

Listing 2 An example gateway class for the Async Package pattern
package com.manning.gwtia.ch18.client.car;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
public class CarGateway {
private CarGateway () { }
public static void createAsync (final Callback callback) {           |#1
GWT.runAsync(new RunAsyncCallback() {
public void onSuccess () {                                       |#2
callback.onCreated(new CarGateway());
}
public void onFailure (Throwable reason) {                       |#3
callback.onCreateFailed(reason);
}
});
}
public interface Callback {                                          |#4
void onCreated (CarGateway gateway);
void onCreateFailed (Throwable reason);
}
public void startEngine () { ... }
public void setSpeed (int mph) { ... }
public int getSpeed () { ... }
}
#1 Factory method
#2 Success handler
#3 Failure handler
#4 Callback interface

In listing 2, we have made the constructor private, forcing callers to construct the class using the factory method createAsync() (#1). The createAsync() method takes a single callback argument, which is an interface that we will define shortly. In createAsync(), we use GWT.runAsync(), which will create our split point.

In the onSuccess() method (#2), we make use of the callback argument, calling callback.onCreated() and passing back a new instance of CarGateway.

The callback.onFailure() method (#3) simply passes the exception back to the user. As stated before, this would typically be triggered if there was a network error where the split point can’t be loaded.

The Callback interface (#4), implemented by the parameter passed to our createAsync() method, is defined here as an inner class. We then provide some work methods that can be called, like startEngine() and getSpeed(), once the CarGateway instance has been constructed.

Creating a gateway class in this manner, where we force callers to use a factory method, means that the caller can’t get it wrong. We will look at some examples of that next.

Using the gateway class

From the client side, use of the gateway class is unrestricted now that it can only be created via the factory method createAsync(). Instead of looking at the simplest use case, let’s look at something you might find in the real world.

Imagine that your application is pretty heavy and you want to present a splash screen as early as possible. This is akin to when you open a web application and it says “Loading”, like you might see on GMail.

The reason developers do this is so that user perceives the application as loading quickly. Your typical will perceive the speed of starting the application based on how long it takes you to render those first widgets, so it is useful to display some sort of splash screen while the full application code loads.

For our example, in listing 3, we will display a simple message “Loading car data…” and then replace that message with a button to start the car once the split point is loaded.

Listing 3 Example client useage of an Async Package gateway
package com.manning.gwtia.ch18.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.*;
import com.manning.gwtia.ch18.client.car.CarGateway;
import com.manning.gwtia.ch18.client.car.CarGateway.Callback;
public class Main implements EntryPoint
{
private CarGateway carGateway;
public void onModuleLoad () {
RootPanel.get().add(new Label("Loading car data..."));           |#1
CarGateway.createAsync(new Callback() {                          |#2
public void onCreated (CarGateway gateway) {
carGateway = gateway;                                        |#3
initDisplay();                                               |#3
}
public void onCreateFailed (Throwable reason) {
Window.alert("Error loading data");
}
});
}
private void initDisplay () {                                      |#4
RootPanel.get().clear();
Button button = new Button("Start your engines!");
RootPanel.get().add(button);
button.addClickHandler(new ClickHandler() {
public void onClick (ClickEvent event) {
carGateway.startEngine();
}
});
}
}
#1 Loading message
#2 Loads split point
#3 Handle loaded
#4 Initializes display

This example is our module entry point, and the first thing we do is add a loading message to the page in the form of a Label (#1). This is followed up by a call to CarGateway.createAsync() (#2), which will load the split point.

In the onCreated() method of the callback’s pass to createAsync()(#3), we can initialize the application. We do this by storing a reference to the CarGateway in the class field carGateway and then calling initDisplay().

The initDisplay() (#4) method then uses the carGateway reference to create a “Start your engines” Button and ClickHandler, which are then displayed on the page.

This example is pretty simple, but there are some things worth noting. First is that we can store a reference to the gateway class. When GWT creates a split point for the gateway it will recognize that the reference can only be null until the object is constructed. And because it is only constructed within a GWT.runAsync() call, all of the CarGateway methods can be included in the split point. To be specific, what we are trying to point out is that you can store references to the CarGateway and pass them around without breaking the split point.

Another thing to point out in this example is that, because the loading message is just a Label, it could have probably been hard-coded in the HTML page. Our example is very small, but in a larger application you might expect to find a progress bar instead of a simple text message. A progress bar is useful to the user because they can see that something is actually happening, preventing them from thinking that the application has hung.

If we had several split points, we could initiate the loading of them all at the same time and increment the progress bar as each one is completed. Then, we could use some sort of an event handler in the progress bar to initialize the application once all of the split points were loaded.

One ideal place to add a second split point in this specific application would be where we create the widgets in the initDisplay() method. If we could move this code into a gateway as well it would decrease out initial download size even further, and generally speaking the UI code is a fairly large part of more GWT applications.

When the first split point is loaded, so is the leftover code. Depending on how you have split your code, you may have a significant chunk of leftover code. To reduce the size of the leftover code GWT has one more trick up its sleeve.

Reducing leftover code by specifying load order

The leftover download shown in the Compile Report is any code that is shared across split points. If you have a lot of split points you may find that this chunk of code can be rather large. For example, if you have four split points, with 5 KB of code shared between split points A and B, and a different 5 KB of shared code used by split points C and D. If you were to load split point A, you would also be downloading the shared code for C and D, even though you might never need it.

GWT provides a mechanism to solve this problem, but, in order to do so, you need to be able to predict the load order of at least some of the split points. To do this you need to first give your split points an identity, and then reference the identity of the split point in the module configuration (listing 4).

Listing 4 Updated gateway class providing an identity to the split point
package com.manning.gwtia.ch18.client.car;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
public class CarGateway
{
private CarGateway () { }
public static void createAsync (final Callback callback) {
GWT.runAsync(CarGateway.class, new RunAsyncCallback() {            |#A
public void onSuccess () {
CarGateway gateway = new CarGateway();
callback.onCreated(gateway);
}
public void onFailure (Throwable reason) {
callback.onCreateFailed(reason);
}
});
}
...
}
#A Identity provided

In listing 4, we omitted most of our sample so that we can focus on the GWT.runAsync() call. Here, you will see that we pass a class literal as the first argument to GWT.runAsync(). This purpose of this is only to provide a unique identity, meaning that we could have used any class literal for this purpose, but typically you would use the class literal that is related to the split point. In our case, it makes the most sense to use CarGateway.class as the identity.

The second part is to identify the order of the initial split points in the module configuration. This is done by extending the configuration property “compiler.splitpoint.initial.sequence”, as in this example:
<extend-configuration-property
name="compiler.splitpoint.initial.sequence"
value="com.manning.gwtia.ch18.client.car.CarGateway"/>

The value is where we specify the identity that we gave our split point. By extending this property we are signaling to the compiler that this split point will be the first to load following the initial download. Because of this the compiler can include shared code that that split point uses in this split point, increasing the size of the split point and reducing the size of the leftover code.

If you can provide the order of additional split points beyond the first, you can specify them by adding additional entries in the module configuration.
<extend-configuration-property
name="compiler.splitpoint.initial.sequence"
value="com.manning.gwtia.ch18.client.car.UIGateway"/>
<extend-configuration-property
name="compiler.splitpoint.initial.sequence"
value="com.manning.gwtia.ch18.client.car.RoadwayGateway"/>

These will again reduce the size of the leftover code, increasing the code in the split points.

The downside of specifying the load order is that if your code ends up loading a split point out of order it will need to go back and load additional split points. For example, if you declare the load order to be A B C, but, try to load C first, it will also trigger the loading of split points A and B.

So, ordering the split points can reduce your total leftover code, but requires some preplanning in order to use it effectively.

Summary

We looked at the Compile Report and code splitting. This is a fairly new tool to the GWT stack and is also one of the most anticipated. With code splitting, there is now, in theory, no upper limit on the size of your applications. With code splitting, you can break out the rarely used parts only to be loaded when they are needed. Just imagine a million-line application running in the browser, with an initial load time of less than one second. That’s amazing considering that the underlying technology, namely JavaScript, was never meant to do that.

What makes GWT such a compelling product in our minds is that it allows us to do all these things without needing to think about the underlying JavaScript all that much. That is a good thing because JavaScript implementations differ and, as a developer, you can either spend your time learning about JavaScript implementation differences, or you can spend your time coding your application. We prefer the later.

2 Comments

  1. Pingback: Software Development Linkopedia June 2012

  2. Pingback: JavaPins

Comments are closed.