Fork me on GitHub

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:

The GWT approach

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:

The JSweet approach

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).