Recently, I came across this mccarroll.net’s post, which shows how to use the GWT transpiler in a standalone mode. It is quite interesting to look at the output of the given simple example and to compare it with JSweet output.
Here is the given program (just counting words passed in an array of strings):
package net.mccarroll; public class WordCount { public static int count(String args[]) { int total = 0; for(String arg:args) { total += arg.split(" ").length; } return total; } }
The GWT output is (yes I know it is for GWT version 1.6 and that version 3 will be quite different, but this is to make a point on the initial GWT approach and why it was pretty bad):
var $gwt_version = "1.6.4";var $wnd = window;var $doc = $wnd.document;var $moduleName, $moduleBase;var $stats = $wnd.__gwtStatsEvent ? function(a) {$wnd.__gwtStatsEvent(a)} : null;var $intern_1 = '', $intern_2 = ' ', $intern_8 = 'String;', $intern_7 = '[Ljava.lang.', $intern_6 = 'client.WordCountEntryPoint', $intern_0 = 'g', $intern_4 = 'moduleStartup', $intern_5 = 'onModuleLoadStart', $intern_3 = 'startup'; var _; function java_lang_Object(){ } _ = java_lang_Object.prototype = {}; _.java_lang_Object_typeId$ = 1; function client_WordCountEntryPoint_count___3Ljava_lang_String_2(args){ return net_mccarroll_WordCount_count___3Ljava_lang_String_2(args); } function com_google_gwt_lang_Array_createFromSeed__II(seedType, length){ var array = new Array(length); if (seedType > 0) { var value = [null, 0, false, [0, 0]][seedType]; for (var i = 0; i < length; ++i) { array[i] = value; } } return array; } function com_google_gwt_lang_Array_initDim__Ljava_lang_Class_2IIII(arrayClass, typeId, queryId, length, seedType){ var result; result = com_google_gwt_lang_Array_createFromSeed__II(seedType, length); com_google_gwt_lang_Array$ExpandoWrapper_$clinit__(); com_google_gwt_lang_Array$ExpandoWrapper_wrapArray__Lcom_google_gwt_lang_Array_2Ljava_lang_Object_2Ljava_lang_Object_2(result, com_google_gwt_lang_Array$ExpandoWrapper_expandoNames, com_google_gwt_lang_Array$ExpandoWrapper_expandoValues); result.java_lang_Object_typeId$ = typeId; return result; } function com_google_gwt_lang_Array(){ } _ = com_google_gwt_lang_Array.prototype = new java_lang_Object(); _.java_lang_Object_typeId$ = 0; _.length = 0; function com_google_gwt_lang_Array$ExpandoWrapper_$clinit__(){ com_google_gwt_lang_Array$ExpandoWrapper_$clinit__ = nullMethod; com_google_gwt_lang_Array$ExpandoWrapper_expandoNames = []; com_google_gwt_lang_Array$ExpandoWrapper_expandoValues = []; com_google_gwt_lang_Array$ExpandoWrapper_initExpandos__Lcom_google_gwt_lang_Array_2Ljava_lang_Object_2Ljava_lang_Object_2(new com_google_gwt_lang_Array(), com_google_gwt_lang_Array$ExpandoWrapper_expandoNames, com_google_gwt_lang_Array$ExpandoWrapper_expandoValues); } function com_google_gwt_lang_Array$ExpandoWrapper_initExpandos__Lcom_google_gwt_lang_Array_2Ljava_lang_Object_2Ljava_lang_Object_2(protoType, expandoNames, expandoValues){ var i = 0, value; for (var name in protoType) { if (value = protoType[name]) { expandoNames[i] = name; expandoValues[i] = value; ++i; } } } function com_google_gwt_lang_Array$ExpandoWrapper_wrapArray__Lcom_google_gwt_lang_Array_2Ljava_lang_Object_2Ljava_lang_Object_2(array, expandoNames, expandoValues){ com_google_gwt_lang_Array$ExpandoWrapper_$clinit__(); for (var i = 0, c = expandoNames.length; i < c; ++i) { array[expandoNames[i]] = expandoValues[i]; } } var com_google_gwt_lang_Array$ExpandoWrapper_expandoNames, com_google_gwt_lang_Array$ExpandoWrapper_expandoValues; function java_lang_Class_createForArray__Ljava_lang_String_2Ljava_lang_String_2(packageName, className){ var clazz; clazz = new java_lang_Class(); return clazz; } function java_lang_Class(){ } _ = java_lang_Class.prototype = new java_lang_Object(); _.java_lang_Object_typeId$ = 0; function java_lang_String_$split__Ljava_lang_String_2Ljava_lang_String_2I(this$static, regex, maxMatch){ var compiled = new RegExp(regex, $intern_0); var out = []; var count = 0; var trail = this$static; var lastTrail = null; while (true) { var matchObj = compiled.exec(trail); if (matchObj == null || (trail == $intern_1 || count == maxMatch - 1 && maxMatch > 0)) { out[count] = trail; break; } else { out[count] = trail.substring(0, matchObj.index); trail = trail.substring(matchObj.index + matchObj[0].length, trail.length); compiled.lastIndex = 0; if (lastTrail == trail) { out[count] = trail.substring(0, 1); trail = trail.substring(1); } lastTrail = trail; count++; } } if (maxMatch == 0) { var lastNonEmpty = out.length; while (lastNonEmpty > 0 && out[lastNonEmpty - 1] == $intern_1) { --lastNonEmpty; } if (lastNonEmpty < out.length) { out.splice(lastNonEmpty, out.length - lastNonEmpty); } } var jr = com_google_gwt_lang_Array_initDim__Ljava_lang_Class_2IIII(com_google_gwt_lang_ClassLiteralHolder__13Ljava_1lang_1String_12_1classLit, 0, 1, out.length, 0); for (var i = 0; i < out.length; ++i) { jr[i] = out[i]; } return jr; } _ = String.prototype; _.java_lang_Object_typeId$ = 2; function net_mccarroll_WordCount_count___3Ljava_lang_String_2(args){ var arg, arg$array, arg$index, arg$max, total; total = 0; for (arg$array = args , arg$index = 0 , arg$max = arg$array.length; arg$index < arg$max; ++arg$index) { arg = arg$array[arg$index]; total += java_lang_String_$split__Ljava_lang_String_2Ljava_lang_String_2I(arg, $intern_2, 0).length; } return total; } function init(){ !!$stats && $stats({moduleName:$moduleName, subSystem:$intern_3, evtGroup:$intern_4, millis:(new Date()).getTime(), type:$intern_5, className:$intern_6}); wordcount = client_WordCountEntryPoint_count___3Ljava_lang_String_2; } function gwtOnLoad(errFn, modName, modBase){ $moduleName = modName; $moduleBase = modBase; if (errFn) try { init(); } catch (e) { errFn(modName); } else { init(); } } function nullMethod(){ } var com_google_gwt_lang_ClassLiteralHolder__13Ljava_1lang_1String_12_1classLit = java_lang_Class_createForArray__Ljava_lang_String_2Ljava_lang_String_2($intern_7, $intern_8);
On the other hand, if you cut and paste the initial Java code to the JSweet sandbox, you will get the following result:
"Generated from Java with JSweet 1.0.0 - http://www.jsweet.org"; var net; (function (net) { var mccarroll; (function (mccarroll) { var WordCount = (function () { function WordCount() { } WordCount.count = function (args) { var total = 0; for (var index126 = 0; index126 < args.length; index126++) { var arg = args[index126]; { total += arg.split(" ").length; } } return total; }; return WordCount; })(); mccarroll.WordCount = WordCount; })(mccarroll = net.mccarroll || (net.mccarroll = {})); })(net || (net = {}));
The result speaks by itself: 150 lines of code for GWT vs only 23 lines of code for JSweet! Besides, the GWT output is not readable, while JSweet's output is concise, clean and readable (WYSIWYG).
But please don't get me wrong. The purpose of all this is not to say that GWT sucks and that JSweet is great. It is to make a point about the very different purposes of GWT and JSweet.
GWT's goal is to emulate Java, so that it is possible to use the Java APIs as is and run them in JavaScript. To do so, GWT needs to implement the Java semantics, which is a complicated task and requires a lot of JavaScript runtime. This approach is sketched in the following diagram:
JSweet, on the contrary, is using the Java syntax to program JavaScript, so that there is a direct mapping between the Java program and the generated JavaScript, as well as the used libraries in Java and in JavaScript. This approach can be summarized in the diagram below:
In theory, the GWT approach brings a lot because it is the promise to be able to reuse legacy Java code. Of course, this promise comes at a great cost. Firstly, the framework is very complex and hits organic limitations (not all Java libraries can run in a browser). Secondly, it binds the users to Java, so that it is harder to use up-to-date JavaScript frameworks with GWT.
JSweet's promise is to use the JavaScript APIs within a Java environment, but the counterpart is to drop Java APIs (almost) completely. This is often a shock to Java programmers, but the good news is that they can program AngularJS, Cordova/IONIC, or KnockoutJS clients with the Java syntax and directly share all the Java data objects with Java servers (JEE).
Heya; long time GWT programmer here.
It looks like JSweet definitely hits the sweet spot of simple, direct transpilation to JS.
What I would like to point out is that GWT can directly use javascript as well, either through the old JSNI syntax (where you can just write raw JS, and call out to java as needed), or through the new JS Interop, which allows you to make a java interface that maps directly to a JS type (and can be generated automatically), or you can export a java type to javascript, and have the field / method names line up correctly.
So, GWT is not stuck in pure java land.
And, when you pick a java lib with an unsupported class, it is possible to “super source” it; AKA, replace the JVM copy with a JS-friendly one that is only used by GWT.
Since there a lot of really good JS rendering libs out there, I have actually supported applications with views written in angular or another framework, and business logic compiled in GWT and exported to the client. This allows the business logic to reuse server classes, like data models, but use a nice JS lib to write the view.
JSweet and GWT definitely target different markets; with J2ObjC and GWT, libraries like PlayN exist, which let you write an entire game in java, and transpile it to all platforms; so, it gives maximal code reuse across all platforms. Meanwhile, it looks like JSweet has the sweet spot in integrating java with JS and producing nice, readable output (big win, very nice!)
With GWT, there is sourcemap support, so you can debug your original source in the browser console, but it does further obfuscate a user from the actual generated source. On that note, it looks like you used -style=DETAILED on the example output, which does generate a massive pile of code. -style=PRETTY will use shorter names and is semi-legible, whilst -style=OBF produces massively optimized, obfuscated, utterly illegible output. The optimizers in GWT will automatically elide all unused code, so a fair bit of boilerplate can actually be removed.
None of that changes the fact that GWT is indeed matching java semantics all the way down to rangeCheck on lists, and thus it reduces interopability with raw javascript. So, I’m glad to see JSweet coming down the pipeline, and look forward to seeing how it evolves!
Very nice work. 🙂
Hi! Thanks for your support and detailed comments and insights on GWT, which are definitely useful.
There have been a lot of interesting conversations around JSInterop and J2CL lately. See: https://github.com/cincheo/jsweet/issues/79
The conclusions could be summed up in that there are many commonalities between JSweet and J2CL/JSInterop, but there are also many fundamental differences, and not only because JSweet’s output code is cleaner. Of course, an obvious difference is that JSweet is out while Google is still working on J2CL/JSInterop 😉
Late reply, just ran across this:
With care, most of the cruft can be optimized away, see https://plus.google.com/+RayCromwell/posts/VK8URgZiLbS for example, which reduces a hello world to ~700 bytes, and could be made smaller to about 350 bytes with extra work.
The new J2CL compiler will optimize that hello world literally to ‘alert(“hello world”) in readable ES6’, with elemental2 providing most of the runtime interop bindings on the fly by reading WebGL, typescript, or closure headers and produce annotated JSInterop interfaces.
Google’s use of Java requires a more fully compliant implementation of JLS semantics, because we share code between iOS, Android, and the Web. So Guava has to work, the JRE has to work, you should just be able to pick up something like JBox2D and make it work without much trouble. If you can get away with a Web-only Java that doesn’t use most of the Java library ecosystem that’s a perfectly valid approach.
Thanks. I have found this: http://www.gwtproject.org/articles/elemental.html.
Note that JSweet’s JavaScript API generation process (from TypeScript) is quite stable now and I believe that it would save a lot of work to collaborate on this.
Regarding code generation, it looks that your post is aiming at minification rather than readability. The issue with readability is that supporting some of the JLS unavoidably leads to code pollution. For example supporting function overloading with int and double parameters requires to emulate the Java numbers (which I believe is also bad for performance).
All the point with JSweet is that you don’t need Java APIs to program Web applications (I am currently implementing an IONIC application with JSweet). You don’t need Guava or anything else because there are good enough (and efficient) JavaScript libraries out there and JSweet makes them available to Java programmers. Of course it can be useful to support some Java API to help porting legacy code or simply to share code between the client and the server. That’s why JSweet version 1.1.x (http://www.jsweet.org/jsweet-version-1-1-is-now-available/) goes further in supporting Java (still avoiding deep emulation and trying to make pragmatic choices). Note that JSweet forked a part of the JRE APIs implementation from GWT to get the basic collections faster.