Setup shadow-cljs react project

🚀 Create project

Maksim Ryzhikov
3 min readMar 13, 2022

The first step is create a project

npx create-cljs-project my-project

This command will create a project my-project with minimal required structure and install required dev dependencies for proper work of shadow-cljs

├── package-lock.json
├── package.json
├── shadow-cljs.edn
└── src
├── main
└── test

shadow-cljs.edn is a configuration file for ClojureScript project

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

♺ Compile ClojureScript to JavaScript

Now, let’s create an entry point ClojureScript file

touch ./src/main/core.cljs

and add minimal boilerplate code to it

;; ./src/main/core.cljs
(ns core)
(defn main []
(js/console.log "Hello World!"))
(main)

Now we should specify how to build the project. To do this, we should modify the :builds section in shadow-cljs.edn file

...:builds
{:app {:target :browser
:modules {:main {:entries [core]}}}}}
  • :app is a build id
  • :browser is target’s type for generated JavaScript files
  • :main is a name of generated JavaScript file
  • core is a ClojureScript entry namespace

You can add :output-dir "public/js" to explicitly specify output directory for generated files by default shadow-cljs will use public/js

Now we can compile our ClojureScript to JavaScript files

npx shadow-cljs compile appshadow-cljs - config: /private/tmp/my-project/shadow-cljs.edn
shadow-cljs - connected to server
[:app] Compiling ...
[:app] Build completed. (43 files, 0 compiled, 0 warnings, 0.33s)

đź‘ľ Start server

Now we are ready to setup the development server which will serve our static files. For this, we should return back to shadow-cljs.edn and add information about the dev server

:dev-http {8000 "public"}
  • 8000 is a port which will be used by http server
  • public is a folder name for static files

Now we can start it by using watch command

npx shadow-cljs watch app

Beside the start http server watchcommand also automatically recompile JavaScript files when we change ClojureScript one

You can open http://localhost:8000 and see that the server is running

Not found. Missing index.html.

but complains on missing index.html. Let’s add index.html to the public folder

touch ./public/index.html

and add some minimal markup with reference to our generated JavaScript file

<!DOCTYPE html>
<html lang="">
<head>
<title>my-project</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app"></div>
<script src="js/main.js" type="text/javascript"></script>
</body>
</html>

If we open developer tools in the browser we should see “Hello World!” message in the browser’s console. You can open core.cljs file and change the message. Browser automatically pickup the change via embedded to shadow-cljs live update functionality and display the new message

📦 Add React as dependency

shadow-cljs support npm dependencies, so here we can install react via npm

npm install react

Now we can use React in our ClojureScript application. Let’s print React’s version in the browser’s console. To do this, let’s modify core.cljs file

;; ./src/main/core.cljs
(ns core (:require ["react" :as react]))
(defn main []
(js/console.log (.-version react)))
(main)

We can look at the console without restarting the server and see that now it displays React version

[Log] shadow-cljs: load JS – "node_modules/react/index.js"
[Log] shadow-cljs: load JS – "core.cljs"
[Log] 17.0.2

By the same way, we can install react-dom and render “Hello World!”

(ns core (:require 
["react" :as react]
["react-dom" :as react-dom]))
(defn main []
(let [app-node (.getElementById js/document "app")]
(.render react-dom "Hello World!" app-node))
)
(main)

🎸 Reagent

We can use raw react and react-dom in our ClojureScript but it’s much better and less verbose to use Reagent is a minimalistic ClojureScript interface for React.

Because Reagent is a ClojureScript module, we can’t install it via npm. To install it, we should define required version in shadow-cljs.edn

:dependencies
[[reagent "1.1.1"]]

Reagent is dependent from React & React DOM packages, so you still should install them via NPM.

Let’s update core.cljs code and replace React on Reagent

;; ./src/main/core.cljs
(ns core (:require
[reagent.core :as r]
[reagent.dom :as rdom]
))
(defn app []
[:b "Hello World!"])
(defn main []
(let [app-node (.getElementById js/document "app")]
(rdom/render [app] app-node)))
(main)

--

--