100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

ClojureScript Basics

Get started with ClojureScript — how it compiles to JavaScript, how to set up and hot-reload a project with shadow-cljs, and how JS interop and platform differences work.

Practical ClojureIntermediate10 min readJul 10, 2026
Analogies

What Is ClojureScript?

ClojureScript is a compiler that translates Clojure's syntax and core semantics into JavaScript, so the same immutable persistent data structures, functional idioms, and macro system that Clojure offers on the JVM also run in browsers and on Node.js. It shares the vast majority of clojure.core with Clojure, so most everyday code — sequence functions, destructuring, protocols — works identically on both platforms.

🏏

Cricket analogy: ClojureScript is like a star Test batsman adapting their exact same technique to succeed in T20 cricket — same grip, same core principles, translated for a different pitch and format rather than the original arena.

Setting Up and Compiling with shadow-cljs

shadow-cljs is the most widely used modern build tool for ClojureScript, configured via shadow-cljs.edn. The :builds map names one or more build targets, such as an :app browser build or a :test Node build, and running the dev server connects a REPL directly to the running browser tab, so evaluating a form updates the live page and hot-reloads changed code without a full page refresh.

🏏

Cricket analogy: A shadow-cljs.edn's :builds config is like a tour manager's master schedule listing each format's separate training plan, with the connected REPL acting like a live coach who can tweak a batsman's technique mid-net-session without stopping the whole session.

clojure
;; shadow-cljs.edn
{:source-paths ["src" "test"]
 :dependencies [[reagent "1.2.0"]]
 :builds
 {:app  {:target :browser
         :output-dir "public/js"
         :modules {:main {:init-fn myapp.core/init!}}}
  :test {:target :node-test
         :output-to "out/test.js"}}}

;; src/myapp/core.cljs
(ns myapp.core
  (:require [clojure.string :as str]))

(defn ^:export init! []
  (js/console.log "app starting")
  (let [el (.getElementById js/document "app")
        greeting (str/upper-case "hello from clojurescript")]
    (set! (.-textContent el) greeting)))

;; JS <-> Clojure data conversion at a boundary
(defn send-payload! [clj-map]
  (js/fetch "/api/save"
    #js {:method "POST"
         :body (js/JSON.stringify (clj->js clj-map))}))

JavaScript Interop in ClojureScript

ClojureScript reuses the same dot-notation convention as Clojure/Java interop: js/ prefixes access to any global JavaScript object such as js/window or js/document, (.-propName obj) reads a JS property, and (.method obj args) calls a JS method. For data crossing the boundary, #js {...} converts a Clojure literal into a plain JS object at compile time, while clj->js and js->clj convert whole nested structures at runtime.

🏏

Cricket analogy: Reaching into js/window.location is like an overseas commentator citing a specific foreign broadcaster's live feed by its full network name to grab a stat, and reading an innings property off a scoreboard object reads a number directly rather than computing it.

js/ is the prefix for reaching any global JavaScript object (js/window, js/document, js/Math). Use (.-propName obj) for property access and (.method obj args) for method calls — the same dot syntax as Clojure/Java interop. #js {...} converts a Clojure map literal to a plain JS object at compile time; for converting whole nested structures at runtime, use clj->js and js->clj (with :keywordize-keys true to get keyword keys back).

Differences from Clojure and Common Gotchas

A few platform differences matter in day-to-day ClojureScript development. JavaScript's single-threaded event loop means there are no real OS threads, so concurrency-flavored code relies on core.async's go blocks and channels instead. ClojureScript numbers compile to JavaScript's native doubles, so Clojure's arbitrary-precision integers and exact ratios don't exist — a real concern for money math. Reader conditionals, #?(:clj expr-a :cljs expr-b), let a .cljc file share the bulk of its logic across both platforms while branching only where they genuinely differ.

🏏

Cricket analogy: ClojureScript's JS numbers are all doubles with no arbitrary-precision integers or ratios, similar to how a scoreboard operator rounds a very long partnership tally to fit the display; reader conditionals are like a coach's playbook with a Test-match page and a separate T20 page for the same underlying strategy.

ClojureScript numbers compile to JavaScript's native double-precision float — there's no arbitrary-precision integer or exact ratio type like Clojure has on the JVM. Money and other precision-sensitive calculations need explicit handling (e.g. integer cents) rather than relying on ClojureScript numeric literals to stay exact.

  • ClojureScript compiles Clojure syntax and semantics to JavaScript, running in browsers, Node.js, and other JS runtimes.
  • shadow-cljs (or the older Figwheel) is the standard tool for compiling ClojureScript, connecting a REPL to a running browser, and hot-reloading code.
  • js/ prefixes access to any global JavaScript object; (.-prop obj) reads a JS property; (.method obj args) calls a JS method — the same dot syntax used for JVM interop.
  • #js {...} converts a Clojure map/vector literal to a plain JS object/array at compile time; clj->js and js->clj convert whole nested structures at runtime.
  • ClojureScript runs on JavaScript's single-threaded event loop; core.async's go blocks provide cooperative, channel-based concurrency instead of real OS threads.
  • ClojureScript numbers are JS doubles — there's no arbitrary-precision integer or exact ratio type, which matters for precision-sensitive math.
  • Reader conditionals, #?(:clj expr-a :cljs expr-b), let a single .cljc file share logic between Clojure and ClojureScript, branching per platform where needed.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ClojureStudyNotes#ClojureScriptBasics#ClojureScript#Setting#Compiling#Shadow#StudyNotes#SkillVeris#ExamPrep