How To Improve A Web Front-End With TeamVM ByteCode Compiler!
So, TeaVM is a Java bytecode compiler in JavaScript. The idea of creating TeaVM came to me while I was working as a full-stack Java developer and used to write the GWT front-end. In those days (and this is about 5 years ago), tools such as Node.js, Web pack, Babel, TypeScript were not widely used.
Angular was in the first version, and alternatives like React and Vue.js were not at all. Then even seriously, people tested the sites in IE7 (and some, who were unlucky with customers, even IE6). In general, the JavaScript ecosystem was much less mature than now, and it was impossible to write JavaScript without pain.
GWT I liked the fact that against the backdrop of all this it seemed to be an adequate solution, although not without its shortcomings.
The main problems are listed below:
- The speed of rebuilding left much to be desired. GWT Well, it’s very slow. We started the compilation and went to drink tea with cookies.
- GWT at the input receives the source code in Java and therefore, firstly, it works slowly (parsing and resolving Java code is not an easy task), secondly, it does not support anything other than Java, and thirdly, it lags far behind even in support new versions of Java itself.
- In GWT, it’s just a horrible framework for creating a UI. On the one hand, he tries to abstract from the DOM; on the other hand, it does not always go there, the abstraction proceeds, and it’s not easy to correct this by direct intervention in the DOM. It is necessary to break through the thick layers of abstraction. In addition, while the entire civilized world went along the path of the declarative description of markup (WPF in .NET, JavaFX, Flex, the above-mentioned modern JavaScript frameworks), GWT in the old fashion made it necessary to compile UI from widgets.
I did not think that the idea of writing web applications in Java was bad. All the shortcomings of GWT were from the fact that Google, I think, simply did not invest enough resources in its development. I preferred to put up with the shortcomings of GWT, just to avoid javascript.
And then I thought – why not use Java bytecode as input? Bytecode saves a lot of information about the source program, so much that the decompilers manage to then almost exactly restore the source code.
Java does all the hard work of generating bytecode, so it takes very little time to generate JavaScript. Plus we get support for other languages for JVM and almost free support for new versions of Java (bytecode is much more conservative than Java language).
What interesting happened to the project
I already said that a lot of interesting things happened to the project. Here are the most important points about which I would like to tell:
- There was support for threads. In JavaScript they are not at all, WebWorkers is not about that, because, unlike threads, they do not have a shared state, i.e. You can not create an object in one of the stakes and transfer it to another without copying. Instead, TeaVM can transform the code of methods so that their execution can be interrupted at some given points (roughly the same as the babel does when it sees await). This made it possible to make some variant of cooperative multi logging, when one thread, after getting to some long IO-operation (well, or just some Thread.sleep ()), pauses and allows execution of another thread.
- There was support for web assembly, while experimental. It supports arbitrary bytecode, but some JDK methods are not supported, which is strongly tied to the JVM (this is primarily a different reflection). There is also absolutely no interop with anything; to cause JavaScript, you have to dance a very long time with a tambourine. In general, for the sake of web assembly support, I had to write my GC and stack unwinding, so this web assembly is low-level! Over time, they promise to add support for GC and exceptions, but for now, it’s a real assembler.
- There was a framework for writing a web-freehand on TeaVM, and this is the same news, because of which I wrote this article. However, the details are lower.
Web-based framework
The Web framework for TeaVM is called the Flavor and it is entirely written in Java. Recently I published the first version (behind the number 0.1.0) on Maven Central. Ideologically, it resembles modern Angular 2/4 and Vue.js but is built entirely on idioms close to the Java developer.
- For example, all Flavor components are represented by ordinary Java classes, marked annotations, there are no separate props and state, or some special objects that must encapsulate changing properties. The language of the HTML templates is completely statically typed, any access to the object’s property or the call of the event handler is checked at compile time and, for example, typos in the name of properties simply will not allow compiling the project.
To communicate with the server, Flavor suggests using interfaces marked with JAX-RS annotations, and data is transferred using DTO, which in turn is marked with Jackson annotations. This should be convenient for Java developers, who are very likely already know and use these APIs in their projects.
- A natural question arises: why create a framework if there are existing: React, Angular, Vue.js?
- You can just use JavaScript interop and do not invent anything. Of course, I thought about it. But no, everything is much worse than it seems at first glance.
- These frameworks are built around idioms of dynamically typed JavaScript, and in it, an object with the necessary set of properties can be created from the air and hoping that the “class” of the object has a magic method with the desired name. In general, in the world of JavaScript, frameworks developers are not used to thinking about typing.
- It can be overcome by writing all kinds of adapters, wrappers, preprocessors, generators. But in the end, the system will be more complex than the original frameworks, so it was decided to write your own.
Few examples
Creating a Project
Of course, all interested can read the documentation on the site. But to make it easier and faster to taste Flavor taste, I will show a small example here.
So, you can create a project using Maven:
mvn archetype:generate \ -DarchetypeGroupId=org.teavm.flavour \ -DarchetypeArtifactId=teavm-flavour-application \ -DarchetypeVersion=0.1.0
The generated project is annoyed, as expected, with the command mvn package.
Templatize
The page is described by two files – a class code that describes its behavior (provides data to display, contains event handlers) and an HTML template. In the created project there is already an example of a page, but I still give one more example. You can replace the example generated from the archetype or simply add two more files to the existing ones. Here is the code for the page class:
@BindTemplate("templates/fibonacci.html") public class Fibonacci { private List<Integer> values = new ArrayList<>(); public Fibonacci() { values.add(0); values.add(1); } public List<Integer> getValues() { return values; } public void next() { values.add(values.get(values.size() - 2) + values.get(values.size() - 1)); } }
And here is an example with Flavor:
<ul> <!-- values - this is a reduction for this.getValues() --> <std:foreach var="fib" in="values"> <li> <html:text value="fib"/> </li> </std:foreach> <li> <! - in fact, event: click can simply execute some piece of code, but for convenience, it is recommended that the event handler fit into the method, and from the template just call it -> <button type="button" event:click="next()">Show next</button> </li> </ul>
std:foreach, html:text & event:click – these are the components of the Flavor. The user can describe his components (who are interested in exactly how they can read about this in the documentation), while they can either manually draw their DOM, or do it through a template. In these components there is nothing special, they are not implemented by compiler magic. If you want, you can write your own analogs. For illustration, you can see the code here.
Finally, here’s how the main method’s code should look like:
public static void main(String[] args) { Templates.bind(new Fibonacci(), "application-content"); }
All the main magic starts here. The framework does not create an instance of the page class and does not control it. Instead, you create it yourself and control it as you like, and Flavor simply generates a DOM, inserts it into the correct location, tracks changes in the state of the object, and redraws the DOM according to these changes. By the way, Flavor does not redraw the entire DOM but changes only the necessary part of it.
I want to note once again that templates are statically typed. If you make a mistake and write event:click=”nxt(), the compiler will write an error message. This approach also allows you to generate a faster code – Flavor does not waste time after loading the page in order to parse directives and initialize the bindings; he does it all at compile time.
REST client
Now I would like to show how Flavor can be useful to a full-stack developer. Let’s say you use some CXF in conjunction with JAX-RS. You wrote about this interface:
@Path("math") public interface MathService { @GET @Path("integers/sum") int sum(@QueryParam("a") int a, @QueryParam("b") int b); }
and implemented it (for example, in the class MathServiceImpl), registered the implementation in CXF. You have a small REST-service.
Now, to make a request to him, you can write the following code from the client:
MathService math = RESTClient.factory(MathService.class).createResource("api"); System.out.println(math.sum(2, 3));
(You can see in devtools that this code will send a GET request to the address /api/math/integers/sum? a = 2 & b = 3).
In general, there is no need to somehow explain to the web client how to correctly make a REST request to the desired endpoint. You already did this in a single way for the server and for the client. It is possible to further increase and refactor the REST-service, without having to synchronize it from the server side and from the client side – there is a synchronization point in the form of the MathService interface.
In GWT, there is a similar mechanism, GWT-RPC, but it forces you to generate an additional async interface and use callbacks, whereas TeaVM can convert the synchronous code to asynchronous. And GWT-RPC uses its own, incompatible protocol, so you can not re-use it, for example, for an iOS client, by creating an endpoint for GWT-RPC.
What else is interesting?
Of course, in a short review article, I can not talk about anything at all. Therefore, just mention that there is such an interesting in TeaVM and Flavor, which makes them quite suitable for creating high-quality web applications.
- Integration with IntelliJ IDEA. Running Maven every time is not serious, after all, Maven is used for assembly on production, and not at development time. TeaVM can run directly from the IDEA, just click the Build button.
- The Debugger. Without it, it’s impossible to talk about anything at all, as a serious tool. TeaVM can generate standard source maps; There is also a debugging information format that is used in the IDEA plug-in.
- Very fast compiler. I spent efforts on optimization, and bytecode – it’s so primitive that there’s nothing to slow down when processing it. This is not Java, where it is necessary, for example, to output types, with generics, variance, and captured-types. In addition, when running the compiler from IDEA, several techniques are used to reduce compilation time, such as running the compiler from the daemon and caching information for subsequent assemblies. All this makes it possible to achieve quite a comfortable speed of JavaScript rebuilding.
- A good optimizer, able to cut off from the impressive size JDK very small piece, necessary for the normal operation of the application, and even make it fast.
- Routing. The flavor can parse and generate links that, from the API’s point of view, are quite statically typed interfaces.
- Validation of forms.
- Modal windows with a blocking API, as in Swing.
- This is already mentioned above – supported by Kotlin and Scala, not just Java.
- By the way, TodoMVC version for TeaVM is written on Kotlin.
- The open license Apache 2.0, habitual in the world of Java-development.
Why Java on the Web?
Recently, the JavaScript development ecosystem has become quite mature. The developer is easier not to use any heavyweight monsters like GWT, but to learn how to set up tools that have become de facto standards and write in a modern language with a lot of advanced features or even in a modern statically typed language (TypeScript).
- The problem is that all this must be studied. Learn not just the syntax of the language (an experienced developer will spend a few days), learn a lot – libraries, idioms, developer tools. I’m sure that yesterday’s experienced Java developer will take it and sort it out with all this in a couple of weeks, but the question is, how well will it sort out? Will he be able to write really good code? Even if it can understand and can, there are still problems. First, the developer will have to switch the context of Java and JavaScript. Secondly, so the developer will have to spend more time adjusting the tools.
Well, I have a question for the Java community. Why is there such an insistent move towards the backend from the side of JavaScript? I suspect that exactly from those considerations that I have quoted above. Why not be the same movement in the opposite direction? Here is a community of JavaScript frontend developers that cooperated and spawned a backend ecosystem around node.js. What we, the community of Java developers, are worse? There is such a myth that supposedly JavaScript is smart and lightweight, and Java is big and heavy and is not suitable for creating a frontend. In fact, with my project, I’m trying to prove that this is exactly what a myth is and that properly prepared Java can also be small and fast. The example is the implementation of TodoMVC, which takes 125kb (try to write TodoMVC on React or Angular 4 and see what a hefty you will get a bundle).