clojurescript

Installation
SKILL.md
Contains Shell Commands

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 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
14
Repository
bsene/skills
GitHub Stars
5
First Seen
Jul 10, 2026
clojurescript — bsene/skills