ST-JS is a Java-to-JavaScript transpiler. It's goal is to let you write true JavaScript while borrowing the syntax and type checking of Java.
ST-JS lets you write your client-side code in Java, and will generate the equivalent JavaScript that can then be executed in a browser. It lets you write your Java source in the same way that you would usually write the equivalent JavaScript. It lets you use all the JavaScript libraries you have grown fond of, without ever forcing any other library on you.
The JavaScript ST-JS generates is not only readable, but also immediately recognizable. It strives to keep the generated JavaScript code as close as possible to your Java source code, and to make it look like you had written the JavaScript yourself by hand.
ST-JS also strives to stay as lightweight and un-intrusive as possible. This is why ST-JS does not impose or even suggest the use of any particular framework or library, be it server-side or client-side.
To better explain how ST-JS is useful and how it differs from existing tools, let me start by telling you a story. It's the genesis of ST-JS.
Alexandru and Nicolas (see: Team) and their team were working on a rather large HTML 5 web application. The development of this application was started many months ago by another team of developers. Both teams were composed mostly of java developers, with limited JavaScript experience. Development was going well at first, but soon slowed down to a crawl.
When the size of the JavaScript codebase reached approximately 5'000 lines of JavaSript code, all the developers began complaining that their progress was being hindered by JavaScript. The project would work perfectly in one browser, and then completely fail to start in another due to minor syntax errors. Fixing a bug in one corner of the application mysteriously caused two new ones in another corner. As a result development continued, but no real progress was made on the important stuff: delivering features to our customers.
One day, we improvised a small, informal retrospective. We analyzed our problems and dug out the root causes. Here is what we found:
After exposing these causes, the solution to the problem came to Alexandru. We could write a Java to Javascript transpiler!
This had multiple benefits for us:
Within about a week, all the existing JavaScript code was converted to Java code following the ST-JS conventions. Since then, the project was able to grow freely and healthily.
Before deciding to write ST-JS, we have listed our needs, and studied the existing alternatives. Our needs were:
Out of all the existing solutions, none matches all of these criteria. ST-JS is our answer to this problem, and it does fulfill all of our needs.
There is another extra benefit that wasn't part of our original requirements. If for some reason, in the middle of your development process you discovered that ST-JS doesn't work for you, you can safely dump your Java source files and continue your development in JavaScript directly, by basing it on the generated JavaScript.
As for many other tools, the answer is: "It depends".
The benefits of ST-JS are most useful if your project matches one or more of the following criteria
Of course, you can use ST-JS for any project, but there is a slight overhead in development due to the fact that you have to run the ST-JS generator on your Java code everytime you change it. ST-JS comes with a few integrated mechanisms to do it automatically, but at the time of writing it only works properly in Eclipse.
Here is an example of a code written in Java and the corresponding generated JavaScript. The Java code may seem a bit longer and possibly more complicated at a first glance, but it provides you with all the advantages of a typed language, while preserving as much as possible the resemblance to the JavaScript style of programming. The visual resemblance is crucial here since it allows for faster learning and easier transition. This visual resemblance maintained throughout the library design
// Java $("#form").submit((ev, THIS) -> { StockData stockData = that.updateStock($("#newStock").val()); $(that.row(stockData)).appendTo("table tbody"); that.stocks.push(stockData.stock); });
// Generated JavaScript $("#form").submit(function(ev) { var stockData = that.updateStock($("#newStock").val()); $(that.row(stockData)).appendTo("table tbody"); that.stocks.push(stockData.stock); });
The problem with having JavaScript code generated from Java is that Java is less rich than JavaScript. There are JavaScript constructions that do not have their corresponding in Java (or they are different) like object (map) constructors, array constructors, inline functions. We tried to provide constructions that may look unusual for a Java developer but somehow familiar to a JavaScript developer.
Javascript has a very handy syntax for creating objects with all of their properties already initialized. As you probably know, this syntax is the basis of the JSON format.
ST-JS promotes the usage of a similar syntax in Java, but using proper type declarations.
// Java StockData stock = new StockData() { { last = 10.0; close = 2.0; stock = "ABC"; } };
// Generated JavaScript var stock = { "last" : 10, "close" : 2, "stock" : "ABC" };
In JavaScript, every object can also be used as an associative array with string keys. This allows each field of an object to be accessed directly via its name.
ST-JS provides a bridge class that allows you to use this behavior in your Java code, and see it the proper associative array construct being used in the translated JavaScript. This capability is provided via the org.stjs.JavaScript.Map class
// Java // This Map is a org.stjs.JavaScript.Map Map<String, Object> stock = $map( "last", 2.0, "close", 3.5, "symbol", "ABC" ); Double close = map.$get("close"); stock.$set("close", 4.8); stock.$delete("close");
// Generated JavaScript var stock = { "last" : 2.0, "close" : 3.5, "symbol" : "ABC"; }; var close = stock["close"]; stock["close"] = 4.8; delete stock["close"];
JavaScript arrays behave like a mixture of a Java's lists and arrays. ST-JS provides the org.stjs.JavaScript.Array class to bring the JavaScript behavior in Java. This class offers the same methods as its JavaScript counterpart. Givent that the bracket ([]) construct can only be used in Java with a regular array, this class provides $get/$set methods that will generate the expected code in JavaScript
// Java Array<Integer> array = $array(1, 5, 10); Integer value = array.$get(2); array.$set(2, 4); // looping by counter for(int i = 0; i < array.$length(); i ++){ console.info(i + " : " + array.$get(i)); } // looping with forEach array.forEach((el, i, arr) -> console.info(i + " : " + arr.$get(i)) );
// Generated Javascript var array = [1, 5, 10]; var value = array[2]; array[2] = 4; // looping by counter for(var i = 0; i < array.length; i ++){ console.info(i + " : " + array[i]); } // looping with forEach array.forEach(function(el, i, arr){ console.info(i + " : " + arr[i]); }
Class inheritance is a fundamental concept in Class-based, Object-Oriented languages like Java. That concept doesn't exist in JavaScript, but its behavior can be reproduced in its native prototype-based inheritance.
In order to keep the generated JavaScript code as close as possible to the source Java code, we have decided not to enforce the (in-)visibility of members in JavaScript. This means that when a field is marked privat, protected or package-protected in the Java source, it will be generated as if it was public in the generated JavaScript. This is an acceptable compromise given that the visibility restrictions are enforced by the Java compiler before the JavaScript code is generated.
// Java public class Child extends Parent { public Child(String name) { super(name); this.name = name; } public static int staticField = 1; public int instanceField = 2; public String name = "default"; @Override public void instanceMethod(String n) { super.instanceMethod(n + "-"); } public static void staticMethod() { staticField += 1; } }
// Generated JavaScript Child = function(name) { this._super(null, test); this.name = name; } stjs.extend(Child, Parent, [], function(constructor, prototype){ constructor.staticField = 1; prototype.instanceField = 2; prototype.name = "default"; prototype.instanceMethod = function(n) { this._super("instanceMethod", n + "-"); } constructor.staticMethod = function() { Child.staticField += 1; } });
The comparison of objects or primitives is slightly different
between Java and Javscript. For example equals method does
not exist in JavaScript (and there is no way to influence how
egality operators behave in JavaScript).
Conversely the
triple "=" (===) does not exist in Java. And the "==" sign has a
slightly different meaning: in JavaScript it may be accompanied by a
type conversion!
To have a predictable behavior of the
generated JavaScript, the equals method is generated as-is.
The equals method was added to all primitive types wrappers in
JavaScript. More all generated classes will have an equals method
defined in their hierarchy. The "==" sign is left untouched as there
is basically no automatic type conversion involved (because the
initial Java code wouldn't allow it).
Due to the fact that ST-JS strives to keep the generated JavaScript code as close as possible to your source Java code, it has to deal with the limitations of both Java and JavaScript. This means that some of the features of the Java language cannot be used in your ST-JS code, and will cause an error during the Java->JavaScript transpilation.
Here are the main restrictions imposed by ST-JS
No overloading. This means you cannot have multiple methods with the same name (or multiple constructors) in the same class. Method overriding however is allowed, so you can easily change the behavior of a method in a subclass.
No separate namespace for fields and methods. As a result, you cannot have a method and a field with the same name within one class and all of its parent classes and interfaces!
Javascript keywords cannot be used as identifiers. So you cannot use JavaScript keywords or reserved words (such as "function", "prototype", "var", etc...) as field, method or class name.
Limited support for varargs.. Varags are only allowed if your method has a single argument, and this argument's name is "arguments".
Instance fields can only be initialized at declaration with literals. Therefore any instance field that is not a primitive type, a Number, a String or null must be initialized in the constructor.
No native Java arrays.. Native Java arrays (such as String[]) cannot be used as they are not direclty compatible with the JavaScript version of arrays. JavaScript arrays are much richer than Java arrays, and provide a set of useful methods such as push, slice, etc... On top of that, the for-in iteration in Java array is done on values (for(String s : array)) while the iteration in JavaScript is done on indexes (for(var idx : array)). ST-JS provides the org.stjs.JavaScript.Array class to reproduced the JavaScript array behavior.
No Java collections. Since JavaScript doesn't have an equivalent to the java.util Collection interfaces and all of its various implementations, they cannot be used in your ST-JS code. We have decided against supporting these collections directly, because they are a very typical Java idiosyncrasy. Using them in some JavaScript code would be awkward and doesn't fit the philosophy of ST-JS.
While some of these limitations could be worked around by making STJS a little bit more complex, it would result in the JavaScript code diverging too far from the original Java source. Removing these limitations would need going against to the philosophy of ST-JS: the generated JavaScript code is immediately recognizable when compared with your Java source code. That's why we prefer to produce transpilation-time errors and to ask the user to find a suitable alternative.