Skip to content

Browser REPLλ︎

It's hard to imagine a productive Lisp experience without a REPL (Read-Eval-Print-Loop). ClojureScript ships with builtin REPL support for Node.js, Rhino, Nashorn, and browsers.

Let's hook up a browser REPL to our project.

First it is recommended that you install rlwrap. Under OS X the easiest way is to use brew and brew install rlwrap.

Let's create a REPL script repl.clj:

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

(cljs.build.api/build "src"
  {:main 'hello-world.core
   :output-to "out/main.js"
   :verbose true})

(cljs.repl/repl (cljs.repl.browser/repl-env)
  :watch "src"
  :output-dir "out")

We build the project at least once before constructing the REPL.

REPLs are always constructed in the same way. The first argument to cljs.repl/repl is the REPL evaluation environment (Node.js, Rhino, Nashorn, browser), the subsequent arguments are the same arguments you pass to cljs.build.api/build in addition to several options that are specific to REPLs. Note that we supply a :watch option with a source directory. This conveniently starts a REPL along with an auto building process. The auto building process will write its activity to out/watch.log so you can easily tail -f out/watch.log. We also specify :output-dir so that the REPL can reuse compiled files generated by the build.

We also need to modify our source to load the browser REPL:

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

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

(enable-console-print!)

(println "Hello world!")

We create the connection with defonce. This ensures the connection is constructed only one time - we may reload this namespace during development and we don't want multiple connection instances.

Let's try it:

rlwrap java -cp cljs.jar:src clojure.main repl.clj

The first time will be somewhat slow as the REPL communication script needs to build. You will also see innocuous WARNINGs from the Google Closure Compiler that can be ignored. You should eventually see the following message:

Waiting for browser to connect ...

Point your web browser at http://localhost:9000.

You should get a REPL. (Note that the REPL will appear in your terminal, not in the browser.)

Try evaluating a simple expression like (+ 1 2).

Debug Note: If the REPL doesn't connect immediately try refreshing the browser a few times (Chrome & Firefox tend to be more stable than Safari). Note that eval will be slow in some browsers if you don't have the browser REPL tab focused. If for some reason the REPL completely hangs, just refresh the page.

Run tail -f out/watch.log in a fresh terminal to view auto build progress.

Try evaluating some expressions like (first [1 2 3]), or (doc first), (source first).

Change your src/hello_world/core.cljs source file to look like the following:

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

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

(enable-console-print!)

(println "Hello world!")

;; ADDED
(defn foo [a b]
  (+ a b))

At the REPL prompt, require your namespace by evaluating (require '[hello-world.core :as hello]). Try evaluating (hello/foo 2 3), you should get the result 5.

Change your source file so that foo uses * instead of +:

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

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

(enable-console-print!)

(println "Hello world!")

(defn foo [a b]
  (* a b)) ;; CHANGED

We can get this new definition in our REPL by appending a :reload keyword to our require statement thereby forcing a reload. Evaluate (require '[hello-world.core :as hello] :reload) and try (hello/foo 2 3) you should get 6 this time.

Lets make a mistake. Try evaluating (ffirst [1]). You should get a source mapped stack trace pointing at ClojureScript source locations not JavaScript ones. This makes debugging a lot nicer.