Clojurescript Boot Live-Reload Weasel Vim-Fireplace Piggieback
So many keywords there's no room for the rest
My long-standing complaint about Clojurescript is that it’s a pain to get running. People who prefer Leiningen seem to have gotten a handle on this with figwheel; however, those of us who have jumped on the Boot train (it’s newer and therefore better!) still have to do some assembly to get the live-reloading auto-compiling repl setup that’s become the standard for Clojurescript development.
Like many problems in the Clojure universe, this is less a problem of technology and more a problem of approachable documentation. With just a little bit of configuration and some understanding, you too can assemble that perfect Clojurescript dev setup using Boot.
If you’ve never used boot before, here are a few points that you’ll be expected to know:
- The
build.boot
file is where your project dependency and build configuration lives. - Run
boot <taskname>
from a command line to run a task - You can choose where to put your source files and resource files; I put them in
src/
andresources/
respectively.
You should be able to bumble through with that plus some Googling.
Step 1: Getting your dependencies in order
The basic tools you’ll need are boot-cljs,
boot-cljs-repl, and
boot-reload. Here’s the boot environment
laid out for your build.boot
:
(set-env!
:source-paths #{"src/clj" "src/cljs"}
:resource-paths #{"resources"}
:dependencies '[[org.clojure/clojure "1.7.X" :scope "provided"]
[org.clojure/clojurescript "1.7.X"]
[adzerk/boot-cljs "1.7.X" :scope "test"]
[adzerk/boot-cljs-repl "X.X.X" :scope "test"]
[adzerk/boot-reload "X.X.X" :scope "test"]])
I mangled all the dependencies with X
because I refuse to be the one held responsible
when you don’t use the latest of everything. Dependency version issues are a real thing for this
operation, trust me – it’s the price we pay for having interchangeable parts.
We’re still missing some requirements – boot-cljs-repl has a few dependencies
that you’ll need to add yourself. However, If you try to run the cljs-repl
task
without those dependencies, it should print out the deps you need so you can paste
those in. Let’s do that. Put this line in your build.boot
under the set-env!
call:
(require '[adzerk.boot-cljs-repl :refer :all])
On the command line, run $ boot cljs-repl
. Somewhere in the ensuing error, you
should see a message such as the following:
You are missing necessary dependencies for boot-cljs-repl.
Please add the following dependencies to your project:
[com.cemerick/piggieback "X.X.X" :scope "test"]
[weasel "X.X.X" :scope "test"]
[org.clojure/tools.nrepl "X.X.X" :scope "test"]
You can just copy and paste those lines into your dependencies. Your build.boot
should now look like this:
(set-env!
:source-paths #{"src/clj" "src/cljs"}
:resource-paths #{"resources"}
:dependencies '[[org.clojure/clojure "1.7.X" :scope "provided"]
[org.clojure/clojurescript "1.7.X"]
[adzerk/boot-cljs "1.7.X" :scope "test"]
[adzerk/boot-cljs-repl "X.X.X" :scope "test"]
[adzerk/boot-reload "X.X.X" :scope "test"]
[com.cemerick/piggieback "X.X.X" :scope "test"]
[weasel "X.X.X" :scope "test"]
[org.clojure/tools.nrepl "X.X.X" :scope "test"]])
(require '[adzerk.boot-cljs-repl :refer :all])
Step 2: Creating the Dev task
Update your requirements and add the following, I’ll explain after.
(require '[adzerk.boot-cljs :refer :all]
'[adzerk.boot-cljs-repl :refer :all]
'[adzerk.boot-reload :refer :all])
(deftask dev []
(comp
(watch)
(reload)
(cljs-repl)
(cljs :optimizations :none)
))
One of the tough parts about boot is intuiting how tasks are composed and why order matters. Basically, everything is functions returning functions returning functions, so I’ll give you the cliff’s notes: the tasks go from bottom to top. So here, we:
- Compile the clojurescript and add it to the fileset
- Start a repl and pass the fileset along
- Reload the browser files when anything changes, and
- Watch the files for changes and re-run the previous tasks
To start this task, just run $ boot dev
.
Step 3: Including the compiled file.
By default, the Clojurescript entry point is the main.js
file
that appears (again by default) in the target
directory of your
boot project.
If you’re like me, you store your static site assets in resources/public
,
so you actually want your main.js
file to appear there (or possibly in resources/public/js
.
Here’s another irritating failure of documentation: to do this, you need to create
a “.cljs.edn” file in your source path which matches the resource path that you want
your file to appear in. So, if you want your main.js file to appear as resources/public/js/main.js
,
you need to put the following in src/cljs/public/js/main.cljs.edn
:
{:require [myproject.core]
:init-fns []
:compiler-options {:asset-path "/js/main.out"}}
Replace myproject.core
with whatever clojurescript namespace you want to load first.
With this configuration in place, your clojurescript files will end up in target/public/js/main.out
.
You can now serve the contents of public
for development purposes; you could use
boot-http for this, but if you’re lazy and
have python installed you can also cd
to the target/public
directory and run
$ python -mSimpleHTTPServer
to start serving files from that directory.
Speaking of which, you’ll also want to create an HTML page to load the file. Here’s
something you can toss in resources/public/index.html
to get going:
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World~</h1>
<script src="/js/main.js">
</body>
</html>
After running boot dev
, you should be able to open that up in a browser.
Step 4: Connecting to the Browser REPL
I use vim-fireplace for Clojure development
because I’m just that way. To connect to a cljs-repl provided browser repl, edit
a namespace in your src/cljs
directory somewhere, then run the
following in vim:
:Piggieback (adzerk.boot-cljs-repl/repl-env)
Make sure you have the page loaded in a browser! If you don’t this will just freeze and you won’t know what’s wrong. Load up the page first, then connect to it with Piggieback.
That should be it! You can test it out by evaluating a javascript alert and
having it appear in the browser: (js/alert "HELLO WORLD")
.
Leave a comment or whatever if you had any trouble, or if you didn’t, both are good.