clojurescript-nbb
ClojureScript on nbb
nbb is "ad-hoc CLJS scripting on Node.js": it evaluates ClojureScript through SCI (the same interpreter that powers babashka), not through the usual CLJS-to-JS compile + Google Closure Compiler pipeline. That trade-off is the whole story of how nbb differs from "normal" CLJS (shadow-cljs, cljs.main, Leiningen):
- Fast startup, no build step. No
:optimizationslevels, no advanced-compilation renaming, no externs.nbb script.cljsjust runs — startup is ~170ms. - A subset of the language. SCI interprets a large but not-complete subset of CLJS. Notably: no
deftype(as of current nbb). Assume standarddefn/let/defrecord/protocols/multimethods/core.async-adjacent constructs work; verify anything exotic againstreferences/or the nbb README/CHANGELOG rather than assuming full compiled-CLJS parity. - First-class macros, right in the script. Unlike self-hosted CLJS generally, you can
defmacrodirectly inside a.cljsfile and use it in the same file — no separate.clj/.cljcsplit needed. This is a headline nbb feature; lean on it for things like sync-looking async code (seepletbelow). - npm interop is the default interop story, not an optional add-on — nbb scripts routinely
:requirenpm packages directly.
If you're unsure whether a request is nbb or general/shadow-cljs CLJS, check for nbb.edn, a #!/usr/bin/env nbb shebang, or explicit "nbb"/"node babashka" mentions. If it's a browser app, Reagent+re-frame SPA, or mentions :optimizations/shadow-cljs.edn, that's the general clojurescript skill's territory instead.
Requiring npm packages
nbb implements :require for npm packages via a dynamic import(), resolved relative to the script and to that script's local node_modules: