clojure

Installation
SKILL.md
Contains Shell Commands

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 for nbb.edn projects, anything run via nbb script.cljs/npx nbb.

Gotchas

  • :optimizations defaults to :none, not :advanced — a project with no explicit :optimizations setting is running unoptimized dev output.
  • Symbol renaming under :advanced breaks 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.
  • :main under :optimizations :none only 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 :require or put it in :preloads.
  • :output-to and :modules are mutually exclusive, not layered — :modules needs a per-module :output-to inside 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.
  • ^:async metadata goes on the fn/name only, never the arg vector, and await only works inside a function actually marked :async — a nested fn doesn't inherit it and needs its own ^:async (see references/async-functions.md).
  • An :async function always returns a Promise, even when the body looks like it returns a plain value.
  • Truthiness differs from JS. Only false and nil are 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 in lazy-seq (sequence-producing recursion) rather than plain recursive calls whenever the input size isn't small and fixed.
Installs
2
Repository
bsene/skills
GitHub Stars
6
First Seen
Sep 26, 2026