When building large applications with GWT, code splitting is a must – otherwise, the entire application (i.e. Javascript bundle) is downloaded in one chunk on the initial load of the application, which is a good recipe for frustrated users! Fortunately, the folks at Google have made the code splitting mechanism extremely simple, and have provided a really handy tool for analyzing and debugging to boot.
Even still, implementing code splitting can be a bit tricky, especially deciding where in your framework to build it in. There are some helpful constructs and patterns out there (e.g. AsyncProxy, Async Provider, etc.), but it’s still not really clear if there is a definitive best practice for code splitting in large applications using GWT MVP. So…for the purpose of (hopefully!) sparking some conversation on this topic, here’s a solution we came up with on a recent project. Please feel free to share your solutions…or any thoughts/criticism you have. Here goes…
Scenario – Application with multiple “Sectionsâ€
Imagine an application that has multiple distinct “sectionsâ€, where each section contains lots of “things†(places, dialogs, widgets, etc.), and further that a “thing†can only live in one section. For example, consider a simple app with three sections “Aâ€, “Bâ€, and “Câ€.
Again, anything that is one section cannot be programmatically referenced by something in another section. In other words, the package dependencies should look like this:
If an application can be segmented in this way, then the “section†could be a good boundary line for code splitting – in other words the application could be chunked in four pieces: main, A, B, and C. When the user first loads the application, only the main chunk is downloaded. When he navigates to any place within section A, for example, then all of section A is downloaded, and similarly for sections B and C.
Now sections may not always be good split points (perhaps they are too big, or there are inherent, un-refactorable dependencies from one section to another), but if they are, here’s how it can work…
Place Subclass
Create a subclass of the GWT Place, from which all places in the application are extended. This base class has a reference to a custom enum “Sectionâ€, which defines the different sections in the application (e.g. “Aâ€, “Bâ€, “Câ€). In psuedo-code it looks like this (though note there’s a lot missing from the MyPlace class):
public enum Section {
A, B, C;
}
public class MyPlace extends Place {
private Section section;
...
}
Again, since all places have a section, the URL for a specific place might look something like “#someplace:section=Aâ€.
Composite and Section ActivityMappers
Now each section will have it’s own ActivityMapper (e.g. AActivityMapper, BActivityMapper, etc.) which instantiates the individual Activities within its section. Splitting up into separate ActivityMappers like this ensures that there is no one master ActivityMapper that has dependencies to all Activities in all sections (thereby breaking the dependency arrows, and thus code splitting).
These section-based ActivityMappers are glued together using a new, custom “composite†ActivityMapper, which, onPlaceChange, inspects the Section of the Place, and then delegates to the appropriate custom section-based ActivityMapper.
public class CompositeActivityMapper extends ActivityMapper {
private final HashMap mappers =
new HashMap();
public Activity getActivity(Place place) {
MyPlace myPlace = (MyPlace) place;
return mappers.get(myPlace.getSection()).getActivity(myPlace);
}
public void addActivityMapper(Section section, ActivityMapper activityMapper) {
mappers.put(section, activityMapper);
}
}
Custom PlaceController
Finally, and this is the crux (!), the PlaceController is sub-classed, and loads the appropriate section’s ActivityMapper in the runAsync, thereby drawing the split-point boundary around the entire section. Essentially, the first time the user navigates to any place within a given section, the PlaceController will load that section (i.e. send down that section’s Javascript code bundle).
public MyPlaceController extends PlaceController {
private CompositeActivityMapper compositeActivityMapper = new CompositeActivityMapper();
private HashMap activityMappers =
new HashMap();
public MyPlaceController(CompositeActivityMapper compositeActivityMapper) {
this.compositeActivityMapper = compositeActivityMapper;
}
public void goTo(Place place) {
MyPlace myPlace = (MyPlace) place;
switch (myPlace.getSection()) {
case A:
GWT.runAsync(new RunAsyncCallback() {
public void onSuccess() {
if(activityMappers.get(A) == null) {
activityMappers.put(A, new AActivityMapper());
compositeActivityMapper.addActivityMapper(Section.A, activityMappers.get(A));
}
parentGoTo(myPlace);
}
public void onFailure(Throwable t) {
// handle somehow
}
}
break;
case B:
...
}
}
public void parentGoTo(AdminPlace place) {
super.goTo(place);
}
}
Conclusion and Caveats
Obviously, this is a high-level description, but hopefully you get the idea. Through the creation of a few custom infrastructure classes (e.g. Place, ActivityMapper, PlaceController, etc.), code splitting can be managed at a section level relatively seamlessly to the end developer – i.e. as long as the developer doesn’t break the dependency structure, it will work.
Finally, here are a few quick caveats:
- The compile report is an essential tool for understanding and managing dependencies for the purpose of effective code splitting. JDepend is also a useful tool for just the dependency portion.
- Obviously my post leaves some details (e.g. GIN, etc.) out, but they should be orthogonal.
Anyway, I’d love to hear other strategies for code splitting in MVP-based GWT apps – please share. Thanks!