IntroductionAs AJAX apps develop, the JavaScript part of the application tends to do more and more work. At some point, the code itself often becomes large enough that it has a significant impact on the application's startup time. To help with this issue, GWT provides Dead-for-now (DFN) code splitting. This article talks about what DFN code splitting is, how you start using it in an application, and how to improve an application that does use it. PrerequisitesYou need a copy of GWT compiled from trunk. Code splitting is not available in the released versions of GWT as of version 1.6. LimitationsCode splitting is only supported with certain linkers. Specifically, the iframe linker is supported, but the cross-site linker is not yet. The -soyc flag is currently only available on the GWTCompiler entry point, not the new Compiler entry point. How to use itTo get your code split, simply insert calls to GWT.runAsync() at the places where you want the program to be able to pause for downloading more code. These locations are called split points. A call to GWT.runAsync() is just like a call to register any other event handler. The only difference is that the event being handled is somewhat unusual. Instead of being a mouse-click event or key-press event, the event is that the necessary code has downloaded for execution to proceed. For example, here is the initial, unsplit Hello sample that comes with GWT: public class Hello implements EntryPoint {
public void onModuleLoad() {
Button b = new Button("Click me", new ClickHandler() {
public void onClick(ClickEvent event) {
Window.alert("Hello, AJAX");
}
});
RootPanel.get().add(b);
}
}Suppose you wanted to split out the Window.alert call into a separate code download. The following code accomplishes this: public class Hello implements EntryPoint {
public void onModuleLoad() {
Button b = new Button("Click me", new ClickHandler() {
public void onClick(ClickEvent event) {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable caught) {
Window.alert("Code download failed");
}
public void onSuccess() {
Window.alert("Hello, AJAX");
}
});
}
});
RootPanel.get().add(b);
}
}In the place the code used to call Window.alert, there is now a call to GWT.runAsync. The argument to GWT.runAsync is a callback object that will be invoked once the necessary code downloads. Like with event handlers for GUI events, a runAsync callback is frequently an anonymous inner class. That class must implement RunAsyncCallback, an interface declaring two methods. The first method is onFailure(), which is called if any code fails to download. The second method is onSuccess(), which is called when the code successfully arrives. In this case, the onSuccess() method includes the call to Window.alert(). With this modified version, the code initially downloaded does not include the string "Hello, AJAX" nor any code necessary to implement Window.alert. Once the button is clicked, the call to GWT.runAsync will be reached, and that code will start downloading. Assuming it downloads successfully, the onSuccess() method will be called; since the necessary code has downloaded, that call will succeed. If there is a failure to download the code, then onFailure() will be invoked. To see the difference in compilation, try compiling both versions and inspecting the output. The first version will generate cache.html files that all include the string "Hello, AJAX". Thus, when the app starts up, this string will be downloaded immediately. The second version, however, will not include this string in the cache.html files. Instead, this string will be located in cache.js files underneath the deferredjs directory. In the second version, the string is not loaded until the call to runAsync is reached. This one string is not a big deal for code size. In fact, the overhead of the runAsync run-time support could overwhelm the savings. However, you aren't limited to splitting out individual string literals. You can put arbitary code behind a runAsync split point, potentially leading to very large improvements in your application's initial download size. Code-splitting development toolsYou've now seen the basic code-splitting mechanism that GWT provides. Unfortunately, when you first try it, your program is likely not to split up exactly like you hoped. You will try to split out some major subsystem, but there will be a stray reference to that subsystem somewhere reachable without going through a split point, and so much of the subsystem will get pulled into the initial download. For that reason, effective code splitting requires iteration. You have to try one way, look at how it worked, then make modifications to get it working better. This section describes several tools that GWT provides for iterating toward better code splitting. The results of code splittingBefore going further, it is important to understand exactly what fragments the code splitter divides your code into. That way you can examine how the splitting went and work towards improving it. One very important fragment is the initial download. For the iframe linker it is emitted as a file whose name ends with cache.html. When the application starts up, the initial-download fragment is loaded. This fragment includes all the code necessary to run the application up to any split point but not past. When you start improving your code splitting, you should probably start by trying to reduce the size of the initial download fragment. Reducing this fragment causes the application to start up quickly. There are a number of other code fragments generated in addition to this initial one. For the iframe linker, they are located underneath a directory named deferredjs. Each split point in the program will have an associated code fragment. In addition, there is a "leftovers" code fragment for code that is not associated with any specific split point. The code fragment associated with a split point is of one of two kinds. Most frequently, it is an "exclusive" fragment. An exclusive fragment contains code that is only needed once that split point is activated. If the split point is an "initial" split point, then it gets an "initial" code fragment rather than an "exclusive" one. Unlike an exclusive fragment, an initial fragment does not rely on anything in the leftovers fragment. However, an initial fragment can only be loaded in its designated initial load sequence; exclusive fragments have the benefit that they can be loaded in any order. The Story of Your Compile (SOYC)Now that you know how GWT splits up code in general, you will want to know how it splits up your code in particular. There are several tools for this, and they are all included in the Story of Your Compile (SOYC). To obtain a SOYC report for your application, there are two steps necessary. First, add -soyc to the compilation options that are passed to the GWT compiler. This will cause the compiler to emit raw information about the compile to XML files in an -aux directory beside the rest of the compiled output. In that directory, you will see an XML file for each permutation and a manifest.xml file that describes the contents of all the others. The second step is to convert that raw information into viewable HTML. This is done with the SoycDashboard tool. To build the tool, type "ant tools" at the top of your GWT checkout. Then, run it by running Java with the following settings: - JVM argument -Xmx1024m (higher if you need)
- classpath build/lib/gwt-soyc-vis.jar:build/lib/gwt-dev-linux.jar
- main class com.google.gwt.soyc.SoycDashboard
- a command-line argument of "-resource build/lib/gwt-soyc-vis.jar"
- a command-line arguments of "stories0.xml.gz", "dependencies0.xml.gz", "splitPoints0.xml.gz"
- a working directory in the same location as manifest.xml
You can optionally specify a -out flag specifying where the output should go; by default the output is into the current directory. The top-level HTML page to open is SoycDashboard-index.html. Overall sizesThe first thing to look at in a SOYC report is the overall size breakdown of your application. SOYC breaks down your application size in four different ways: by Java package, by code type, by type of literals (for code associated with literals), and by type of strings (for code associated with string literals). By looking at these overall sizes, you can learn what parts of the code are worth much effort to pay attention to when splitting. For that matter, you might well see something that is larger than it should be; in that case, you might be able to work on that part and shrink the total, pre-split size of the application. Fragment breakdownSince you are working on code splitting, you will next want to look at the way the application splits up. Click on any code subset to see a size breakdown of the code in that fragment. The "total program" option describes all of the code in the program. The other options all correspond to individual code fragments. DependenciesAt some point you will try to get something moved out of the initial download fragment, but the GWT compiler will put it there anyway. Sometimes you can quickly figure out why, but other times it will not be obvious at all. The way to find out is to look through the dependencies that are reported in the SOYC report. The most common example is that you expected something to be left out of the initial download, but it was not. To find out why, browse to that item via the "initial download" code subset. Once you click on the item, you can look at a chain of dependencies leading back to the application's main entry point. This is the chain of dependencies that causes GWT to think the item must be in the initial download. Try to rearrange the code to break one of the links in that chain. A less common example is that you expected an item to be exclusive to some split point, but actually it's only included in leftover fragments. In this case, browse to the item via the "total program" code subset. You will then get a page describing where the code of that item ended up. If the item is not exclusive to any split point, then you will be shown a list of all split points. If you click on any of them, you will be shown a dependency chain for the item that does not include the split point you selected. To get the item exclusive to some split point, choose a split point, click on it, and then break a link in the dependency chain that comes up. Specifying an initial load sequenceBy default, every split point is given an exclusive fragment rather than an initial fragment. This gives your application maximum flexibility in the order the split points are reached. However, it means that the first split point reached must pay a significant delay, because it must wait for the leftovers fragment to load before its own code can load. If you know which split point in your app will come first, you can improve the app's performance by specifying an initial load sequence. Simply add a line such as the following to your module's gwt.xml file: <extend-configuration-property name="compiler.splitpoint.initial.sequence"
value="@com.yourcompany.yourprogram.SomeClass::someMethod()" />The value part of the line specifies a split point. Currently the only way to specify a split point is to include a complete JSNI reference to the method enclosing the split point in question. For some applications, you will know not only the first split point reached, but also the second and maybe even the third. You can continue extending the initial load sequence by adding more lines to the configuration property. For example, here is module code to specify an initial load sequence of three split points. <extend-configuration-property name="compiler.splitpoint.initial.sequence"
value="@com.yourcompany.yourprogram.SomeClass::someMethod()" />
<extend-configuration-property name="compiler.splitpoint.initial.sequence"
value="@com.yourcompany.yourprogram.AnotherClass::someMethod()" />
<extend-configuration-property name="compiler.splitpoint.initial.sequence"
value="@com.yourcompany.yourprogram.YetAnotherClass::someMethod()" />The down side to specifying an initial load sequence is that if the split points are reached in a different order than specified, then there will be an even bigger delay than before before that code is run. For example, if the third split point in the initial sequence is actually reached first, then the code for that split point will not load until the code for the first two split points finishes loading. Worse, if some non-initial split point is actually reached first, then all of the code for the entire initial load sequence, in addition to the leftovers fragment, must load before the requested split point's code can load. Thus, think very carefully before putting anything in the initial load sequence if the split points might be reached in a different order at run time. Common coding patternsGWT's code splitting is new, so the best idioms and patterns for using it are still in their infancy. Even so, here are a couple of coding patterns that look promising. Keep them in mind for your coding toolbox. Async ProviderFrequently you will think of some part of your code as its own coherent module of functionality, and you'd like for that functionality to get associated with a GWT exclusive fragment. That way, its code will not be downloaded until the first time it is needed, but once that download happens, the entire module will be available. A codding pattern that helps with this goal is to associate a class with the module and then make sure that all code in the module is only reachable by calling instance methods on that class. Then, you can arrange for the only instantiation of that class in the program to be within a runAsync. The overall pattern looks as follows. public class Module {
// public APIs
public doSomething() { /* ... */ }
public somethingElse() { /* ... */ }
// the module instance; instantiate it behind a runAsync
private static Module instance = null;
// A callback for using the module instance once it's loaded
public interface ModuleClient {
void onSuccess(Module instance);
vaid onUnavailable();
}
/**
* Access the module's instance. The callback
* runs asynchronously, once the necessary
* code has downloaded.
*/
public static void createAsync(final ModuleClient client) {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable err) {
client.onUnavailable();
}
public void onSuccess() {
if (instance == null) {
instance = new Module();
}
client.onSuccess(instance);
}
});
}
}Whenever you access the module from code that possibly loads before the module, go through the static Module.createAsync method. This method is then an "async provider": it provides an instance of Module, but it might take its time doing so. Usage note: for any code that definitely loads after the module, store the instance of the module somewhere for convenience. Then, access can go directly through that instance without harming the code splitting. PrefetchingThe code splitter of GWT does not have any special support for prefetching. Except for leftovers fragments, code downloads at the moment it is first requested. Even so, you can arrange your own application to explicitly prefetch code at places you choose. If you know a time in your application that there is likely to be little network activity, you might want to arrange to prefetch code. That way, once the code is needed for real, it will be available. The way to force prefetching is simply to call a runAsync in a way that its callback doesn't actually do anything. When the application later calls that runAsync for real, its code will be available. The precise way to invoke a runAsync to have it do nothing will depend on the specific case. That said, a common general technique is to take extend the meaning of any method parameter that is already in scope around the call to runAsync. If that argument is null, then the runAsync callback exits early, doing nothing. For example, suppose you are implementing an online address book. You might have a split point just before showing information about that contact. A prefetchable way to wrap that code would be as follows: public void showContact(final String contactId) {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable caught) {
cb.onFailure(caught);
}
public void onSuccess() {
if (contactId == null) {
// do nothing: just a prefetch
return;
}
// Show contact contactId...
}
});
}Here, if showContact() is called with an actual contact ID, then the callback displays the information about that contact. If, however, it is called with null, then the same code will be downloaded, but the callback won't actually do anything.
|
This looks amazing.
A suggestion for the common coding patterns: I've found that I needed three cases more often than not and so extended the ModuleClient?? interface as follows:
public interface ModuleClient { void onLoad(Module instance); void onAlreadyLoaded(Module instance); void onUnavailable(); }The addition is the onAlreadyLoaded() method which can be used if the code is already loaded. This is useful if you want to do additional work on the first load and only a subset or different work if already loaded.
A very simple example is where the RunAsync? code might return a widget. The onLoad() could add that widget to the DOM and then configure some items on it, the onAlreadyLoaded() method may not want to add the widget to the DOM and only show it.
I know this must sound dumb: If I have a pre-compiled core module and want to compile and add an extension module on the fly (WITHOUT the core module's source code being available) will this feature make it possible?
I'm afraid not. runAsync still relies on a monolithic compile.
Above: "The -soyc flag is currently only available on the GWTCompiler entry point, not the new Compiler entry point."
On the current GWT 2.0 HEAD off of SVN, -soyc are supported for the Compiler. Also, -aux has been removed, being replaced by -extra.
Hello.
This is certainly a nice feature, but one not requiring a monolithic compile would be that just great.
Our application is built on top of OSGi. When a new module is added a new menu item appears in the GUI to access that module GUI (under different URL).
Currently we must compile each OSGi module with the common "Workspace" library project and so the common Workspace part is duplicated over the modules and so needs to be loaded together with each module code. This is because of the monolithic build.
Much nicer would be a separate Workspace module that just lazy-loads its (OSGi based) dynamic view modules on demand (menu item clicks). This way there would be no page reload (= the same URL), because the Workspace (header, menu, footer, common code) doesn't need to be reloaded. Is there any plan to make possible to do it like this? I think I ask for the same as mirceade does.
Thanks in advance.
@m.zdila: Right now, so many of GWT's biggest speed benefits are a direct result of monolithic compilation of modules, so adding features to encourage runtime modularity is not something we're really considering anytime soon. While we understand the desire to not include shared code redundantly, the optimal solution would have only one module at runtime in the first place. Is there no way you can compile together the necessary OSGi modules together ahead of time, so that they can benefit from compiler optimizations?
@br: We can compile together the client code for all the current modules, but it would not be very OSGi conforming. For example, imagine that some client bought our product including only some of the modules. It would not be very clean to include client code for all the existing modules as one monolithic build. Even if we gave him only the client code for the bundles he bought, then if he would buy additional bundle(s) and dynamically install it to the running system, there would be no chance to add the additional client side part to the existing monolithic build. Simply, each server-side user bundle should have its client module counterpart so they can be dynamically added/removed without recompiling (all) the client.
Currently, as described in my first post, the shared workspace code gets duplicated. And exactly this is what makes traversing between modules slow - the JS code must be dowloaded every time (if not cached), parsed, and the workspace initialization code must be run. This way the monolithic build give us the opposite of the desired speed improvement.
I admin that I don't know much about the optimization internals. Despite of that I dare to imagine that (in a near future) it would be possible the common workspace module to be separated from the view modules. The optimization can stay within the single module (build). The interface between workspace and view is governed by the Java interfaces (or maybe even the classes) and so the compiler can take it to the accound when optimizing. I personally would rather have this functionality even if the code would not be as much optimal as possible ;-).
In any case, currently we can live just OK with our current solution. Good luck!
The problem is, if you support separate compilation, the compiler can no longer make assumptions about which methods in your code base are 'live' (are going to be called) and which are 'dead' (are never called), it would be forced to include them on the chance that they might be called in the future by some third party module. This would massively bloat the size of the JS output as well as hamper other optimizations like inlining which depend on knowing things like that if an interface I is implemented by classes A and B, and B is never used, than I == A. This comes up frequently with say, List/ArrayList?/LinkedList? where LinkedList? is rarely if ever used.
In my opinion, what you really want is some kind of message bus or RPC mechanism, so that differently compiled packages can communicate over some interface. In this case, the amount of shared code that must be replicated can be kept to a minimum.
cromwellian: I don't understand how could the RPC solve my problem.
Maybe what I would like to have is currently very complicated in the GWT. I am accepting it and can live with that.
Long time ago I was using DOJO and I was dynamically loading the views to the workspace. I know it has nothing to do with the GWT, I only want to tell you that some other frameworks can do it. (Un)fortunately I wouldn't use any JS-only framework even if it had such an feature :-).
Anyway it would be great if GWT had some means to do the true dynamic code loading. It doesn't have to be necessairly transparent for the developer. I wouldn't mind to somehow explicitly mark some code "interface points" or something...
We are also searching for solution to dynamically extend the GUI produced via GWT. Regarding Cromwellian's comment about the compiler optimizations and the static analysis of what code parts are "live" or "dead", I would say that what we want is something like JARs for GWT. Have the base GWT application compiled as one or more JAR-like packages, develop you extensions/plugins against the main application's API and package them as JAR-like files. At runtime of course you as the application owner must make sure that all required JARs/bundles are present. But hey, that's exactly what people know from JAVA apps. If you miss a JAR file you will get a ClassNotFoundException?.
Just a message to the Google GWT team that while I appreciate the theoretical utility of pruning unused methods, especially as applicable to the GWT core UI classes themselves, the applicability to end-user code is likely much less.
I routinely run dead code analysis against my source and remove unused methods. So, while it's nice that the compiler would do that for me, it is certainly not a necessity. If a developer's code is too big because of dead code, it should be on his/her shoulders to fix it themselves.
If somehow we (end-users) could control which modules were pruned of un-invoked methods it may serve to solve part of the problem. A user such as m.zdila who would like their user's "plugins" to have full access to the UI component APIs could turn off (all?) pruning. Sure, maybe the result would be a 2meg JavaScript? file, but that's his choice.
I'm not a GWT team member, but I'll comment. It's not just pruning unused methods. It also affects obfuscation and a number of other things that make 'separate compilation/linkage' a difficult prospect.
In order for N different separately compiled programs to be linked at runtime, they'd all have to agree upfront on what the names of every publicly available class, method, and field were in the output ahead of time. This would have the effect of lengthening the names of most of the identifiers, further ballooning code.
Then there's inlining and type-tightening. Currently, the compiler is able to detect when a method is not polymorphic (no overrides), and turn it into a static method. Moreover, in many cases, it can inline this method. Compile time linkage would prevent this, and the compiler would be forced to conclude that any non-final method can potentially be overriden, disabling inlining and prototype-chain reduction effects.
There are lots of other problems to. You're essentially asking GWT to run in "Java to Javascript translator mode" rather than "Java to Javascript compilation" mode, akin to the way hand coded Javascript apps are architected.
@brett: What Ray said. Also, we don't want to create the web equivalent of DLL Hell, which is a very easy situation to get into. Finally, and most importantly, runtime modularity simply has a really high performance cost because HTTP round-trips over the internet are inevitably slow and so we strive to encourage architectures that avoid them. All that said, I think there are a few things we'll do before long (e.g. making it easy to expose JS-callable APIs from GWT modules, like Ray's gwt-exporter) could go a long way toward providing some amount of dynamic pluggability without sacrficing too much in the way of performance.
I have used Ray's GWT Exporter and I believe that you could use this to achieve what you want with plugability while also allowing the compiler to do it's optimizations. One thing I haven't experimented with that Ray would have to answer is if you could add the exporter annotations to the interface or abstract class so that you would not have to re-write them all for each of the modules. I am currently looking at doing a similar thing, but am still researching/designing how I am going to implement it.
Not currently. GWT Exporter was created under the paradigm that it only exports what is explicitly requested, or what is absolutely necessary. I felt at the time that 'inheriting' annotations could lead to a lot of bloat, as well as asking the TypeOracle? to give you all subtypes can cause performance issues in Hosted Mode.
I planned on adding module parameter 'exportAll', where you don't even have to invoke GWT.create() on each class to export, but rather, there would be a module entry point which triggers a special generator that finds every single class in the classpath with @Export annotations and exports them automatically. There are some people who would like this as an ease of development option.
Hi, I have a question regarding Code Splitting + DI with Guice/Gin. I have a module that looks like this:
public class Module {
}I am trying to split the code for this class, so I inserted a call to GWT.runAsync directly in the constructor. But the code for Provider gets pulled in. Any idea how can I combine DI with Guice/Gin and still be able to split the code like I want?
Thanks in advance.
Evely
One hack I was using to get modules to "speak through compilations" was using Ray's exporter. Basically, common functions like custom dialogs that cover the screen... If I want them to look nice, but be accessible before I ever access Widget code, I use public static class methods like note(String txt), ask(String question, AsyncCallback? response, String ... answers). These methods aren't even in the same package as the code that implements them; they just tunnel through a "JS API" $wnd.parent.MyExportedAPI. The root API module puts a JS object MyExportedAPI on it's $wnd, then it loads the "dll" nocache.js files, which implement things like note() and ask(), adding them to MyExportedAPI.Dialog.ask()
Finally, each osgi iframe has .nocache.js modules that never access the packages which implement the UI stuff, only static methods that tunnel to $wnd.parent.ME_API {or $wnd.parent.parent...ME_API}. Biggest issue was context. All callbacks from an osgi child of the root must send along it's window variables {NOT $wnd}, and map all the parameters in and out of JSNI.
Think: public static native void getFeeling(AsyncCallback?<String> callback) /-{ }); }-/;
then, to actually perform the callback, in JSNI, x.callback.call(x.wind, 'Great')
PROBLEMS {only some of them}: Memory leaks; adding gwt workspace iframe.contentWindow to every tunneled call could get REAL messy. Hand coded; it works, but there's piles and piles of boilerplate code; external public static access, a to JSNI bridge method, a from JSNI bridge, and finally, an implementation method. Don't forget @Export boilerplate too! Tracing errors becomes nearly impossible. Had to make a special, ForeignException? so different frames could throw an error and call an asyncCallback.onFailure without ClassCastException?. Accessing osgi subframes {/ls, /dl} without being a child of the root frame = not possible. Power users like to skip straight to the url w/ content, sans fancy multiple-module downloads. "Dlls" came in .cache.html, so they didn't block downloads of other code, which I thought was an advantage, but with heavy caching situations, weird page reload bugs happened when the root api changed, but the children did not. Proxied calls were slllllow Event.addNativeEventListener does not work through iframes Compile times through the roof!! A giant wad of atrocities I've tried to block out.
Trust me, the amount of extra dancing and working you will have to do for "dll osgi-like modules" will cost you more time than money can replace. And what Ray mentioned before about RPC is that your obfuscated Java objects CANNOT sanely be made translatable unless you RPC encode/decode when tunneling to the root MyExportedAPI. This would mean an encode/decode pair of overhead methods for every paramete or returned object that's not a primitive or JSO.
I did all this before runAsync and GWT2.0 was compatible with Appengine, and I TOTALLY ABANDONDED THIS METHOD. It hurt too much to implement. Maybe if I was a generator wizard like Ray it wouldn't be so bad, but at least my package segregation methods made the "root API" modules transferable to runAsync without worry of external dependancy.
...If you feel like you wasted time reading these run on sentences of mine, do yourself a favor, AND SPARE YOUR CODING TIME, COMPILING TIME AND EXECUTION SPEED: Monolithic Compile Is The Only efficient Way To Obfuscate, Split And Link Independent Modules. Any other way means a translation layer and jumping through lots of unnecessary hoops!
@Evelyne24
If you haven't come across this yet, it may be of help using GIN and CodeSplit?.
http://code.google.com/p/google-gin/issues/detail?id=61