clojure
This skill contains shell command directives (!`command`) that may execute system commands. Review carefully before installing.
Clojure & ClojureScript
Clojure runs on the JVM. ClojureScript (CLJS) shares its syntax, immutable data structures, and REPL-driven workflow, but compiles to JavaScript via the Google Closure Compiler and interoperates with JS objects, functions, and npm/Closure libraries.
This skill covers idiomatic Clojure, JVM/JS interop, dependency/build configuration, and native CLJS async/await. First identify the runtime: .clj is JVM Clojure, .cljs is ClojureScript, and .cljc needs a deliberate shared-runtime boundary. When in doubt about compiler flags or interop mechanics, check references/ rather than guessing.
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.