Up and Running with Shadow CLJS

Dan Sutton

Created: 2020-01-08 Wed 08:09

1 Shadow CLJS and ClojureScript



2 What this will demonstrate

  • how to get clojurescript installed
  • how to configure
  • how to run/build
  • how to deploy on Netlify

3 Why ClojureScript

  • functional
  • interactive/REPL
  • immutable
  • Google Closure
  • OOO

4 What is Shadow CLJS

  • Clojurescript is a compiler, its just a jar file for a jvm.
  • need configuration for CLJS compiler
  • websockets to and from the js engine for interpreting
  • dependency management
  • hot code reload

5 Getting Set Up

The web is built on this language so we need two programs in this language:


Two c++ programs:

  • jdk
  • node/v8

6 Creating an app

To target a browser:

mkdir app
cd app
npm init -y
npm i react create-react-class react-dom shadow-cljs

Alternatively, can use a starter:

npx create-cljs-app my-app
cd my-app
npm start

This can be a bit heavier. It has some opinionated testing out of the box and installs a heavy headless browser in node-modules. The former options is usually better for me.

7 Create shadow-cljs.edn

{:builds       {:app {:asset-path "/js"
                      :modules    {:main {:init-fn app.main/init}}
                      :output-dir "public/js"
                      :target     :browser}}
 :dependencies [[reagent "0.8.1"]]
 :dev-http     {3000 "public"}
 :source-paths ["src"]}

8 Index.html

tree public
└── index.html
<!DOCTYPE html>
<html lang="en">
    <div id="app"></div>
    <script src="/js/main.js" type="text/javascript"></script>

9 Hiccup

Datastructures describing html

  <a href="https://www.google.com">Link text</a>
    <li>Thing 1</li>
    <li>Thing 2</li>

 [:h1 "Title"]
 [:a {:href "www.google.com"
      :style {:color "blue"
              :font-size "24px"}}
  "Link Text"]
  [:li "Thing 1"]
  [:li "Thing 2"]]]

10 Closer look

tree src
└── app
    └── main.cljs
(ns app.main
  (:require [reagent.core :as r]))
(defn app
  [:div {:style {:margin "auto"
                 :margin-top "100px"
                 :width "600px"}}
   [:h1 "hi"]])

11 Boiler Plate

(defn ^:dev/after-load start []
  (r/render [app]
            (.getElementById js/document "app")))
(defn ^:export init

12 Source files

;; src/app/main.cljs
(ns app.main
  (:require [reagent.core :as r]))

(defn app
  [:div {:style {:margin "auto"
                 :margin-top "100px"
                 :width "600px"}}
   [:h1 "hi"]])

(defn ^:dev/after-load start []
  (r/render [app]
            (.getElementById js/document "app")))

(defn ^:export init

13 Run the app

dan@pop-os:~/projects/clojure/cljs-play/app$ npx shadow-cljs watch app
shadow-cljs - config: /home/dan/projects/clojure/cljs-play/app/shadow-cljs.edn  cli version: 2.8.83  node: v12.13.1
shadow-cljs - updating dependencies
shadow-cljs - dependencies updated
shadow-cljs - HTTP server available at http://localhost:3000
shadow-cljs - server version: 2.8.83 running at http://localhost:9630
shadow-cljs - nREPL server started on port 44793
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build completed. (162 files, 161 compiled, 0 warnings, 32.62s)

And visit localhost:3000 as configured in shadow-cljs.edn

:dev-http     {3000 "public"}


How to connect to a repl

dan@pop-os:~/projects/clojure/cljs-play/app$ npx shadow-cljs cljs-repl app
shadow-cljs - config: /home/dan/projects/clojure/cljs-play/app/shadow-cljs.edn  cli version: 2.8.83  node: v12.13.1
shadow-cljs - connected to server
cljs.user=> (require 'app.main)
No application has connected to the REPL server. Make sure your JS environment has loaded your compiled ClojureScript code.
> (require 'app.main)
cljs.user=> (in-ns 'app.main)
app.main=> (app)
[:div {:style {:margin "auto", :margin-top "100px", :width "600px"}} [:h1 "hi"]]

Above is a common mistake: "no application has connected…". You need to visit localhost:3000 so that Shadow CLJS is connected to the js runtime to evaluate your code.

Also, note that `(app)` returns just a vector. No React objects. Note that the code in the repo is a bit more advanced so will actually return a function though.


  • in a browser setting, generally not as useful
  • rely on browser, hot code reloading, and tooling
  • tooling: inline evaluation results, autocomplete, etc.
  • in a node setting, absolutely useful
  • of course on the jvm its a requirement

16 Npm packages

While server is running you can just install

npm i react-modal
(ns app.main
  (:require [reagent.core :as r]
            ["react-modal" :as Modal]))

(.setAppElement Modal "#app")

(defn app
  (let [modal-state (r/atom false)]
    (fn []
      [:div {:style {:margin "auto"
                     :margin-top "100px"
                     :width "600px"}}
       [:h1 "hi"]
       [:button {:on-click #(swap! modal-state not)}
       [:> Modal {:isOpen @modal-state
                  :onRequestClose #(reset! modal-state false)
                  :contentLabel "Example Modal"
                  :shouldCloseOnOverlayClick true
                  :style {:content {:top         "50%"
                                    :left        "50%"
                                    :right       "auto"
                                    :bottom      "auto"
                                    :marginRight "-50%"
                                    :transform   "translate(-50%, -50%)"}}}
         "This is a modal"
          [:li "With content"]
          [:li "And lists"]]]]])))

17 Netlify

Netlify can build!

  • Build Command: `npx shadow-cljs release app`
  • Publish directory: "public"


18 The bad

  • can use most libs but not all
  • tooling not as polished
  • there's not 100,000k people making cool libraries

19 Links and Info