Live coding ClojureScript on top of nREPL with Piggieback and Weasel
Network REPL (nREPL) ( https://github.com/clojure/tools.nrepl ) is a Clojure library designed primarily to offer remote Clojure code evaluation. It follows a client-server architecture in which the server exposes the REPL functionality by responding to client code evaluation queries. nREPL has been created to offer a means to the makers of development tools to connect and explore running Clojure environments in a way that is agnostic to the platform these tools may be running on.
Many prominent Clojure development tools rely on nREPL to implement their functionality. In fact, CIDER on Emacs or Cursive on Intellij IDEA, the most used tools for developing Clojure at the time of writing this, both rely on nREPL (CIDER relying more on nREPL for code introspection than Cursive).
Naturally, ClojureScript also benefits from nREPL.
Piggieback ( https://github.com/cemerick/piggieback ) is an nREPL middleware that seizes this opportunity. Piggieback hooks into nREPL and changes its operation so it can evaluate and load ClojureScript code, while being understandable to the vast majority of existing Clojure tooling. In the next section, we'll take a closer look at Piggieback.
Working with Piggieback
Piggieback changes the nREPL behavior to turn it into a ClojureScript remote evaluation environment. It does this by functioning as a middleware layer on top of nREPL. Let's see how to do this using Leiningen.
Note
Leiningen, the Clojure build tool, can be downloaded from http://leiningen.org/ or https://github.com/technomancy/leiningen . You can also find detailed instructions and documentation on how to install and get started with Leiningen on both the sites. We'll be using Leiningen a lot in this book, so you'll definitely want to install it.
We'll use this project management tool to create a new Clojure project:
lein new piggieback_project
Then, we'll have to modify the project.clj
file (where project-specific configuration is kept) in order to turn our project into a ClojureScript one, adding ClojureScript as a dependency:
(defproject piggieback_project "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"] [org.clojure/clojurescript "1.8.51"]])7.228"]])
Now, it is time to add Piggieback into the mix. Add its dependencies and its middleware to the project.clj
file:
(defproject piggieback_project "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"] [org.clojure/clojurescript "1.7.228"]] :profiles {:dev {:dependencies [[com.cemerick/piggieback"0.2.1"] [org.clojure/tools.nrepl"0.2.10"]] :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}}})
Piggieback is just an entry point to ClojureScript REPLs. Once we hook into our nREPL, we can operate in the same manner as we did when we were working with the REPL bundled with the compiler. Once again, we'll need to prepare a ClojureScript namespace that can define the connection to the REPL, and an HTML page that we can load into our browser. Create a core.cljs
file under the src/piggieback_project
folder:
(ns piggieback-project.core (:require [clojure.browser.repl :as repl])) (defonce conn (repl/connect "http://localhost:9000/repl"))
Next, copy the greet.html
file we wrote previously into the root of your new project.
We can now start an nREPL session. Type the following at the root of your project:
lein repl
In order to launch interactive code evaluation and loading in the browser, we must use some ClojureScript namespaces as to be able to hook Piggieback into our running nREPL session. We'll need to build the ClojureScript program we wrote at least once, so first we'll have to load the compiled JavaScript code to connect to the nREPL. Issue the following commands at the running REPL prompt:
user=> (require 'cljs.build.api) user=> (cljs.build.api/build "src" #_=> {:main 'piggieback-project.core #_=> :output-to "out/main.js" #_=> :verbose true}) user=> (require 'cljs.repl.browser)
We are now ready to hand over the code evaluation responsibility to Piggieback. Type the following:
user=> (cemerick.piggieback/cljs-repl (cljs.repl.browser/repl-
env))
You'll see that a JavaScript compilation process has been launched. It is our core.cljs
file being compiled, and constructing a connection to the REPL. This will be our nREPL session—accessed through the JavaScript artifacts loaded via the greet.html
page.
Once this operation has finished, you'll get the Waiting for browser to connect message that we previously encountered during our interaction with the REPL built by the ClojureScript compiler. As soon as you point your browser to http://localhost:9000/greet.html
, you'll notice that the prompt has changed; it now shows the following:
cljs.user=>_
This means that the nREPL session has started accepting code to be compiled. The compiled JavaScript will automatically be evaluated on the connected browser. Try to generate a Hello World! popup from the browser from Piggieback this time:
cljs.user=> (js/alert "Hello World from Piggieback!")
And your browser says it with a popup, from your nREPL/Piggieback
session.
We've now seen how we can change the nREPL's behavior so that it is turned into an entry point to the ClojureScript REPL. Code passed to the REPL executed on into the JavaScript environment-the browser in our context-via JavaScript files that are regenerated after each and every operation.
We can make this workflow even leaner by hot loading the JavaScript artifacts into the browser with websockets. This is what we will see in the next section, with the Weasel
library.
Setting up Weasel with PiggieBack for browser live coding
Weasel
(
https://github.com/tomjakubowski/weasel
) sets a up realtime communication channel using a websocket between a ClojureScript REPL and the JavaScript evaluation environment. The authors of this library say that choosing websockets as a means for delivering compiled JavaScript to runtime environments made it possible for them to profit from a simple and reliable transport. It also empowered them to reach a much wider range of JavaScript engines, especially those that don't properly support, the <iframe>
tag (the main technology behind the vanilla ClojureScript browser REPL). Let's now add Weasel on top of the Piggieback-enabled nREPL environment that we've set up in the previous sections.
Let's first modify the piggieback_project
file we worked on earlier to support Weasel
by adding its dependency to the project.clj
file:
(defproject piggieback_project "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.7.228"]
[weasel "0.7.0" :exclusions
[org.clojure/clojurescript]]]
:profiles {:dev {:dependencies
[[com.cemerick/piggieback"0.2.1"]
[org.clojure/tools.nrepl "0.2.10"]]
:repl-options {:nrepl-middleware
[cemerick.piggieback/wrap- cljs-repl]}}})
Let's modify our ClojureScript code so that it can connect to the websocket opened by Weasel
:
(ns piggieback-project.core (:require [weasel.repl :as repl])) (when-not (repl/alive?) (repl/connect "ws://localhost:9001"))
Just like we did in the previous section, we'll need to connect to our nREPL and plug Piggieback in on top of it, but this time, we'll run it with a Weasel browser. After making sure that you are at the root of the project folder, type in a terminal:
lein repl
First, let's compile our ClojureScript source so we have our JavaScript ready to connect to the format as code websocket. Run the following from your nREPL session:
user=> (require 'cljs.build.api) user=> (cljs.build.api/build "src" #_=> {:main 'piggieback-project.core #_=> :output-to "out/main.js" #_=> :verbose true})
Next, we require the Weasel REPL namespace:
user=> (require 'weasel.repl.websocket)
We hook Piggieback onto the current session and ask it to use the Weasel
websocket as the REPL environment (we also set the IP address and the port we want our websocket to be listening to):
user=> (cemerick.piggieback/cljs-repl #_=> (weasel.repl.websocket/repl-env :ip "0.0.0.0" :port 9001))
After Piggieback is launched, Weasel
shows a message to inform us that it is awaiting incoming connections:
<< waiting for client to connect ...
Open the greet.html
file in your browser (you need to open it from the files manager on your OS, as we are not serving it over HTTP this time!). As soon as the file opens, you see the following message in your nREPL session:
<< waiting for client to connect ... connected! >>
The prompt in your nREPL session should change to the following:
cljs.user=> _
As usual, let's salute the world and see if our greeting pops up in the browser:
cljs.user=> (js/alert "Hello World from Weasel!")
Your browser should now emit an alert with a greeting message on it.
Until this point, we have been able to put together a powerful environment for developing ClojureScript. Thanks to Piggieback being hooked on nREPL, we can benefit from the integrated development tools that already exist for Clojure. Before we go too much further on this subject, let's see how we can write ClojureScript programs that target other JavaScript environments. In the following section, we'll focus on setting up a Piggieback powered nREPL that evaluates code on the Node.js platform.
ClojureScript REPLs on Node.js with Piggieback
Setting up Node.js REPLs is simpler than targeting the browser. You don't need to set up connections from the REPL process to the browser with the help of some vanilla JavaScript. All you have to do is make sure that the source is compiled, and set the REPL target to Node.js, so that the compiled output is handed over to that evaluation environment for running.
First, make sure that Node.js is installed. Under piggieback_project
, change the core.cljs
file so that it looks like the following:
(ns piggieback-project.core (:require [cljs.nodejs :as nodejs])) (nodejs/enable-util-print!) (defn -main [& args] (println "Hello world from Node.js!")) (set! *main-cli-fn* -main)
Launch an nREPL session for your project by typing the following:
lein repl
We then require the namespaces necessary for the launch of our Node.js REPL:
user=>(require 'cljs.build.api) user=> (require 'cljs.repl.node)
Now, launch the first build of our ClojureScript core.cljs
source:
user=> (cljs.build.api/build "src" #_=> {:main 'piggieback-project.core #_=> :output-to "out/main.js" #_=> :verbose true})
We can now hook a Piggieback REPL into this running nREPL session. Issue the following command:
(cemerick.piggieback/cljs-repl (cljs.repl.node/repl-env))
The nREPL session responds with a message saying that a Node.js REPL has been launched:
ClojureScript Node.js REPL server listening on 49449
And as usual, the prompt has been changed so as to notify us that we have successfully launched a ClojureScript REPL on top of our nREPL session:
cljs.user=> _
At this point, we can implement a program similar to the Node.js "Hello World," a little HTTP server that greets the browser that queries it. Type the following in your, now, ClojureScript-enabled nREPL session (we'll elaborate more on the syntax later on):
cljs.user=> (def http (js/require "http")) cljs.user=> (.listen (.createServer http #_=> (fn [req res] #_=> (do #_=> (.writeHead res #_=> 200 #_=> (js-obj #_=> "Content-Type" "text/plain")) #_=> (.end res #_=> "Hello World from Node.js http server!")))) #_=> 1337 #_=> "127.0.0.1")
If you navigate your browser to http://127.0.0.1:1337
, you should see the greeting from your Node.js server.
In the next section, we'll elaborate on a new contender in the realm of interactive ClojureScript coding environments: Figwheel, the new kid on the block that gets you to a ClojureScript browser REPL quickly.