How to set up shadow-cljs

Installation

Note, this was written for Ubuntu linux. Other distros, Mac and especially Windows will have differences in how you install these, but each of them have good instructions.

You need 3 things to get shadow-cljs up and running.

Install Nodejs

https://github.com/nodesource/distributions/blob/master/README.md#installation-instructions

from the command line, add the node repository, and install node. Here, we’re installing the LTS variant.

# Using Ubuntu
curl -sL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs

Install Shadow-cljs

This will install shadow-cljs globally. It’s the most convenient way to use it.

sudo npm install -g shadow-cljs

Project Setup

Quick reference

Detailed

From the terminal:

Create the directory you want to make your project in.

Run npm init to initiate the project. This will create a package.json file, where your npm dependencies will live

Run npm install --save-dev shadows-cljs. This installs shadow-cljs locally (in the previous section you installed it globally). the --save-dev flag puts the dependency in your package.json file.

Run shadow-cljs init to initiate the shadow-cljs project. This will create a shadow-cljs.edn, which is where all your clojurescript dependencies go, and also where your build instructions live. It tells shadow-cljs where to run the dev-server, where to look for the main function, and where to output the compiled javascript.

Update shadow-cljs.edn using the below config. Here’s an explanation of the parts

Create a file public/index.html and put the below in it.

Create the file src/main/my-project/main.cljs and put the below code in it.

Run shadow-cljs watch app from the project root directory. This will launch the application

Watch the build script - on the first launch it will update any dependencies necessary (including adding them to your package.json), launch all the infrastructure, and compile your clojurescript files to js, outputting them to where you instructed it. It will take a while to actually launch, because it has to compile everything. On reload (when you edit and save a .cljs file) it will only have to compile the changed files, so will be much quicker.

Once the build is complete, navigate your browser to http://localhost:9090/. You should see “hello world” in an h1 block (from your app function in main.cljs).

Next, make sure the ‘reload’ is working properly - while the app is running, go to main.cljs and change the text in the h1 div from “hello world” to something else. When you save, your terminal should show that the project is recompiling, and your browser should quickly reflect the changed text.

Further resources

For actually learning how build a clojurescript application with reagent and re-frame, I recommend Eric Normands various courses. They’re not free, but the quality is excellent, and he explains how all the pieces work very well.

File templates

package.json

{
  "name": "qniform",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "shadow-cljs": "^2.20.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

shadow-cljs.edn

;; shadow-cljs configuration
{:source-paths
 ["src/dev"
  "src/main"
  "src/test"]

 :dependencies
 [[reagent "1.0.0-alpha2"]
  [re-frame "1.0.0"]]

 :dev-http {9090 "public/"}

 :builds
 {:app {:output-dir "public/compiledjs/"
        :asset-path "compiledjs"
        :target     :browser
        :modules    {:main {:init-fn my-project.main/main}}
        :devtools   {:after-load my-project.main/reload}}}}

Index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
    <head>
    </head>
    <body>
        <div id="app"></div> 
        <script src="/compiledjs/main.js"></script>
    </body>
</html>

main.cljs

(ns my-project.main
  (:require [reagent.core :as r]
            [reagent.dom :as rd]))

(defn app []
  [:div
   [:h1 "hello world"]])

(defn mount []
  (rd/render [app]
             (.getElementById js/document "app")))

(defn main []
  (mount))

(defn reload []
  (mount))