clojurescript
This skill contains shell command directives (!`command`) that may execute system commands. Review carefully before installing.
ClojureScript
ClojureScript (CLJS) is Clojure that compiles to JavaScript via the Google Closure Compiler. It shares Clojure's syntax, immutable data structures, and REPL-driven workflow, but runs on JS engines (browser, Node.js) and interoperates directly with JS objects, functions, and npm/Closure libraries.
This skill covers writing idiomatic CLJS, JS interop, dependency/build configuration, and the newer native async/await support. When in doubt about compiler flags or interop mechanics, check references/ rather than guessing — CLJS has a lot of build-tooling surface area that's easy to get subtly wrong.
Not for nbb. Babashka/nbb runs CLJS through an SCI interpreter (no Google Closure Compiler, no
:optimizations/:advanced, no externs) for fast-starting Node.js scripts — a different language surface from what this skill covers. nbb is out of scope here — consult nbb's docs and examples fornbb.ednprojects, anything run vianbb script.cljs/npx nbb.
Gotchas
:optimizationsdefaults to:none, not:advanced— a project with no explicit:optimizationssetting is running unoptimized dev output.- Symbol renaming under
:advancedbreaks untyped JS interop. "Works in dev, breaks in prod" is almost always this — fix with an externs file or string-keyed access (goog.object/get), not by changing app logic. :mainunder:optimizations :noneonly loads what's actually:required. A side-effect-only namespace not reached by the entry namespace's transitive requires silently doesn't run — add an explicit:requireor put it in:preloads.:output-toand:modulesare mutually exclusive, not layered —:modulesneeds a per-module:output-toinside each module map, not a top-level one.- No arbitrary-precision numbers. CLJS numbers are all JS
number(a double) — no JVM-style BigDecimal/BigInt/ratios. Watch for precision loss on large integers. ^:asyncmetadata goes on thefn/name only, never the arg vector, andawaitonly works inside a function actually marked:async— a nestedfndoesn't inherit it and needs its own^:async(seereferences/async-functions.md).- An
:asyncfunction always returns aPromise, even when the body looks like it returns a plain value. - Truthiness differs from JS. Only
falseandnilare falsy —0,"",js/NaN,[], and(array)are all truthy.(if 0 "yes" "no")→"yes", unlike JS. Easy to get backwards when translating JS/TS conditionals. - No automatic tail-call optimization, same as JVM Clojure. CLJS functions run as ordinary JS function calls, and most JS engines don't reliably implement proper tail calls either — deep non-tail self-recursion will blow the JS call stack on large/unbounded input, even though it "works" for small test inputs. Reach for
recur(self-recursion, tail position only — see below),trampoline(mutual recursion), or wrap the recursive case inlazy-seq(sequence-producing recursion) rather than plain recursive calls whenever the input size isn't small and fixed.