JSweet version 1.0 was released today.
JSweet is a Java to JavaScript transpiler built on the top of TypeScript and it gives access to hundreds of up-to-date and well-typed JavaScript APIs from Java. The following figure shows how JSweet translates from the TSD repository and uses the TypeScript tsc compiler and APIs (d.ts
) to transpile Java into JavaScript.
With the release of version 1.0, I would like to explore the main reasons why you, as a programmer, would want to try JSweet out, and ultimately use it to program Web applications in a better and safer way.
This is the most obvious reason, of course. JSweet ensures strong typing on JavaScript APIs and programs. JSweet has been designed to match TypeScript typing concepts and a JSweet program can bring the same type safety level as a TypeScript program. Furthermore, the JSweet design (see the previous figure) makes it very robust, because it cross-validates the source code transpilation (Java → TypeScript) and the API definition files translation (TypeScript → Java). On one hand, it ensures that the transpiled TypeScript code is correct with regard to the APIs (otherwise, the tsc transpilation would fail). On the other hand, it ensures that the generated APIs from the d.ts
is fully equivalent to the original definitions (otherwise, the tsc transpilation, which uses the original APIs, would also fail).
JSweet is actually more constrained than TypeScript. In JSweet, it is not possible to use the any
type, which is legitimate when reusing legacy JavaScript code in TypeScript, but does not make sense in Java. Therefore, with JSweet, programming with no types is possible through down-casting or the use of $get
and $set
reflective methods, which results into ugly-to-read code. For example, if you know that an object o
defines a p
property (could be a function), which is not defined in the definitions, then you can just write:
o.$get("p");
Which is basically equivalent in TypeScript to:
(<any>o).p;
Or:
o["p"];
One could argue that JSweet’s syntax sucks, but this bad-looking code is on purpose. It aims at enforcing the programmers to write well-typed APIs and programs. What may seem like a defect to some programmers is actually a strength when programming complex applications. When working with teams, you will be happy at the end of the day that your co-workers commit well-typed code rather than good-looking code that is actually not typed (and not safe as a matter of fact).
Many JavaScript APIs are very good APIs and allow the programmers to fully take advantage of HTML5. Before JSweet, most of these great libraries/frameworks were not accessible to Java developers (except by writing some bridges/wrapper manually, which is a herculean task). JSweet is the first project in the world to automate the translation of well-typed JavaScript APIs from the TypeScript DefinitelyTyped repository. This means that JSweet programmers can take advantage of the work of thousands of DefinitelyTyped contributors, which makes DefinitelyTyped the most up-to-date typings for the JavaScript libraries and frameworks. Once a library is added or updated to DefinitelyTyped, the new library or the update will be available to the JSweet repository the day after.
Not convinced? Well, take a look at the Java code of these cool projects: Angular.js and Node.js in Java, and Babylon.js in Java.
Java tooling, and especially IDEs, remain the best and most efficient to build complex applications. Refactoring, references, call graphs, unit testing, completion, and so on, are very efficient and more advanced than JavaScript and TypeScript tooling. Besides, the Java echosystem will be appreciated by the Java developers: Javadocs of the APIs, Maven for automatic dependency management, and so on. As an example, checkout this Javadoc-generated documentation for the jQuery framework.
JSweet generates programmer friendly TypeScript and JavaScript code. In addition, by design, JSweet uses the exact same APIs in Java and in TypeScript, which means that you have a strong warranty that the generated TypeScript programs will be compatible to current TypeScript APIs. Additionally, JSweet generates no extra code and uses no additional JavaScript runtime. JSweet is not a framework and does not impose any specific architecture (no other than the one you choose by picking up JavaScript frameworks of your choice). In short, I often say that JSweet (similarly to TypeScript) is a lightweight and a WYSIWYG transpiler, because the generated code exactly maps the original source code and that there are no hidden librairies or runtimes that are used by JSweet. For all these reasons, Web developers using JSweet can easily revert to TypeScript (or even JavaScript) and forget completely about JSweet. JSweet does not bind you to JSweet!
One of Java strength is that it is an Object Oriented language that enforces strict structuring rules. By adding a Java syntax layer on the top of TypeScript, programmers will take advantage of some Java characteristics that make it so comfortable to most programmers. For instance, abstract methods and classes, final variables, method overloading, static variables instead of global variables, and static imports, are Java mechanisms that can be useful when used in an appropriate manner. But a small example is sometimes better than a long speech. Consider the following TypeScript code:
private draw(ctx : CanvasRenderingContext2D) : void { ctx.save(); ctx.beginPath; ctx.fillStyle = "white"; ctx.arc(this.position.x, this.position.y, radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.fill(); ctx.restore(); }
This TypeScript code may contain 2 mistakes, which are entirely due to how TypeScript is designed as a superset of JavaScript. First, line 3, beginPath
is a function that is not called (the programmer forgot the parenthesis). In TypeScript, this statement is completely legal but has no effects at runtime. As a consequence, the drawing path is never begun on the context, having unpredictable runtime effects that may be difficult to debug (because this code fails silently, finding the line of code that makes the drawing look bad can be complicated depending on the context and on other functions that may use the given CanvasRenderingContext2D
instance). Second, at line 5 the radius
variable is not qualified with this
, and as a consequence, it is a global variable. The bad thing is that the programmer may have just forgot the this
access, wanting to access a class field rather than a global variable of the same name. In Java, no global variables are even allowed, so all of them must be defined as static variables and qualified with the class they belong to. Java developers can omit the qualification by using static imports, but it is still safer than in TypeScript because it has to be declared and because the Java compiler raises an error when a conflict is found.
Of course, this may be seen as simplistic examples, but as a matter of fact, these are examples that happened to me while I was developing an HTML5 canvas-based game. I developed the same game in both JSweet and TypeScript during one month. Several times, I had these hard-to-find bugs in TypeScript that did not even occur in Java… just because in Java they are not possible.
Server-side and client-side APIs are different but classes (DTO) can be shared between the client and the server easily. On the server side, one can use Node.js (check out this example) or a regular Java-based server. It is particularly interesting within an IDE, when programming a full multi-tiers application within Java projects that can be compiled altogether. In that case, when refactoring and looking up for dependencies to DTOs, it will refactor/find on both client side and server side. With JSweet, the typical issue of maintaining consistency between your client and server tiers code (when data evolves) just becomes an out-of-the-box feature! Here again, let us take a small example to illustrate this quite important point:
public class Server { [...] private Response myService(IHTTPSession session) { logger.info("request from " + session.getHeaders().get("remote-addr") + " allHeaders=" + session.getHeaders()); MyDataResponse response = new MyDataResponse(); response.startTime = OUTPUT_DATE_FORMAT.format(new Date()); response.data = "hello client"; String jsonResponse = new GsonBuilder() // .setPrettyPrinting() // .create() // .toJson(response); return newFixedLengthResponse(jsonResponse); } [...] }
public class MyDataResponse { public String startTime; public String data; }
public class Client { [...] currentRequest = new XMLHttpRequest(); currentRequest.open("POST", SERVICE_URL, true); currentRequest.onload = e -> { MyDataResponse response = (MyDataResponse) JSON .parse(currentRequest.responseText); String data = response.data; [...] } }
This simple application snippet uses the Java NanoHttpd API for the server side, and uses core XMLHttpRequest
JavaScript API on the client side to communicate with the server. However instead of using plain JavaScript or TypeScript, it uses JSweet on the client side, so that the client side is written in Java. As a consequence, the data layer (MyDataResponse
class) can be shared as is by both the client side and the server side.
Now, please imagine that you are programming this application within your favorite IDE. Since all server side, data and client side layers are full Java programs, all the IDE features apply. For instance, finding the references to the MyDataResponse
object will work both for client and server side (although one actually runs in a browser and the other one in a JRE). Any refactoring will also work. For instance, renaming the MyDataResponse.data
field to MyDataResponse.content
(for instance) will successfully rename all its uses, both on the client (line 7) and on the server (line 8).
Although JSweet is JavaScript and does not use any Java APIs, JSweet has been carefully designed so that the program behavior remains close to what would be expected by a Java programmer. For example, static variable initialization, variables scopes in lambda, foreach loops and so forth will behave like in Java. Of course, some constructs will behave like in JavaScript, but this is never for arbitrary reasons (refer to the Language Specification‘s Semantics section for more details on Java and JSweet behavior). Basically, Java programmers will find that 80% of the JSweet concepts are common to Java, so that it will be much easier for a Java developper to use JSweet than JavaScript or even TypeScript. Regarding the JavaScript APIs, besides the completion and inlined doc in the IDE, the JSweet repository gives access to the Javadoc, which makes it easy for the Java developers to learn existing JavaScript APIs.
JSweet follows a self-imposed language segmentation design principle to avoid confusion. When programming with JSweet, programmers are actually programming JavaScript with the Java syntax. Programmers use the JavaScript APIs, not the Java ones, and JSweet transpiler tells them when writing unallowed code (typically when trying to access the JDK APIs). JSweet even comes with an optional strict mode that completely hides the Java APIs to the users. This separation between Java and JavaScript is a good thing, because when it is not there, it encourages the programmers to try to write code that runs both in Java and JavaScript. These attempts to write portable code in Java and JavaScript actually brings more issues than it solves. In JSweet, we believe that the APIs, the language semantics, and the execution environments are so different in Java and JavaScript, that it is foolish to think that a generic solution is even possible.
In the coming releases, JSweet will propose a plugin system to allow the programmers to support some Java APIs for their own context (but it will be at their own risks).
JSweet can easily integrate with existing JavaScript and TypeScript programs. To do so, you just need to use the @Ambiant
annotation to declare an API to an external JavaScript program. With JSweet, it is possible to have mixed Java and TypeScript development teams working on the same Web application. Coming soon, we will set up an online tool to translate your own d.ts
files to Java so that it gets even simpler to integrate to TypeScript applications or components.
Integrating with legacy Java code is easy too. Although JSweet is not designed to run legacy Java code (on purpose), there are still many ways to interoperate with Java through the use of DTOs. The strength of JSweet being that it gets really easy to refactor you programs when your DTOs change (both the JSweet and the Java programs will be impacted). External tools such as this project from Vojtěch Habarta can also help integration. Last but not least, remember that JavaScript programs (and consequently JSweet-generated programs) can run on a JRE and interoperate directly with Java programs within the same JRE, simply by using the Java Script Engine API, available in Java since version 1.7…
JSweet is a lightweight transpiler that integrates very well to all kinds of environments. You can build JSweet applications with Maven, Gradle, Ant, but even with JavaScript build tools such as Gulp. Since JSweet is WYSIWYG, the generated program is very close to the original source code, which makes it very easy to debug in an browser, with or without source maps.
To get started, you just need to clone the quick start project as explained here. An Eclipse plugin is available, and a IntelliJ plugin is on its way (but IDE plugins are not required to use JSweet). You can even try JSweet programming with our online sandbox (by the way, the sandbox is a small Web application… written with JSweet).