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
-
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 usingcljs.repl.browser
, as well as explaining several of its implementation details.-
Explains several things to be aware of when developing ClojureScript REPLs.