A beginner guide to compile ClojureScript with shadow-cljs

shadow-cljs provides everything you need to compile your ClojureScript code with a focus on simplicity and ease of use.

To install shadow-cljs, use npm:

npm install -g shadow-cljs

shadow-cljs requires Java, make sure you have Java installed. Don’t worry, shadow-cljs handles Java for you. If you still want to learn more about ClojureScript, checkout this list.

Compile to browser

Say you have a project:

.
├── README.md
├── assets
│ └── index.html
├── package.json
├── shadow-cljs.edn
├── src
│ └── app
│ ├── lib.cljs
│ └── main.cljs
└── yarn.lock

assets/index.html(please copy it to target/index.html in order that it can be accessed) looks like:

<script src="main.js"></script>

src/app/main.cljs may look like:

(ns app.main)
(def value-a 1)
(defonce value-b 2)
(defn reload! []
(println "Code updated.")
(println "Trying values:" value-a value-b))
(defn main! []
(println "App loaded!"))

The namespace for this project is just app. To compile this project, first create a shadow-cljs.edn file, with configs:

{:source-paths ["src"]
:dependencies []
:dev-http {8080 "target/"}
:builds {:app {:output-dir "target/"
:asset-path "."
:target :browser
:modules {:main {:init-fn app.main/main!}}
:devtools {:after-load app.main/reload!
:http-root "target"
:http-port 8080}}}}

Here’s how it’s configured:

  • :source-paths is where you put source code
  • :dependencies is ClojureScript deps of this projects
  • :builds is where you specify build configurations
  • :app is a build id named by us, we will need it to run specific compilations from command line tools or from APIs
  • :output-dir decides where the generated files are saved
  • :asset-path is the base path of files of the hot updated code
  • :target is short for “compilation target”, :browser is for browser apps
  • :modules specifies the modules we want to generate, :main also means the bundle will be named main.js
  • :init-fn specifies the main function of the whole program
  • :devtools specifies configurations for development tools
  • :dev-http tells shadow-cljs to serve the folder target/ on port 8080.

During development, we can compile with hot code swapping, watch how we use the build id :app :

shadow-cljs watch app

After initial compilation finished, shadow-cljs would generate code in target/main.js and serve target/ folder on http://localhost:8080. When you open the url, the page will connect to shadow-cljs’ server and listen for updates. As you modify code and save files, the app will be hot reloaded and then app.main/reload! will be called.

To compile and optimize code:

shadow-cljs release app

For the complete example, please clone this repo:

Compile to Node.js

It’s quite similar steps to compile ClojureScript targeting Node.js , for the project:

.
├── README.md
├── package.json
├── shadow-cljs.edn
├── src
│ └── server
│ └── main.cljs
└── yarn.lock

src/server/main.cljs may look like:

(ns server.main)
(def value-a 1)
(defonce value-b 2)
(defn reload! []
(println "Code updated.")
(println "Trying values:" value-a value-b))
(defn main! []
(println "App loaded!"))

We can use shadow-cljs.edn with configurations:

{:source-paths ["src"]
:dependencies []
:builds {:app {:target :node-script
:output-to "target/main.js"
:main server.main/main!
:devtools {:after-load server.main/reload!}}}}

Now we use :node-script as the compilation :target . And there are two fields:

  • :output-to specifies the output file
  • :main specifies the main function

Like in previous demo, we can start compilation in development mode, and run it with Node.js :

shadow-cljs watch app
# in another terminal
node target/main.js

As Node.js runs, it will connect to shadow-cljs’ WebSocket server listening for updates. Some npm modules(wsand source-map-support) are required, please install them. It also does hot code swapping as well as calling reload! function.

To create a release bundle, use the sub-command:

shadow-cljs release app

You may also add :compiler-options {:optimizations :simple}} to disable mangling. To try the full example, clone this repo:

More

There are also examples you can browse in shadow-cljs, like using Macros:

Or like compile code with long term caching:

And some examples run in several environments of JavaScript:

Explore more on shadow-cljs and send feedbacks on GitHub :)