Setup ClojureScript with Shadow CLJS

Here's how to set up a simple ClojureScript project with shadow-cljs.


Shadow CLJS is so many things, I don't think I can currently enumerate them. In this case, it's going to configure the building of ClojureScript into JavaScript. It'll give us a nice development server too.


On a Clojure/Script project, dependencies are usually in deps.edn. You can put them in shadow-cljs config too, but let's stick with the standard. So, here's deps.edn:

{:paths ["src"]
 :aliases {:cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.23.3"}}}}}

By setting up the alias, we're making shadow-cljs available where it needs to be.

The Build

The build and dev server are defined in shadow-cljs.edn:

{:deps {:aliases [:cljs]}
 :builds {:app {:target :browser
                :output-dir "public/js"
                :modules {:main {:init-fn app.core/init}}}}
 :dev-http {5555 "public"}}

:deps indicates that shadow-cljs should look in deps.edn for dependencies. This build is for an in-browser web app. You could build for Node.js too.

The :init-fn shows the entry point to our application, which we have yet to write.

The Source

As we indicated in our deps.edn, src will be where our ClojureScript code is. Here is core.cljs:

(ns app.core)

(defn on-load []
  (js/console.log "Hi ya yahoos")

  (let [root (js/document.getElementById "root")]
    (set! (.-innerHTML root) "Loaded, yay!")))

(defn ^:export init []
  (js/document.addEventListener "DOMContentLoaded" on-load))

The namespace matches to full path in :init-fn. There's not much here, but it should demonstrate a few things:

^:export is putting metadata on the function, and it's equivalent to a named export in ES modules.

The js/ namespace is available globally in cljs. Methods and attributes usually available on window/global are available here.

set! is the cljs api for setting variables

.- is used to access properties on objects.

Lots more to learn there, but these are the basics: A callback will be trigger on a DOM event, the console will print, and the DOM will be manipulated.

Run the App

We need to make the shadow-cljs cli available to run commands. You can do that with npx or install it where it's in your $PATH, as local node_modules are for my system:

npm init -y
npm install shadow-cljs --save-dev

Now we can run the dev server. For watch mode:

shadow-cljs watch app

app is the build identifier from shadow-cljs.edn.

Now you're ready on your :dev-http port:

xdg-open localhost:5555

You'll see the console.log and the DOM changed. You're CLJS'ing now!