Comparing JVM alternatives to JavaScript
Written on 17 Apr 2019, 09:11 PM (Last updated on 21 Apr 2019, 07:56 PM)
I am mostly a backend developer, but every now and then I need to do some frontend work. Even if just for personal use.
For that reason, I’ve been following developments in the JavaScript world from a distance, but I dislike much of
what I see.
The thing I dislike most is how complex the toolchain for a professional JavaScript application has become. The days
when you wrote a few lines of HTML, opened it on the browser with file:///home/me/index.htlm
, then linked to a
simple JS file to add dynamic behaviour – with updates only a page refresh away – seem to be long gone.
The MDN page on JavaScript
actually shows how to use HTML and JS as it was intended – no build tools, no frameworks. If only people listened.
The JavaScript toolchain of today has all the complexity of backend toolchains, and then some. And just a handful of
years ago, many parts of the toolchain were different! Checking some of the answers on this
StackOverflow question is hilarious.
This Pluralsight JS development environment
course suggests you need 32 tools to build your application (e.g. babel, chai, cheerio, eslint, mocha, webpack…).
This article from 2016
that keeps coming up on Google and DuckDuckGo searches shows how a basic JS development environment consists of:
- dependency management tool (npm or yarn).
- module bundling (webpack or gulp, browserify, bower).
- ES6 compilation (babel).
- task automation (npm scripts).
- live reload (live-server).
If you follow instructions from that article, you end up with 3 config files (one for each of npm, babel and webpack),
your build steps are managed by shell scripts embedded into a JSON config file, and your project
now requires a compilation step and a live reloading HTTP server to run.
At this point, I thought that if I am going to be compiling stuff, I should at least use a compiler that can type check
my code!
So why not try TypeScript?!
I then followed this tutorial to start a TypeScript project.
Now, on top of those config files I mentioned earlier, we get 2 more: tsconfig.json
and tslint.json
.
It’s all so complex Google has even created a tool (GTS, or Google TypeScript Style)
to manage TypeScript config.
I tried to use that but then the browser refused to run my JavaScript file with some module-related error… I don’t
know which of the myriad tools I was using could fix this for me, so I just decided to look for alternatives to this
madness!
In doing so, I found this really nice blog post proclaiming that
You might not need a build toolchain.
It shows that it’s still possible to use modern tools like React without build tools!! But
unfortunately, that is not very practical once you start using dependencies and your application grows larger than
a few hundred lines of JavaScript.
Back to square one.
Given that a build/compilation step seems to be a necessary evil no matter what we do, and that we’ve known
how to write large applications for a long time in the backend using decent tools, why not try to use those in the
front end too!?
After all, back in 2011 when I was doing web development, people were already experimenting with that idea…
for example, I was using GWT, which is a Java-based web toolkit, in 2011…
surely, 8 years later, the scene should have improved a lot for Java-based web applications?
I set out to see what’s available out there. Can a backend developer who is happy to stay with the nice tooling and
great ecosystem of the Java world do frontend work without jumping ship to the madness of the JS world?
Get ready to find out!
Methodology
As a means of comparing each option listed in this blog post, I decided to create a very simple
counter app, just like the React app demonstrated in the
You might not need a build toolchain
blog post I mentioned earlier.
For reference, here’s the code for that app using React (without JSX):
The methodology I used to compare the different options is very straightforward:
- find a getting-started guide and run the demo application to see how easy it is set it up.
- check how well the framework fits into what Java developers expect.
- create the counter app using the most basic tools provided by the product/framework.
- measure the application’s size, LOC and performance.
The last bullet point was inspired by the blog post
A real-world comparison of front-end frameworks.
The application size is determined by looking at the browser’s network tab (all types of resources are included to
avoid favouring frameworks that rely on heavy, non-JS resources).
The performance, using the auditing tool from Google Chrome’s built-in
Lighthouse.
Here are the React’s results for reference.
The React JS files were downloaded and served from the local server instead of from a CDN to make comparisons fair.
Application size:
Performance results:
Now, we know what we’re up against!
So, here we go, starting with the grandpa of all Java-based web toolkits: GWT.
Click on the titles below to expand each section.
GWT – Java-source-to-JS, server and client-side framework
GWT
Website: http://www.gwtproject.org
Probably one of the first efforts to get Java in the browser without Applets or plugins, GWT has been around for a long
time (since 2006). It’s a very mature technology now, but it has not been a Google-backed framework since 2013… I
remember at the time Google seemed to be focusing its web efforts on Dart, which competed
directly with GWT in the space of JS alternatives, and the GWT community was feeling abandoned. Anyhow, the project
seems to be still around, so I thought I must include it in this comparison to really understand how the more modern
alternatives are doing.
To get started with GWT, their website recommends either downloading the SDK
or installing the Eclipse Plugin… I haven’t used Eclipse in years (I’ve migrated to IntelliJ and never looked back)
so I took the first option:
./webAppCreator -out gwt-app com.athaydes.GwtApp
This command runs very quickly and creates an Ant project (what?!).
I have been working with Java for over 10 years
and even I missed out on Ant heydays – Maven was already the Java build tool of choice if I remember correctly.
But I don’t mind Ant! Converting it to a Gradle or Maven build later should be almost trivial. Not to mention that
IntelliJ has great support for Ant projects, so you can open the project in IntelliJ and everything will work.
To run the demo app:
ant devmode
This opens up an unmistakable Swing app which lets you control the development server! I could swear it looks the same
as it did in 2009! Opening the actual app on the browser, I can see that, apart from some fresher styles, the app also
looks pretty much the same.
The demo app tries to show how easy it is to use GWT’s strongest feature: the seamless RPC-framework which lets
developers almost forget about the distinction between client and server, letting the two communicate as easily as
calling Java methods.
But for the purpose of building the counter app, we don’t need that. So the first thing I did was to remove the RPC
stuff from the getting-started app.
A GWT application normally consists of a single sources root which is divided into:
- client – client-side only code that runs in the browser.
- server – server-side only code – the backend.
- shared – code visible from both client and server.
you can configure which packages are visible on the client-side part of the app in the
GwtApp.gwt.xml
file, at the
top-level package of the app. This file also controls the GWT Theme the app should use, so you can, for example,
easily make your app use a dark theme.
In this get-started app, the Java sources are in src/
and the web stuff in war/
. The entry point HTML file can be
found at war/GwtApp.index.html
. Looking at it, it consists of a fairly normal HTML file, but strangely, most of the
view is already there – implemented using a <table>
layout (clearly, this was created circa 2008).
I wanted to implement the view using code, so I removed the table
thing and added a <div id="content"></div>
.
Implementing UIs in GWT is very easy because of the widget-based API. So, I was able to create the counter app in less
than 5 minutes (please keep in mind I have prior experience with GWT, even though I barely remembered anything).
But when I tried to re-compile the code, it complained about my lambdas – the ant file declares we want to use Java 7!
Someone should update that!! GWT does support Java 8, so I updated that to Java 8 and all was good.
This is the code I came up with to implement the counter app in GWT:
package com.athaydes.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.*; import java.util.function.Consumer; public class GwtApp implements EntryPoint { public void onModuleLoad() { RootPanel.get( "content" ).add( new Counter() ); } } class Counter extends Composite { private int value; Counter() { HorizontalPanel buttonsPanel = new HorizontalPanel(); buttonsPanel.setSpacing( 10 ); Button up = new Button( "Increment" ); Button down = new Button( "Decrement" ); Label out = new Label(); Runnable update = () -> out.setText( "The current count is " + value ); update.run(); Consumer<Boolean> handler = ( increment ) -> { if ( increment ) { value++; } else { value--; } update.run(); }; up.addClickHandler( clickEvent -> handler.accept( true ) ); down.addClickHandler( clickEvent -> handler.accept( false ) ); buttonsPanel.add( up ); buttonsPanel.add( down ); VerticalPanel root = new VerticalPanel(); root.setSpacing( 20 ); root.add( buttonsPanel ); root.add( out ); initWidget( root ); } }
Quite nice. Hot reloading works with the Super Dev Mode so fixing little things in the UI is quick and easy.
To compile the production version of the app:
ant build
The generated JS files are placed in the war/gwtapp
directory. Serving the war
directory is enough to run the app
in a browser, so I just open war/GwtApp.html
in IntelliJ and click on the little browser icon on the top-right to
let IntelliJ start a HTTP server serving the HTML file and open that in whatever browser I want. Worked perfectly.
Application size:
Performance results:
TeaVM – Java-bytecode-to-JS compiler
TeaVM
Website: http://teavm.org/
TeaVM is a project that claims to be able to emit JavaScript and Webassembly from Java bytecode. Because it doesn’t
require Java sources, it can also be used to compile from Kotlin and Scala (and probably other JVM-based languages).
To get started with TeaVM, use the Maven archetype:
$ mvn -DarchetypeCatalog=local -DarchetypeGroupId=org.teavm -DarchetypeArtifactId=teavm-maven-webapp -DarchetypeVersion=0.5.1 archetype:generate
This will create a fairly standard Java project within a directory with the name you gave when prompted,
following Maven conventions. Java sources are in src/main/java
, web resources like index.html
in src/main/webapp
.
Here’s the Java code I wrote for our little counter:
package com.athaydes; import org.teavm.jso.dom.html.HTMLButtonElement; import org.teavm.jso.dom.html.HTMLDocument; import org.teavm.jso.dom.html.HTMLElement; import java.util.function.Consumer; public class Client { public static void main( String[] args ) { HTMLDocument document = HTMLDocument.current(); HTMLElement div = document.createElement( "h2" ); div.appendChild( document.createTextNode( "TeaVM CounterApp" ) ); document.getBody().appendChild( div ); div.appendChild( new Counter().build() ); } } class Counter { private int value = 0; HTMLElement build() { HTMLDocument document = HTMLDocument.current(); HTMLButtonElement up = ( HTMLButtonElement ) document.createElement( "button" ); up.appendChild( document.createTextNode( "Increment" ) ); HTMLButtonElement down = ( HTMLButtonElement ) document.createElement( "button" ); down.appendChild( document.createTextNode( "Decrement" ) ); HTMLElement out = document.createElement( "p" ); Runnable update = () -> out.setInnerHTML( "The current count is " + value ); Consumer<Boolean> handler = ( increment ) -> { if ( increment ) { value++; } else { value--; } update.run(); }; up.listenClick( ( event ) -> handler.accept( true ) ); down.listenClick( ( event ) -> handler.accept( false ) ); update.run(); HTMLElement div = document.createElement( "div" ); div.appendChild( out ); div.appendChild( up ); div.appendChild( down ); return div; } }
Interestingly, the Java Counter component is 34-lines long, quite a bit shorter than the equivalent React.js
equivalent, which has no less than 55 lines!
To compile, run:
$ mvn package
This will compile all Java code as usual, producing .class
files in the target/classes
directory (like a normal
Java project), but it will also create the JavaScript output under target/<project-name>-<version>/teavm
,
or in my case, as I named the project mytea
, target/mytea-1-0-SNAPSHOT/teavm
.
To run the application, simply start a web server to serve the `target/mytea-1-0-SNAPSHOT/
directory.
Application size:
Performance results:
JSweet – Java-source-to-JS (and TypeScript) compiler with library ecosystem
JSweet
Website: http://www.jsweet.org/
JSweet compiles Java source code to both TypeScript and JavaScript. Java libraries can be published as
TypeScript libraries (or candy, as they call it). Unlike the other options,
the Java code can fully interop with the JS/TS code in both directions!
With JSweet, the recommended way to get started is by cloning the quick-start project from GitHub:
$ git clone https://github.com/cincheo/jsweet-quickstart.git $ cd jsweet-quickstart $ mvn generate-sources
Again, the Java project uses the standard Maven convention with Java sources in src/main/java
.
However, web resources are placed directly under the webapp
directory.
Unlike with TeaVM, the JSweet compiler does not bother generating class files at all: that’s why you can run just
mvn generate-sources
instead of the more usual mvn package
or mvn install
, to generate the JS code.
The Java code I ended up writing is almost identical to the TeaVM one _(with one notable exception: the JSweet API
chose java.util.function.Function
for click handlers instead of the expected Consumer<Event>
, so they must
return a value, making the lambda pretty awkward to write, with curly braces and a mandatory return null
at the end):
package quickstart; import def.dom.HTMLButtonElement; import def.dom.HTMLElement; import java.util.function.Consumer; import static def.dom.Globals.document; public class QuickStart { public static void main( String[] args ) { HTMLElement div = document.createElement( "h2" ); div.appendChild( document.createTextNode( "JSweet CounterApp" ) ); document.body.appendChild( div ); div.appendChild( new Counter().build() ); } } class Counter { private int value = 0; HTMLElement build() { HTMLButtonElement up = ( HTMLButtonElement ) document.createElement( "button" ); up.appendChild( document.createTextNode( "Increment" ) ); HTMLButtonElement down = ( HTMLButtonElement ) document.createElement( "button" ); down.appendChild( document.createTextNode( "Decrement" ) ); HTMLElement out = document.createElement( "p" ); Runnable update = () -> out.innerText = "The current count is " + value; Consumer<Boolean> handler = ( increment ) -> { if ( increment ) { value++; } else { value--; } update.run(); }; up.onclick = ( event ) -> { handler.accept( true ); return null; }; down.onclick = ( event ) -> { handler.accept( false ); return null; }; update.run(); HTMLElement div = document.createElement( "div" ); div.appendChild( out ); div.appendChild( up ); div.appendChild( down ); return div; } }
Application size:
Performance results:
CheerpJ – Full JVM implementation on the browser
CheerpJ
Website: https://leaningtech.com/cheerpj/
CheerpJ is definitely the craziest option we’re going to look at (it may even be the craziest project you’ll see,
period!).
It is not really a Java-to-JavaScript transpiler… it’s an actual complete Java runtime! That’s right. It can do
everything a more conventional JVM can do. Threads, file system (apparently, it emulates a file system via IndexDB),
reflection, class loading, networking… you name it.
If that’s not enough to make your head spin, then let me introduce you to the CheerpJ’s Get Started Tutorial.
It shows how you can take a compiled jar containing a Swing app (remember Swing, that multi-platform Java UI
framework for the desktop??) and run it right on the browser… without plugins.
It’s not an applet runner. It actually runs the Swing code!! In the browser!!! Without plugins!!!!
I didn’t really believe it, so I followed along with the tutorial to check this out for myself…
I’ll summarize the steps here for your convenience:
- Download a Swing app’s jar.
- Run it using your local Java just to see what it looks like.
- Install CheerpJ (you do have to manually download it and put it under your home folder, they say).
- Make sure you have
python3
installed (I know, right? Python is going to be part of your build if you want to use CheerpJ). - Run
~/cheerpj_1.3/cheerpjfy.py TextDemo.jar
. - Create an
index.html
file. Gist. - Serve the current directory with a real web server.
Now, it didn’t look like it was going to work as the loader just hanged there for a while… but after a
longer-than-what-you-would-probably-be-willing-to-wait wait, there it was!
The same Swing app I had just run with local Java, but embedded within a web page inside the browser!
Swing TextDemo running on Ubuntu
Swing TextDemo running on the browser via CheerpJ
If you have an old Swing app laying around, CheerpJ may be for you… but if you’re looking to write a new application,
you probably want to stay well away…
I tried to implement the Counter App using CheerpJ’s support for the DOM (a cheerpj-dom.jar
file is included in the
distribution)… but I ran into 2 bugs in like 10 minutes.
And they were bad enough to block me completely… not to mention that I had to wrap every single Java String into
a call to Global.JSString()
!
Hint to the CheerpJ developers: put your jars in a Maven repo – no self-respected Java developer is going to hardcode
a dependency to a local file – and no, putting it under the home directory is not ok! Also, learn the language
conventions: don’t call a Java methodset_onclick
… it’ssetOnClick
thank you.
I was ready to give up on CheerpJ, but then I remembered one of my methodology statements:
create the counter app using the most basic tools provided by the product/framework.
Well, in the case of CheerpJ, it seems the basic tool provided for UIs is the JVM itself! And the JVM offers a UI kit:
Swing!
As of Java 12, Swing is the only UI Kit distributed with the JVM… JavaFX has become a separated project. And in any
case, CheerpJ only supports Swing.
So here’s my implementation of the Counter App in pure Swing:
import javax.swing.*; import java.awt.*; import java.util.function.Consumer; public class Main implements Runnable { @Override public void run() { JFrame frame = new JFrame( "CheerpJ Demo" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 400, 80 ); frame.add( new Counter() ); frame.setVisible( true ); } public static void main( String[] args ) { SwingUtilities.invokeLater( new Main() ); } } class Counter extends JPanel { private int value; public Counter() { JButton up = new JButton( "Increment" ); JButton down = new JButton( "Decrement" ); JLabel out = new JLabel(); Runnable update = () -> out.setText( "The current count is " + value ); update.run(); Consumer<Boolean> handler = ( increment ) -> { if ( increment ) { value++; } else { value--; } update.run(); }; up.addActionListener( event -> handler.accept( true ) ); down.addActionListener( event -> handler.accept( false ) ); JPanel buttonsPanel = new JPanel(); buttonsPanel.add( up ); buttonsPanel.add( down ); JLabel title = new JLabel( "CheerpJ CounterApp (Swing)" ); setLayout( new BorderLayout() ); add( title, BorderLayout.PAGE_START ); add( buttonsPanel, BorderLayout.PAGE_END ); add( out, BorderLayout.CENTER ); } }
This will run fine on the desktop: it’s written as a completely normal Swing app!
We just need to create the totally normal jar first:
mvn package
Run with Java:
java -jar cheerpj-swing/target/cheerpj-swing-1.0-SNAPSHOT.jar
And here it is:
CounterApp running on Ubuntu
The crazy thing is that it can also run in the browser with CheerpJ.
Looking at the Network tab on the browser was fun: it kept downloading stuff, apparently there’s one JS file for each
Java’s standard library package, and CheerpJ downloads them on demand.
Application size:
Performance results:
To understand why it looks so bad, here’s the helpful diagnostics Lighthouse gives:
This is the first time I see the Avoid enourmous network payloads
diagnostic, and I am definitely going to try to
follow that advice!
Vaadin Flow – Java-source-to-JS-source, server and client-side framework
Vaadin Flow
Website: https://vaadin.com/flow
I had heard of Vaadin a long time ago as a GWT-based framework… but it seems that they recently created a simpler
product based on web components called Vaadin Flow.
I like web components, so I decided to check that out!
The starter page has several options to start a project integrating with things like Spring or CDI. I chose the most
basic option I could find, Project Base, to start simple as all I want
is a client-side only application.
I downloaded the zip, as instructed, and got the code up and running in a couple of minutes.
Once again, it was a familiar Maven project (I expected Gradle to be the most popular choice these days, but apparently
not)… there was no index.html
file in sight, which I found surprising. How exactly does the browser load the app?
Anyway, to run the app we just need to run a magic command: mvn jetty:run
, then go to http://localhost:8080
, as one
would expect, to visit the running app.
Looking at the huge POM file (139 lines for a hello world app), it seems they use the jetty-maven-plugin
for that.
So, again, I can’t use my favourite option, the IntelliJ server, to simply serve the index.html
file and start messing
around. This is a little bit too much magic to me… sure, I’m a Java developer, but come’on. I can handle a HTML file
or two.
While I was changing stuff, it looked like the server was restarting, which made me think it was doing hot reloading.
Great, I thought! But unfortunately, whatever the server was doing, it was not hot reloading my code. I tried
refreshing the page a few times but that only caused errors!! Looks like Vaadin apps don’t like page refreshes
(it was in debug mode – but still, it’s probably doing too much magic for its own good, and is now crumbling under its
own complexity).
The Java API for writing UIs, however, is excellent. Easy to use and learn, it was obviously written for Java
programmers to feel at home, which is great!
Just look at the code I wrote for the Counter app, and see if you don’t agree it’s pretty awesome:
package com.example.test; import com.vaadin.flow.component.Composite; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.Route; import java.util.function.Consumer; @Route( "" ) public class MainView extends VerticalLayout { public MainView() { H2 header = new H2( "Vaadin Flow CounterApp" ); add( header, new Counter() ); } } class Counter extends Composite<Div> { private int value; Counter() { Paragraph out = new Paragraph(); Runnable update = () -> out.setText( "The current count is " + value ); update.run(); Consumer<Boolean> handler = ( increment ) -> { if ( increment ) { value++; } else { value--; } update.run(); }; Button up = new Button( "Increment", event -> handler.accept( true ) ); Button down = new Button( "Decrement", event -> handler.accept( false ) ); getContent().add( out, up, down ); } }
To measure the performance of the app, I ran it in production mode,
which is done with this command:
$ mvn jetty:run-exploded -Pproduction-mode
The page feels quite snappy and, without any effort from me, styled nicely already.
Application size:
Notice how my little Vaadin counter needed nothing less than 312KB to run (mostly in a huge HTML file,
vaadin-flow-bundle
– the total size of the JS resources was only 46.7KB)… more than I would’ve hoped.
Performance results:
Chrome showed a warning on the console while running the app, which is a little bit concerning.
It seems Vaadin has been an early adopter of experimental features such as HTML imports, which are now being abandoned.
Too bad, but as long as they keep their product up-to-date, this shouldn’t be a big issue.
Vaadin is a fair bit slower than JSweet and TeaVM, but to be fair, this version of the counter is doing a little bit
more than the others, though not voluntarily (is there a way to remove styling and fonts from a Vaadin app??)!
Bck2Brwsr – Java-bytecode-to-JS compiler
Website: http://wiki.apidesign.org/wiki/Bck2Brwsr
Bck2Brwsr started in 2012 with the goal of creating a small Java capable to boot fast and run in 100% of modern
browsers.
Unlike CheerpJ, however, it does not promise a full JVM implementation, only a small sub-set that makes sense on the
browser, more similar to TeaVM as it also compiles from Java bytecode to JS.
There is an old looking wiki at apidesign.org which shows how to get
started with bck2brwsr
. The first suggestion on the page
was called Bck2BrwsrViaCLI, the second,
Knockout4Java.
I didnt’ really want to use Knockout.js – it went out
of favour in the JS world since around 2015 – so I
went with the CLI option.
It instructs us to run the Maven archetype to create a new project like thus:
$ mvn archetype:generate -DarchetypeGroupId=com.dukescript.archetype -DarchetypeArtifactId=knockout4j-archetype -DarchetypeVersion=0.26 -Dwebpath=client-web
Careful with the command shown on their wiki! It’s missing a back-slash on the second last line, and it points to version
0.16
, when the current version as of writing is0.26
.
Then, compile and package the app with:
$ mvn package
Warning: this will open up a popup window with a
JUnit Browser Runner
.
Didn’t expect that… moving on, it says we can run the app with something called FXBrwsr
by running:
$ mvn -f client process-classes exec:exec
This didn’t work for me. I got this error:
Exception in thread "main" java.lang.IllegalStateException: Can't find any Fn.Presenter at net.java.html.boot.BrowserBuilder.showAndWait(BrowserBuilder.java:255) at com.athaydes.Main.main(Main.java:16) [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------
Quickly googling around turned up nothing (my guess is that they haven’t tested this on Linux), so I tried the next
suggestion to run the app in a real browser:
$ mvn -f client-web clean package -DskipTests bck2brwsr:show
This gave another error:
[ERROR] Failed to execute goal on project bck2brwsr-web: Could not resolve dependencies for project com.athaydes:bck2brwsr-web:jar:1.0-SNAPSHOT: Could not find artifact com.athaydes:bck2brwsr-js:jar:bck2brwsr:1.0-SNAPSHOT -> [Help 1]
This is a classic problem with Maven: you need to install your artifacts sometimes to make them visible to certain
plugins… to fix that you normally run:
$ mvn install
Running the command to start the app again after this seemed to work. But the app didn’t do anything.
Checking the console, sure enough, there was an error:
VM69 bck2brwsr.js:6 Uncaught Cannot find com.athaydes.BrowserMain w @ VM69 bck2brwsr.js:6 v @ VM69 bck2brwsr.js:2 r @ VM69 bck2brwsr.js:2 load (async) z @ VM69 bck2brwsr.js:3 r @ VM69 bck2brwsr.js:4 w @ VM69 bck2brwsr.js:6 n.loadClass @ VM69 bck2brwsr.js:6 (anonymous) @ index.html:28
I looked around… tried to find something that could help me understand the problem. But I couldn’t…
What I could find, however, was a huge amount of XML. 931 of lines, to be precise, distributed across 4 Maven poms.
I also found the project consists of 3 Maven modules: client
, client-web
and js
.
The js
module contains a Java class with a few methods marked
native
and annotated with @JavaScriptBody
annotations containing the JavaScript code implementing the methods,
embedded into Java Strings (ugh!).
The client
module contained a bootstrapping main class as well as a DataModel
class with even more annotations,
seemingly trying to bind the data model with the view, while trying to use the functions declared in the js
module.
It referred to another class called Data
that doesn’t exist in sources… it is auto-generated when the project is
compiled. I couldn’t for the life of me figure out what is generating that class. It’s probably buried somewhere in the
nearly 1000 lines of pom.
The client-web
module contained the missing class: com.athaydes.BrowserMain
. Because that’s the module we run in the
Maven command above, I have no idea why the class wouldn’t be found by the browser.
To create the UI, I was hoping to be able to use a DOM API, as in some of the previous examples. I searched
everywhere for a way to do that, but all I found was some NetBeans APIs, and they
were absolutely not what I was looking for (check for yourself).
It seems the only way available to interact with the web page is via data-bind
attributes on the actual HTML text,
powered by, wait for it, Knockout.js!
I thought I had avoided the Knockout option, but look at the ID of the Maven archetype they showed on the
“CLI” page!
Maybe it’s also possible to do that by using the horrendous @JavaScriptBody
annotation to write JavaScript code
directly from within Java Strings (please don’t do this… for your own sanity’s sake), but I didn’t want to find out.
I decided that enough is enough. There’s nearly no docs to help me debug issues like this, and searching for answers
online is a waste of time – there are no answers.
I am sorry to leave no actual performance data about bck2brwsr
in this blog post, but I’ve spent the best part of a
day trying to do that and failed… I think it’s fair to say that, unless you have an extremely compelling reason to
do so, you should make sure to never touch anything related to this project, including
DukeScript.
You may notice several references to DukeScript throughout the
bck2brwsr
documentation and even source code.
The example shown in the DukeScript’s Get Started Page is exactly the
same as the one created by the Maven archetype I used, so the two projects seem to be closely related – though the
Dukescript founders say
bck2brwsr
is only a small part of the project, and the most experimental one, and they also support TeaVM.
Conclusion
Let’s jump straight to a summary of the results:
bck2brwsr is not included because I couldn’t get it working. If anyone points out what went wrong with my attempts,
I will update the results.
Performance
* FCP = First Contentful Paint (this value was used to break the tie between tools with the same performance score)
Size
* CheerpJ’s result was too large to be shown on scale, so it is shown here 10 times smaller than it is
Lines of Code
Lines of Code count excludes imports and comments.
Summary
Q. Performance is of the essence?
A. You should probably pick JSweet or TeaVM.
Q. Do you like a small footprint?
A. JSweet wins by a large margin, but TeaVM is also pretty good.
Q. Do you want to have the smallest code base to maintain?
A. Vaadin Flow is great for that. The other options are all very close to each other, so it probably wouldn’t
make much of a difference… they all still manage to beat React!
Q. Do you want something as close as possible to normal Java?
A. Vaadin Flow if you care mostly about UI components, TeaVM if you care about business logic.
Q. Do you want something as close as possible to the JS ecosystem, but still using Java tools?
A. JSweet is perfect for you.
Q. Do you want something that is Java, but in the browser!
A. Ha! CheerpJ actually manages to do that! It has a heavy cost, but it works as advertised.
Q. Want to try something different from Java, just not JS?
A. Elm is an accessible, functional programming language designed to create
web applications. It has great tooling, including an awesome IntelliJ Plugin, so it’s perfect for Java developers who
always wanted to go functional! Another option a little closer to home is definitely Dart.
Very similar to Java but with lots of syntactic sugar to make common tasks easy. Ant it comes with great tooling,
including a hot-reload server, and frameworks like Angular and React if you need them.
If you’re curious how the JVM alternatives compare with Dart, I added the Dart implementation to the GitHub repo.
The results: Application size: 79KB,
Performance: 100 (FCP: 1.4s),
Lines of Code: 31.
All code shown in this blog post is available on GitHub,
including the React.js and Dart implementations, for comparison with the Java implementations.