3 ClojureScript REPLs

ClojureScript Built-in REPLs

ClojureScript is a variant of the Clojure language, which compiles (or "transpiles") to JavaScript code. That way you can write Clojure code, but run it anywhere JavaScript is available. It turns out that's quite a lot of places.

When it comes to ClojureScript REPLs the story becomes a bit more involved. The ClojureScript compiler is written in Clojure, so it lives in the same environment Clojure lives in: the JVM (Java Virtual Machine).

To evaluate compiled ClojureScript code, which has now turned into JavaScript, we need a separate JavaScript environment.

So a ClojureScript REPL consists of two parts: the first part is written in Clojure, it handles the REPL UI, and takes care of compiling ClojureScript to JavaScript. This JavaScript code then gets handed over to the second part, the JavaScript environment, which evaluates the code and hands back the result.

ClojureScript comes bundled with support for three JavaScript environments: Rhino, Node.js, and the browser.

Rhino

Rhino is a JavaScript engine written in Java. The project was started by Netscape in 1997, and is now managed by Mozilla. It comes bundled with Java (JDK or JRE), so if you have Java you should have Rhino available.

Assuming your project includes ClojureScript, getting a Rhino-based REPL going is as easy as

(require '[cljs.repl :as repl])
(require '[cljs.repl.rhino :as rhino])

(repl/repl (rhino/repl-env))

Notice how there are clearly two parts, the repl/repl function takes a JavaScript environment as its argument.

Rhino is a good option if you want a quick ClojureScript REPL to experiment with, but it has its limitations. Since it's not tied to a browser there is no DOM, and although people are still working on Rhino, don't expect your favorite HTML5 or ES6 features to be available.

One fun thing you get with Rhino (a gimmick, really), is Java interop! That's right, you can use all your favorite Java classes straight from ClojureScript.

cljs.user> (def f (js/java.io.File. "/etc/hosts"))
#'cljs.user/f
cljs.user> (.exists f)
true

Node.js

The most popular JavaScript engine outside the browser is without a doubt Node.js. To get a Node.js-based REPL going you have a couple of options. There's a leiningen plugin called cljs-noderepl, and then there is Lumo, a standalone Node.js based REPL which will be covered in the section on Bootstrapped ClojureScript REPLs.

It's easy enough to do it youreslf though. To run your own node-based REPL simply swap out the Rhino env from the previous section with the Node.js repl-env.

(require 'cljs.repl)
(require 'cljs.repl.node)

(cljs.repl/repl (cljs.repl.node/repl-env))

Assuming you have a recent enough (>= 0.12.0) Node.js on your system, this will spin up node in the background and drop into a REPL. There's still no DOM since we're not targeting a browser, but you should get significantly better performance, and you have access to the Node APIs, so you can do things like access the network or work with files.

If your ClojureScript project targets Node.js, you will want a REPL that has your project code loaded. The ClojureScript Quick Start page provides more info.

Browser connected REPL

ClojureScript also comes with a REPL environment that uses a running web browser to evaluate expressions typed in to the REPL. This is especially useful because it allows you to inspect the state of, and interact with, a running web app.

Just like with Rhino or Node.js there's a repl-env function that you can plug into cljs.repl/repl, this particular repl-env is defined in cljs.repl.browser.

(require 'cljs.repl)
(require 'cljs.repl.browser)

(cljs.repl/repl (cljs.repl.browser/repl-env))

This time the REPL won't immediately pop up though, instead it spins up a web server and waits for the browser app to connect back to it, so somewhere in the ClojureScript code for your web app you'll have to initiate this connection.

(ns repl-test.core
  (:require [clojure.browser.repl :as repl]))

(defonce conn
  (repl/connect "http://localhost:9000/repl"))

You'll need a build script (or use something like lein-cljsbuild) to compile this code, and an index.html to deliver it to the browser. The relevant section in the ClojureScript Quick Start guide goes into greater depth about how to do this, including stand-alone examples.

In HTTP interactions are always initiated by the client. To enable the REPL (the server) to send data back to the browser (the client), it uses a technique called "long-polling". With this technique the client will initiate an HTTP request, and wait as long as necessary for the server to respond. This effectively reverses the role of client and server.

It's a crude mechanism, but has the benefit that it works across browsers. Newer ClojureScript REPLs like Weasel and Figwheel use websockets which are ideal for this use case, but only supported in more recent browsers.

The HTTP server that the REPL uses is often not the one serving up your application, and so it will have a different domain or port. For security reasons browsers will prevent these two from interacting with each other, this is called the same-origin policy. To circumvent this restriction cljs.repl.browser uses CrossPageChannel, a part of the Google Closure Library. CrossPageChannel allows scripts from different origins to communicate via an iframe.

Piggieback

If you haven't read the section about nREPL yet you better go and read that first.

Piggieback forms the bridge between ClojureScript and nREPL. It's an nREPL middleware which intercepts "eval" messages, and routes them to a ClojureScript REPL. Most editors and IDEs for Clojure are based on nREPL, so you'll need Piggieback to use them with ClojureScript.

There are two steps to using Piggieback. First make sure the Piggieback middleware is added to the middleware stack when starting the nREPL server. Now when you start nREPL Piggieback will be dormant, waiting to be woken up.

Calling (cider.piggieback/cljs-repl (repl-env)) from the REPL spurs Piggieback into action. From now on any code that's being evaluated will be passed to the given ClojureScript REPL env.

How to add the nREPL middleware depends on how you're starting nREPL.

In Leiningen the project map takes a :repl-options key where you can configure middleware that will be added to the default stack when running lein repl.

(defproject cljsrepl "0.1.0"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.9.183"]
                 [cider/piggieback "0.4.0"]]
  :repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]})

In Boot boot.repl/*default-middleware* can be modified to insert middleware. Here's a sample build.boot. You can also configure this for all projects at once in \~/boot/profile.boot.

(require 'boot.repl)

(swap! boot.repl/*default-dependencies*
       concat '[[cider/piggieback "0.4.0"]])

(swap! boot.repl/*default-middleware*
       conj 'cider.piggieback/wrap-cljs-repl)

If you're starting your own nREPL server then pass the middleware to the default-handler

(require '[nrepl.server :as nrepl]
         '[cider.piggieback :as piggieback])

(nrepl/start-server :handler (server/default-handler piggieback/wrap-cljs-repl))

Now start your REPL (whichever way you choose), and evaluate (cider.piggieback/cljs-repl), passing in the ClojureScript repl-env you would like to use. This signals to Piggieback that it should start intercepting and redirecting "eval" type messages.

To get your regular Clojure REPL back type :cljs/quit.

$ boot repl
nREPL server started on port 44543 on host 127.0.0.1 - nrepl://127.0.0.1:44543
REPL-y 0.3.7, nREPL 0.6.0
Clojure 1.7.0
OpenJDK 64-Bit Server VM 1.8.0_91-8u91-b14-3ubuntu1~16.04.1-b14
        Exit: Control+D or (exit) or (quit)
    Commands: (user/help)
        Docs: (doc function-name-here)
              (find-doc "part-of-name-here")
Find by Name: (find-name "part-of-name-here")
      Source: (source function-name-here)
     Javadoc: (javadoc java-object-or-class-here)
    Examples from clojuredocs.org: [clojuredocs or cdoc]
              (user/clojuredocs name-here)
              (user/clojuredocs "ns-here" "name-here")
boot.user=> (require 'cider.piggieback)
nil
boot.user=> (require '[cljs.repl.node])
nil
boot.user=> (cider.piggieback/cljs-repl (cljs.repl.node/repl-env))
ClojureScript Node.js REPL server listening on 58146
To quit, type: :cljs/quit
nil
cljs.user=>

cljs.user=> (+ 1 1)
2
cljs.user=> :cljs/quit
nil
boot.user=>

Austin

Austin (apparently named after a sci-fi character from the 70's) is an alternative to the built-in browser REPL, trying to improve upon it in several ways. Since the project came out some other alternatives have come onto the scene, notably Weasel and Figwheel, and unless you have specific reaons to choose Austin for your REPL needs you're probably better off with something else. Nevertheless Austin introduced some important ideas, which are worth looking into.

Browser connected REPLs were introduced as a way to interact with the application running in the browser. It can however also be useful to have a independent REPL that still has access to all your project's namespaces, and that runs in an environment with a DOM, but without booting the app.

This kind of "project REPL" is good for quickly trying stuff out, playing around with libraries, or running tests.

The killer feature of Austin is making it easy to start such a Project REPL.

Calling (cemerick.austin.repls/exec) will spin up a PhantomJS (or compatible) "headless" browser in the background, and connect to that. PhantomJS uses WebKit, the browser engine used by Safari and (before the fork) Chrome, so it has a full DOM available, but it's headless, so there is no browser window.

Austin can run multiple REPLs in parallel, and will provide specific entry point URLs for each. This means you don't have to manually add client code to connect back to the server.

There's a screencast by Chas Emerick, the Author of Austin and Piggieback that shows off more of its features. Austin still uses the same long-polling and CrossPageChannel based approach that cljs.repl.browser uses.

Weasel

Weasel was the first to replace the long-polling approach with WebSockets. While this prevents it from being used in pre-websocket browsers, it does open up several JavaScript environments which do have websockets, but don't have a DOM, prohibiting the use of CrossPageChannel's iframe hack.

Using Weasel is very similar to using other ClojureScript REPL, it's just another repl-env that's made available to you. The Weasel README heavily hints at using it with Piggieback, but if you don't need nREPL you can use it with the standard cljs-repl just the same.

Just like with the built-in browser connected REPL, there's a server and a client part.

Server (Clojure)

(cljs.repl/repl (weasel.repl.websocket/repl-env :ip "0.0.0.0" :port 9001))

Client (ClojureScript)

(ns main
  (:require [weasel.repl :as repl]))

(when-not (repl/alive?)
  (repl/connect "ws://localhost:9001"))

Figwheel

Figwheel is a Leiningen plugin that automatically pushes code changes to the browser, so you get instant feedback. Figwheel also ships with its own browser connected REPL, making it a great one stop shop for ClojureScript development.

Considering the amount of work it takes care of you might think it's complicated to set up, but the opposite is true. Figwheel is incredibly easy to use, and has really made it easier for people to get an interactive ClojureScript environment up and running.

The easiest way to use Figwheel is through its leiningen plugin, just add the latest version to your Leiningen's plugin vector

(defproject ,,,
  :plugins [[lein-figwheel "0.5.4-7"]])

And start it with lein figwheel. This will do an initial compilation of your ClojureScript code, start a browser connected REPL, and it will even open the browser for you so you don't have to figure out what URL to connect to.

Before you do that you'll need to configure a ClojureScript build though, and add :figwheel true to the build configuration. Check out the Figwheel README for an example, or if you're starting with a new project use a pre-existing template, e.g. lein new figwheel my-project or lein new chestnut my-project.

Using Figwheel with nREPL is a bit more involved, if you need nREPL support, e.g. for using Figwheel with CIDER, I can recommend the Chestnut template. For the full low-down check out the Figwheel wiki page on using it with nREPL.

Figwheel is Piggieback-aware, meaning if you start Figwheel from an nREPL based REPL (using the figwheel-sidecar library), and the piggieback middleware is loaded, then Figwheel will automatically do the right thing.

Figwheel uses WebSockets just like Weasel.

cljs-nashorn

Java comes bundled not with one, but with two JavaScript environments. We already covered the first one, Rhino. The other one is called Nashorn (German for "Rhino") and is said to be a faster, more modern implementation.

While ClojureScript comes with Rhino support out of the box, for Nashorn you need a third-party implementation.

The cljs-nashorn README has all the info you need. You can either use the Leiningen plugin, or manually set up the Nashorn environment and plug it into cljs.repl/repl.

Further Reading

The ClojureScript wiki has several interesting pages about its REPLs

  • Quick Start

    A must read for every serius ClojureScript developer. Teaches you how to use the compiler and built-in REPLs from the ground up.

  • The REPL and Evaluation Environments

    Explains the IJavaScriptEnv Protocol, and has a full example of using cljs.repl.browser, as well as explaining several of its implementation details.

  • Custom REPLS

    Explains several things to be aware of when developing ClojureScript REPLs.