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:
The first time will be somewhat slow as the REPL communication script
needs to build. You will also see innocuous WARNING
s from the Google
Closure Compiler that can be ignored. You should eventually see the
following message:
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.