Getting familiar with the ClojureScript ecosystem
At the heart of the ClojureScript's ecosystem lies the compiler. In this section, we'll gain an insight into its internals: what is its underlying architecture, how does it work, and how can its functioning be tweaked in order to allow for leaner ClojureScript development?
Inside the ClojureScript compiler
The ClojureScript compiler is a piece of Clojure software packaged as a JAR along with Clojure itself, so the package is self-contained and can be manipulated easily. As such, the ClojureScript compiler requires the JVM for its operation. Currently, as ClojureScript developers have baked in the compiler, among many other things, an integration mode for Nashorn, the Java 8 embedded JavaScript engine, they recommend using the same version of the JVM. But, Java 7 is sufficient for the sole operation of the compiler.
Note
A bootstrapped version of ClojureScript, that is, one that uses pure ClojureScript for compilation, has recently been released. Cljs-bootstrap ( https://github.com/swannodette/cljs-bootstrap ), at the time of writing this, is still a work in progress, and offers worse performance than the JVM mainstream compiler. Besides, bootstrapped ClojureScript does not allow for the advanced compilation flags that its JVM counterpart offers.
At its most stripped down definition, the compiler accepts ClojureScript code, that is, mainly s-expressions obeying some Clojure subset semantics, and emits JavaScript artifacts that are passed on to the Google Closure Library ( https://developers.google.com/closure/ ) in order to get it "polished".
The Google Closure Library is a set of JavaScript optimizing tools open sourced by Google, which it uses to support the development of its JavaScript-rich applications, such as Gmail or Maps. Using this library has the following benefits:
- It abstracts away the effort of managing inconsistencies across the many JavaScript engines of the market.
- It takes advantage of the Google Closure's complete program optimization with features such as JavaScript minification or dead code elimination.
- It exposes the namespace functionality, which is otherwise unavailable in Vanilla JavaScript. Actually, a ClojureScript namespace maps to a Google Closure namespace.
Now, let's see the ClojureScript compiler in action. To understand its fundamentals, we won't use any build-automation tooling such as Leiningen for the moment, though we'll for sure need it to construct our tooling later on. Let's begin by downloading the latest release of the compiler (1.7.48 as of the time of writing):
Note
Throughout this book, we assume that you are working on a POSIX compatible system, such as Linux or Mac OS X.
Create a new directory for your first project, label it cljs_first_project
, and then download into it the compiler JAR:
mkdir cljs_first_project; cd cljs_first_project wget \ https://github.com/clojure/clojurescript/releases/download/r1.7.48/cljs.jar
Once the compiler JAR is downloaded, you'll need to create a source directory and the path for your first namespace inside the project folder:
mkdir -p src/cljs_first_project; cd src/cljs_first_project
Now it's time to write your first ClojureScript namespace, which must conform to the path we're currently in (note that you should replace the underscores with dashes in the directory name). Type the following code in a file named core.cljs
:
(ns cljs-first-project.core) (js/alert "Hello world!")
This code just declares a namespace and the only operation that our program does is showing an alert popup with the familiar "Hello World!" greeting.
Now with the ClojureScript compiler being a Clojure library, we must write some Clojure code in order to trigger the building of the ClojureScript code we just wrote. Create a Clojure
file at the root of our project (at the same level as the /src
directory) and label it build.clj
with the following Clojure code in it:
(require 'cljs.build.api) (cljs.build.api/build "src" {:output-to "out/main.js"})
Building ClojureScript is a matter of requiring the cljs.build.api
namespace and then launching the build
function that takes two arguments. The first argument is where to look for the ClojureScript source code to build, that is the src
directory in our case, and second one is where to output the result JavaScript; out/main.js
as far as this example is concerned.
With this helper Clojure program under our belt, we can launch the ClojureScript compilation process. It is about launching the embedded Clojure from the JAR we downloaded and passing to it the build program we just wrote. When we run Clojure this way, we make sure that the ClojureScript facilities are loaded, especially the cljs.build.api
namespace. To be able to achieve this, we'll have to add the JAR we downloaded as well as the src
directory to the classpath when we invoke Clojure with the help of the java
command:
java -cp cljs.jar:src clojure.main ./src/build.clj
After you've run this command, you'll notice that an out
directory containing our target main.js
file has just been created. In order to launch the output JavaScript artifact, we'll need an HTML page, which when loaded into our browser will greet us with a popup. On our HTML page, we must surely load the generated main.js
file, but we must also bootstrap the Google Closure Library by loading the out/goog/base.js
script.
Also, note that the main.js
file only contains a description of the different namespaces' dependencies managed by the Google Closure Library and no logic of execution. So, we must explicitly set an entry point to our program by telling the Google Closure Library to require a namespace to start with, and that's our cljs_first_project.core
namespace (note how the dashes got transformed to underscores in the HTML page). Here's what the HTML page, which we'll store under the greet.html
file, at the root folder of your project, looks like:
<html> <body> <script type="text/javascript" src="out/goog/base.js"></script> <script type="text/javascript" src="out/main.js"></script> <script type="text/javascript"> goog.require("cljs_first_project.core"); </script> </body> </html>
Accessing this page from your browser greets you with a JavaScript alert
popup. Congratulations! You've successfully written and compiled your first ClojureScript program!
There are more advanced ways to work with the architecture of the build process. For example, to get rid of all the goog
requires in your HTML page, you can tell the compiler in your Clojure build program which namespace should be set as an entry point, as follows:
(require 'cljs.build.api) (cljs.build.api/build "src" {:main 'cljs-first-project.core :output-to "out/main.js"})
This lets you strip the necessary script declarations in your greet.html
page down to the following:
<html> <body> <script type="text/javascript" src="out/main.js"></script> </body> </html>
Another way to optimize the build process is to set the auto-build of your ClojureScript code on. The compiler can be triggered to be on the watch mode, thus recompiling the output JavaScript as soon as it observes any changes in the src
directory. Set your Clojure build program to use the watch
function instead of build
, as shown here:
(require 'cljs.build.api) (cljs.build.api/watch "src" {:main 'first-cljs-project.core :output-to "out/main.js"})
We've taken quite a deep dive inside the compiler. But, to be able to keep the promise of bringing agile development to JavaScript land, ClojureScript ought to offer a REPL to its users, as any decent lisp would do. Let's discover how ClojureScript addresses this matter in the next section.
Working with the ClojureScript REPL
ClojureScript comes bundled with REPL support for the browser, Node.js, Rhino, and Nashorn. The REPL functionality can be triggered through a call to the repl
function from the cljs.repl
namespace present in the ClojureScript JAR. Just as we did for the building process, we must create a REPL launching Clojure program. In this program, we begin by building our project and then launch the interactive REPL session. Create a repl.clj
Clojure program containing the following listing:
(require 'cljs.repl) (require 'cljs.build.api) (require 'cljs.repl.browser) (cljs.build.api/build "src" {:main 'cljs-first-project.core :output-to "out/main.js" :verbose true}) (cljs.repl/repl (cljs.repl.browser/repl-env) :watch "src" :output-dir "out")
Here, we'll build a REPL with evaluation on the browser, as we've used the cljs.repl.browser
namespace. Note how we set the :watch
option, so our REPL automatically gets fresh versions of the output JavaScript, providing for interactive ClojureScript code evaluation. The :output-dir
directive tells the REPL where to look for generated artifacts so that they can be loaded into the relevant evaluation environment. As the interactive evaluation session goes, output of the compilation goes into out/watch.log
, so we can follow along what's going on while the code interacts with the REPL.
Now, you must set a connection to the REPL inside your ClojureScript program, core.cljs
. Once built, the resulting JavaScript program will stay in tune with the REPL environment, by pushing to the browser any changes made to the ClojureScript source:
(ns cljs-first-project.core (:require [clojure.browser.repl :as repl])) (defonce conn (repl/connect "http://localhost:9000/repl")) (js/alert "Hello world!")
The connection has been defined with the defonce
parameter to make sure that the same connection is used across the many builds that will occur while the user interacts with the REPL and triggers a new build per evaluation.
Now, launch the REPL, preferably using the rlwrap
command, so the display on the terminal is properly rendered:
rlwrap java -cp cljs.jar:src clojure.main repl.clj
Be patient while the first build, involving the construction of the connection to the REPL, is completed. When it completes, you'll see the Waiting for browser to connect message in your terminal. Once you see this message, point your browser to the HTML page we prepared before (greet.html
) now through http://localhost:9000/greet.html
. Accept the first greeting popup and go back to your terminal; you'll see the following output:
Watch compilation log available at: out/watch.log To quit, type: :cljs/quit cljs.user=>
Type another greeting to see if it gets automatically executed in your browser. Type in your REPL the following:
cljs.user=> (js/alert "Hello World From REPL!")
You'll see new greetings from the REPL interactively popping up without hitting refresh on your browser:
So far, were able to come up with a ClojureScript REPL that empowered us to interact with the browser. But, we are far from having a full-fledged development environment yet; the terminal through which we are coding is quite limited, and we lack several essential features such as code completion, syntax coloring, source code exploration, refactoring, or version control management to name a few. We need a much more complete and fluid coding experience and that's what we will strive to achieve in the next sections. We'll begin by exploring the two most promising facilities that permit text editors or integrated development environments to take advantage from the ClojureScript REPL. Then, we'll showcase two Emacs setups based on those facilities-one based on CIDER and another one backed byinf-clojure.