Clojure Guide — ClojureScript: Clojure for the Browser
In this tutorial, you will learn about Clojure Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
ClojureScript is a compiler that translates Clojure to JavaScript, bringing Clojure's functional paradigms, immutable data structures, and concurrency model to browsers and JavaScript runtimes.
What You'll Learn
- Setting up ClojureScript projects
- ClojureScript vs Clojure differences
- Interoperability with JavaScript
- React integration with Reagent
- Building and optimizing CLJS apps
Why It Matters
ClojureScript brings the same productivity gains as Clojure to frontend development: immutable data, REPL-driven development, and a concise syntax. Doda Browser uses ClojureScript for its extension API.
Real-World Use
CircleCI's frontend is built with ClojureScript. Netflix uses ClojureScript for internal tools. Nubank, a Brazilian fintech, built their entire app in ClojureScript with Reagent.
flowchart LR
A["ClojureScript"] --> B["Setup"]
B --> C["CLJS vs CLJ"]
C --> D["JS Interop"]
D --> E["Reagent"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Project Setup
;; deps.edn
{:deps {org.clojure/clojurescript {:mvn/version "1.11.60"}}}
;; project.clj
(defproject my-app "0.1.0"
:dependencies [[org.clojure/clojurescript "1.11.60"]]
:plugins [[lein-cljsbuild "1.1.8"]]
:cljsbuild {:builds [{:id "dev"
:source-paths ["src"]
:compiler {:main "my-app.core"
:output-to "public/js/main.js"
:optimizations :none}}]})
ClojureScript vs Clojure
;; Same Clojure code works in both
(defn greet [name]
(str "Hello, " name))
;; Different: CLJS uses js/* for host interop
;; CLJ: (System/currentTimeMillis)
;; CLJS: (.getTime (js/Date.))
;; Different: type hints
;; CLJ: ^String name
;; CLJS: ^js/String name or ^string name
JavaScript Interop
;; Access JavaScript properties
(.-length "hello") ; Property access
(.toUpperCase "hello") ; Method call
(js/console.log "Hello from CLJS") ; Global object
;; JavaScript object literals
#js {:name "Alice" :age 30} ; JS object (not CLJS map)
;; Using JavaScript arrays
(aget #js [1 2 3] 0) ; Access index
(aset #js [1 2 3] 0 99) ; Set index
;; Arrow functions (CLJS 1.10.600+)
(def fn-arrow #(js/console.log %))
DOM Manipulation
;; Direct DOM access
(js/document.getElementById "app")
;; Set innerHTML
(set! (.-innerHTML (js/document.getElementById "app"))
"<h1>Hello from ClojureScript</h1>")
;; Event listeners
(.addEventListener (js/document.getElementById "btn")
"click"
#(js/console.log "Clicked!"))
Reagent (React Wrapper)
(require '[reagent.core :as r])
;; Simple component
(defn hello-component []
[:div
[:h1 "Hello from Reagent"]
[:p "This is ClojureScript!"]])
;; Reactive state
(defn counter []
(let [count (r/atom 0)]
(fn []
[:div
[:p "Count: " @count]
[:button {:on-click #(swap! count inc)} "Increment"]])))
;; Render
(r/render [counter] (js/document.getElementById "app"))
Working with npm
;; Shadow CLJS or figwheel.main for npm integration
:dependencies [[thheller/shadow-cljs "2.20.0"]]
;; shadow-cljs.edn
{:dependencies [[reagent "1.1.0"]]
:builds {:app {:target :browser
:output-dir "public/js"
:assets-path "js"}}}
Reader Conditionals
;; Code that works in both CLJ and CLJS
(defn platform-info []
#?(:clj (str "Clojure " (clojure-version))
:cljs (str "ClojureScript " (cljs-version))))
;; Reader conditional splicing
(def x
#?@(:clj [(System/currentTimeMillis)]
:cljs [(.getTime (js/Date.))]))
Common Mistakes
1. Forgetting CLJS has no threading
ClojureScript runs in a single thread. Atoms work, but refs and agents are not available. Use core.async for concurrency.
2. Using JVM-only libraries
Not all Clojure libraries work in CLJS. Check for CLJS compatibility or use reader conditionals.
3. Confusing JS objects with CLJS data structures
#js {} creates JS objects. Clojure maps {} are persistent data structures. Use the right one for interop.
4. Ignoring advanced compilation warnings
:optimizations :advanced renames variables. Use ^:export and externs for public API.
5. Not using the REPL
CLJS has REPL support via Figwheel or shadow-cljs. Use it for interactive development.
Practice Questions
1. What is ClojureScript? A compiler that translates Clojure to JavaScript, enabling Clojure programming for browsers and JavaScript runtimes.
2. How does ClojureScript interop with JavaScript?
Use js/ for globals, .- for property access, . for method calls, and #js for JS object literals.
3. What is Reagent? A minimal ClojureScript wrapper around React that renders Hiccup-style vectors as React components.
Challenge: Build a simple ClojureScript todo list using Reagent with local state management.
FAQ
{{< faq question="Is ClojureScript faster than JavaScript?" >}} ClojureScript performance is comparable to hand-written JavaScript when compiled with advanced optimizations. Immutable data structures add overhead but enable safer code. {{< /faq >}}
{{< faq question="Can I use npm packages with ClojureScript?" >}} Yes, through shadow-cljs or figwheel.main. These tools integrate with npm and provide module resolution. {{< /faq >}}
{{< faq question="What is the difference between CLJS and CLJ data structures?" >}} CLJS persistent data structures are implemented in JavaScript. They behave identically but have different performance characteristics than JVM Clojure. {{< /faq >}}
{{< faq question="Does ClojureScript support macros?" >}} Yes, but macros must be written in Clojure (JVM) and used in CLJS. The macroexpansion happens at build time on the JVM. {{< /faq >}}
{{< faq question="What build tools are available for CLJS?" >}} Leiningen with cljsbuild, shadow-cljs, figwheel.main, and deps.edn with CLI tools. Shadow-cljs is the most popular for modern projects. {{< /faq >}}
Mini Project
Create a simple interactive counter in ClojureScript with Reagent:
(ns my-app.core
(:require [reagent.core :as r]))
(defn app []
(let [count (r/atom 0)]
(fn []
[:div {:style {:text-align "center" :padding "2rem"}}
[:h1 "ClojureScript Counter"]
[:p {:style {:font-size "3rem"}} @count]
[:button {:on-click #(swap! count inc)} "+"]
[:button {:on-click #(swap! count dec)} "-"]
[:button {:on-click #(reset! count 0)} "Reset"]])))
(defn ^:export init []
(r/render [app] (js/document.getElementById "app")))
What's Next
Now that you understand ClojureScript, explore GraalVM for compiling Clojure to native binaries.
| Topic | Description | Link |
|---|---|---|
| Clojure GraalVM | Native compilation | {{< ref "28-graalvm" >}} |
| Clojure Performance | Optimization guide | {{< ref "29-performance" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro