Compiling ClojureScript Projects Without the JVM
21 Feb 2017I have dreamed about being able to compile a ClojureScript project without installing Java ever since coming to know that ClojureScript can compile itself. While projects like Planck and Lumo can either run arbitrary ClojureScript forms at the REPL or run ClojureScript scripts, none have actually supported flat out compiling an entire ClojureScript project. Until now.
Wait... what?!
That's right. Starting with version 1.2.0, Lumo
can compile ClojureScript projects, just like the regular JVM-based ClojureScript
compiler. And you know what? Thanks to
google-closure-compiler-js
,
the generated JavaScript can be optimized
and benefit from dead-code elimination too! This is a huge step forward in being
able to compile ClojureScript source code ahead of time into optimized JavaScript
bundles using nothing but Lumo and Node.js.
Making this work meant porting the JVM-based code in the ClojureScript compiler to work under Lumo and the Node.js platform. While most of it has been generally straightforward, changing the synchronous JVM build API to the self-hosted ClojureScript asynchronous API has represented the most amount of work so far.
Compiling ClojureScript projects with Lumo requires no external dependencies (such as the Google Closure Library) apart from those required by the projects themselves, as all the necessary compiler dependencies are bundled within the single Lumo executable.
Go ahead and install the latest
Lumo release to try this feature out. Make sure you get the 1.2.0
release 1.
Example
Take for example this small section
of the ClojureScript Quick Start guide. By simply changing cljs.build.api
to
lumo.build.api
, we can start compiling the Quick Start guide sample project without
the JVM, today. Also, let's use advanced optimizations right away so we can see the
JavaScript version of the Google Closure Compiler in action. This is what our build.cljs
file will look like:
(require '[lumo.build.api :as b])
(b/build "src"
{:main 'hello-world.core
:output-to "main.js"
:optimizations :advanced
:target :nodejs})
Let's now run the compilation script with Lumo. Don't forget to add the src
folder
to the classpath so that Lumo knows where to find the project. This can be done with
the -c
/--classpath
command line option (hint: run lumo -h
for all the
available options).
$ lumo --classpath src build.cljs
After waiting a few moments, we should be able to run our newly compiled project
with node main.js
. How cool is that?!
Caveats
This new feature isn't short of tradeoffs. The most obvious, and also the harder to solve in the short term are described below.
ClojureScript version lock-in
Lumo ships with its own version of the ClojureScript compiler. This means that, for now, it is not possible to compile ClojureScript projects against versions of ClojureScript different than the one that is bundled within Lumo. While this caveat may be possible to circumvent in the future, it is what allows us to not require any external dependencies when compiling ClojureScript projects with Lumo.
Google Closure Compiler JS
The Google Closure Compiler JS is a JavaScript port of the Java version, generated using GWT. That makes it take longer to optimize code when compared to the Java version. Furthermore, it also ships with fewer features than its Java counterpart, both because not every feature included in the Java version is portable to JavaScript, and also because it is a fairly new project, only announced in late August 2016.
Conclusion
This Lumo release represents a lot. To my knowledge, there exists no other effort to compile and optimize ClojureScript projects with the Google Closure Compiler without requiring a JVM installation. But we have only began to scratch the surface, and there's an equally large amount of work that remains to be done in order to achieve feature parity with the current ClojureScript compiler on the JVM.
This feature of Lumo should be considered pre-alpha, and we will continue to improve it over the coming releases. I decided to release it nevertheless, with the objective of gathering initial feedback and hopefully attracting a few new contributors to Lumo. Please report issues, and if you want to help, do get in touch (e.g. on Twitter)!
As a final remark, no JVMs were spawned during the process of writing this blog post.
brew install --HEAD lumo
.
*Thanks to Mike Fikes and David Nolen for reading a draft of this post.*