I set out to write a Node.js application in ClojureScript, using Boot as my build tool. Because these tools are still young and rapidly evolving, I had to sort through an abundance of incomplete, misleading or out-of-date information to figure it out. For reference, here is an excellent article on how to do the same thing using Leiningen.

tl;dr - here’s the quickstarter kit.

Let’s start off by creating a file src/app/core.cljs:

1
2
3
4
5
6
7
8
9
(ns app.core
(:require [cljs.nodejs :as nodejs]))
(nodejs/enable-util-print!)
(defn main [& args]
(println "Abracadabra!"))
(set! *main-cli-fn* main)

The namespace app.core reflects the source file core.cljs placed in the app/ directory. We can compile this directly using the ClojureScript compiler:

1
$ cljsc src/app/core.cljs '{:target :nodejs :optimizations :advanced :output-to "out/main.js"}'

There are a number of ways to install the ClojureScript compiler, but building it from source worked best for me. If all went well, you can run your app with:

1
$ node out/main.js

Using the ClojureScript compiler manually can be tricky. If you can’t quite get it to work, don’t worry. We’re going to use the flexible Boot build tool to automate this process.

Let’s create a build.boot project file:

1
2
3
4
5
6
(set-env!
:source-paths #{"src"}
:dependencies '[[adzerk/boot-cljs "1.7.228-2" :scope "test"]])
(require
'[adzerk.boot-cljs :refer [cljs]])

You can adjust the boot-cljs version to reflect the latest on Clojars. The point of Boot is to be a flexible build automation system that lets you assemble your own pipeline using a rich assortment of independent composable tasks.

Let’s define a file watcher task that responds to changes by compiling our Node.js app with source mapping enabled:

1
2
3
4
5
6
7
8
9
(deftask dev
"Watch/compile files in development"
[]
(comp
(watch)
(cljs :source-map true
:optimizations :none
:compiler-options {:target :nodejs})
(target)))

Running boot dev should now look something like this:

1
2
3
4
5
6
7
8
9
$ boot dev
Starting file watcher (CTRL-C to quit)...
Writing main.cljs.edn...
Compiling ClojureScript...
• main.js
Writing target dir(s)...
Elapsed time: 9,342 sec

The app should now appear in the target/ directory, which is created by the target task, introduced as a standalone operation in Boot 2.5 (See: Boot 2.5: Slow is smooth, smooth is fast). You must cd into the target directory to run the compiled app because at this point it relies on the relative paths of the dependencies generated by the Google Closure Compiler in the target/main.out/ directory.

1
2
$ cd target
$ node main.js

For building the final product, let’s add a prod task:

1
2
3
4
5
6
7
(deftask prod
"Compile for production"
[]
(comp
(cljs :optimizations :advanced
:compiler-options {:target :nodejs})
(target)))

You are now ready to start writing your Node.js application in ClojureScript. This guide has not covered installing JavaScript dependencies or the various other options you can pass to the compiler. Note that we could have accomplished the same build process with the command line:

1
2
3
4
5
# dev build
$ boot watch cljs -c '{:target :nodejs}' target
# prod build
$ boot cljs -c '{:target :nodejs}' -O advanced target

However, a build.boot file will allow you to expand your build configuration as your project evolves. More information about boot-cljs is available on the wiki, though as usual some of it is geared specifically toward JavaScript in the browser.