January 11, 2016

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/ and resources/ 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:

  :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:

  :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 []
    (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:

        <title>Hello World!</title>
        <h1>Hello World~</h1>
        <script src="/js/main.js">

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.