Getting Started with ClojureScript and Node.js

11 minute read

I've been an on-and-off Node.js practitioner for about three years now, taking my first deep dive while developing an online multiplayer version of Settlers of Catan. My opinions of the technology have been all over the map, ranging from "OMG THIS IS THE BEST THING EVAR" to "UGH WHY DO I ONLY GET THIS ONE STUPID THREAD." Regardless of my current thoughts on Node, one thing is painfully clear: it's here, and it's not going anywhere for a while.

And that's kind of unfortunate, because no matter what you think about the virtues of single-threaded applications and non-blocking IO and callbacks and/or promises, I think one thing should be universally acknowledged: JavaScript is a terrible language.

I fully admit to the inflammatory nature of that statement. To add fuel to the fire, I'm not even going to defend it. Its fervent supporters will point at its popularity as evidence for its apparent quality. Detractors need only draw attention to the popularity of PHP to dismantle that argument. In the end, it's a holy war, and minds will not be changed in one direction or another in the span of this blog post.

But maybe we can shine light on a potential alternative, another way for those who feel just a little more sorrow than joy while working with JavaScript/Node day to day.

(((((Lisp?)))))

For the past few months, I've been playing around with Clojure, created by Rich Hickey and arguably the first Lisp to see significant usage in enterprise due to its relationship with the JVM.

Lesser known is ClojureScript (CLJS), Clojure's little brother, also created by Rich Hickey and currently maintained by David Nolen. While Clojure (primarily) exists as a guest language on the JVM, ClojureScript targets JavaScript runtimes. I've recently seen an uptick in CLJS blog posts due to the rise of React and a library called Om (also written by Nolen), which provides a CLJS-friendly API. While CLJS can certainly be used to write JavaScript applications for the browser, that's only one of many potential target runtimes.

Another such target runtime is—you guessed it—Node.js!

"But Mason," you ask, "why would someone want to run a ClojureScript app in Node? Why not just use Clojure?" That's a good question. There are a myriad of potential answers.

Maybe you already have an existing Node application or corpus of company JS libraries.

Maybe you're unable to convince your technical lead of the virtues of being able perform concurrent CPU-heavy operations without the pain of going out of process or dipping into C++, as Clojure on the JVM would give you, but can still make a case for using a language which encourages simplicity.

Maybe running ClojureScript on Node is a good "puppy dog close" for eventually moving to Clojure.

Maybe you want to write a sweet desktop application, but don't want the hassle of having to ensure the target environment has Java installed (especially annoying now that Java no longer comes preinstalled on OS X), instead opting for something like Atom Shell or node-webkit.

Regardless, it's possible. There's a decent amount of information available to get started, but it seems to be rather spread out. The last time I tried this, a couple months ago, things were outright broken due to changes in the (unfortunately similarly-named but largely unrelated) Closure and ClojureScript compilers, so I figured I'd lay it all out here.

Getting to 'Hello world!'

Thankfully, there's not much to it. We just do the following:

Install Java Acquire lein Create a new project (using a handy template) Alter our generated project.clj file Organize our sources Create an entrypoint, and run it

Easy peasy.

For the purposes of this post, I'll assume you're on either OS X or some kind of Linux.

Install Java

Go do this. I trust you can figure out how.

Acquire lein

Bam. Easy.

Create a new project (using a handy template)

We're going to make this easy by starting with a template. Saves me a lot of typing, and you a lot of work. After dropping lein on your path, cd to your most favorite project directory and do:

$ lein new mies pow $ cd pow

This creates a new project directory called pow and populates it with a bunch of stuff. Feel free to pick a different project name. Really. It's okay. mies is the name of the lein template we're using to bootstrap this project.

Take a quick peek at src/pow/core.cljs . You should see the following:

( ns pow.core ( :require [ clojure.browser.repl :as repl ])) ;; (repl/connect "http://localhost:9000/repl") ( enable-console-print! ) ( println "Hello world!" )

As simple as it gets. We can ignore the repl bits for now. Just note those last two lines.

Alter our generated project.clj file

Cracking open project.clj in our project directory, you should see something like the following:

( defproject pow "0.1.0-SNAPSHOT" :description "FIXME: write this!" :url "http://example.com/FIXME" :dependencies [[ org.clojure/clojure "1.6.0" ] [ org.clojure/clojurescript "0.0-2725" ]] :node-dependencies [[ source-map-support "0.2.8" ]] :plugins [[ lein-cljsbuild "1.0.4" ] [ lein-npm "0.4.0" ]] :source-paths [ "src" "target/classes" ] :clean-targets [ "out/pow" "pow.js" "pow.min.js" ] :cljsbuild { :builds [{ :id "dev" :source-paths [ "src" ] :compiler { :main pow.core :output-to "out/pow.js" :output-dir "out" :optimizations :none :cache-analysis true :source-map true }} { :id "release" :source-paths [ "src" ] :compiler { :main pow.core :output-to "out-adv/pow.min.js" :output-dir "out-adv" :optimizations :advanced :pretty-print false }}]})

Change it to this:

( defproject pow "0.1.0-SNAPSHOT" :description "FIXME: write this!" :url "http://example.com/FIXME" :dependencies [[ org.clojure/clojure "1.6.0" ] [ org.clojure/clojurescript "0.0-2725" ]] :node-dependencies [[ source-map-support "0.2.8" ]] :plugins [[ lein-cljsbuild "1.0.4" ] [ lein-npm "0.4.0" ]] :source-paths [ "src" "target/classes" ] :clean-targets [ "out/server/pow" "out/server/pow.js" ] :cljsbuild { :builds [{ :id "server" :source-paths [ "src/server" ] :compiler { :main pow.core :output-to "out/server/pow.js" :output-dir "out/server" :optimizations :none :target :nodejs :cache-analysis true :source-map true }} ]})

We've removed one of the cljsbuild build declarations, and renamed the other. Additionally, we've changed the :source-paths to ["src/server"] , and similarly changed :output-to and :output-dir . We've added a :target :nodejs entry to the map, and, lastly, we've modified the :clean-targets to refer to our new output locations.

lein-cljsbuild allows us to have different build configurations for our server and any potential client we may also write (say, in a subsequent blog post). This can be useful for many reasons, but the most obvious is that there's almost no reason to compile with optimizations when building for the server, but we almost certainly want advanced compilation for the client.

You can certainly play with different source and namespace topographies. This one just gets us up and running quickly while still providing some small amount of forward-thinking source partitioning. For example, it's unlikely that we'll ever want to instantiate an express application from client code.

Organize our sources

We need to move the generated sources from the old location to the one we've just specified in project.clj . From your project directory, that should be as easy as:

$ mkdir src/server $ mv src/pow src/server/

You can now test your build:

$ lein cljsbuild once Compiling ClojureScript. Compiling "out/server/pow.js" from ["src/server"]... Successfully compiled "out/server/pow.js" in 0.655 seconds. $

Woo!

Create an entrypoint, and run it

Lastly, let's create an entrypoint.js file that we'll use to run our CLJS application. Create it in the top-level of your project directory:

// entrypoint.js require ( "./out/server/goog/bootstrap/nodejs" ); require ( "./out/server/pow" ); require ( "./out/server/pow/core" );

Three things are happening here. The first line bootstraps the Google Closure library for use with Node. Mostly, this involves setting up the means to load files and prepping some globals. The second line declares your dependency sources to Closure. This includes things like cljs.core and Closure library dependencies. The last line loads the JS-compiled version of your pow/core.cljs .

Take a peek at out/server/pow/core.js to see what the JS version looks like:

// Compiled by ClojureScript 0.0-2725 {} goog . provide ( 'pow.core' ); goog . require ( 'cljs.core' ); goog . require ( 'clojure.browser.repl' ); cljs . core . enable_console_print_BANG_ . call ( null ); cljs . core . println . call ( null , "Hello world!" ); //# sourceMappingURL=core.js.map

Pretty readable!

Last step:

$ node entrypoint.js Hello world! $

And that's it!

END

Join me next time, when we'll learn how to depend on and require node modules (hint: check out lein-npm ), and use them to do every Node developer's favorite second project: spinning up an expess app!

Update

You can simplify this slightly. First, set your :optimizations level to :simple . This also means you need to set your :source-map entry to an explicit file path, rather than a boolean:

( defproject pow "0.1.0-SNAPSHOT" :description "FIXME: write this!" :url "http://example.com/FIXME" :dependencies [[ org.clojure/clojure "1.6.0" ] [ org.clojure/clojurescript "0.0-2725" ]] :node-dependencies [[ source-map-support "0.2.8" ]] :plugins [[ lein-cljsbuild "1.0.4" ] [ lein-npm "0.4.0" ]] :source-paths [ "src" "target/classes" ] :clean-targets [ "out/server/pow" "out/server/pow.js" ] :cljsbuild { :builds [{ :id "server" :source-paths [ "src/server" ] :compiler { :main pow.core :output-to "out/server/pow.js" :output-dir "out/server" :optimizations :simple :target :nodejs :cache-analysis true :source-map "out/server/pow.js.map" }}]})

Next, update your src/server/pow/core.clj file to read as follows:

( ns pow.core ( :require [ clojure.browser.repl :as repl ])) ;; (repl/connect "http://localhost:9000/repl") ( enable-console-print! ) ( defn -main [] ( println "Hello world!" )) ( set! *main-cli-fn* -main )

We've moved our println call into a function, which we've set as the value for the special *main-cli-fn* var. This will be called automatically for us.

Rebuild then run:

$ node out/server/pow.js Hello world! $

Same result as before, but without the need for an entrypoint.js file.