ClojureScript

CIDER works well with ClojureScript, but ClojureScript’s compile-on-the-JVM, run-in-a-JavaScript-runtime model means a few Clojure features can’t apply to it. See Feature support below for what works, what doesn’t, and what to reach for instead.

Unlike the Clojure ecosystem, which is dominated by a single REPL, the ClojureScript ecosystem offers several different REPLs (e.g. browser, node, weasel, figwheel and shadow-cljs). This section explains how ClojureScript support is implemented in CIDER and helps you pick one; the subsequent sections cover launching and configuring the most popular ones.

nREPL and ClojureScript

nREPL doesn’t natively support ClojureScript evaluation, that’s why an additional middleware is needed. For most REPLs (with the notable exceptions of shadow-cljs, nbb), CIDER relies on the popular Piggieback middleware for its ClojureScript support.

Piggieback works in the following manner:

  • You start a regular Clojure REPL

  • You run some Clojure code in it, that converts it to a ClojureScript REPL

This means that jacking-in is a two-fold process for ClojureScript, compared to Clojure, as now we have the extra REPL "upgrade" step.

On the bright side - this also means that a single CIDER session can hold both a Clojure and a ClojureScript REPL at once, sharing one nREPL connection. That’s the reason commands like cider-jack-in-clj&cljs and the "sibling" connection commands exist: they set up both REPLs together, and CIDER routes each evaluation to the right one. See Managing Connections for how sessions group these REPLs and how to control where code gets evaluated.

`shadow-cljs’s REPL is implemented in a very similar fashion, but its mechanism is provided by its own nREPL middleware - not Piggieback.

Choosing a ClojureScript REPL

You’ll have to decide which ClojureScript REPL to run and how you want CIDER to interact with it. Here’s a quick map of the most common choices:

REPL How CIDER drives it Setup

browser / node

Vanilla ClojureScript REPLs upgraded in place via Piggieback. node evaluates in a Node.js process; browser waits for a browser tab to connect.

Other REPLs

Figwheel Main

A Piggieback-style upgrade, with Figwheel’s hot-reloading and build tooling on top. CIDER checks that Figwheel is on the classpath before upgrading.

Using Figwheel

shadow-cljs

Uses shadow-cljs’s own nREPL middleware rather than Piggieback; CIDER prompts for the build to connect to.

Using shadow-cljs

Weasel

A browser-connected REPL over a WebSocket, upgraded via Piggieback. Maintained under the nREPL org, alongside CIDER.

Other REPLs

Node-based ClojureScript runtimes such as nbb are reached through the same ClojureScript commands, even though they aren’t Piggieback-based.

Feature support

ClojureScript compiles on the JVM but evaluates in a separate JavaScript runtime (a browser tab, Node, React Native, and so on). Features that read the compiler/analyzer therefore work much like they do for Clojure, while features that need to instrument running code, rebind vars at runtime, or inspect the JVM have nothing to attach to in a JavaScript runtime.

The following work in a ClojureScript REPL:

  • evaluation and loading files

  • code completion

  • eldoc (argument lists) and documentation lookup (cider-doc)

  • jump to definition

  • macroexpansion

  • namespace operations (listing namespaces and their vars)

  • the test runner (via cljs.test)

  • code formatting

The following are not available for ClojureScript, and aren’t planned, because they can’t work over nREPL against a JavaScript runtime - reach for the suggested alternative instead:

Feature Why, and what to use instead

Debugger / stepping

The debugger instruments forms and pauses a JVM thread. For ClojureScript use Cider Storm (time-travel debugging), or your browser/Node DevTools together with source maps.

Enlighten

Built on the same instrumentation as the debugger.

Tracing

Wraps JVM vars; ClojureScript vars compile to static JavaScript.

Profiling

Relies on JVM instrumentation; use the runtime’s own profiler (e.g. the browser’s performance tools).

apropos, find references / who-calls

Runtime introspection of Clojure vars. For static navigation and find-references across ClojureScript, use clojure-lsp alongside CIDER.

When you invoke one of these in a ClojureScript REPL, CIDER tells you it isn’t available rather than failing obscurely.

For inspecting values, the ClojureScript ecosystem leans on tap>-based tools such as shadow-cljs Inspect, Portal and cljs-devtools, which complement CIDER nicely.

Piggieback differences with the Standard ClojureScript REPL

While the Piggieback-powered ClojureScript REPLs behave more or less the same as the standard ClojureScript REPL, there are few subtle differences everyone has to be aware of.

Handling of Multiple Forms

Here’s how the standard ClojureScript behaves with multiple input forms:

cljs.user>
(declare is-odd?)
(defn is-even? [n] (if (= n 0) true (is-odd? (dec n))))
(defn is-odd? [n] (if (= n 0) false (is-even? (dec n))))
#'cljs.user/is-odd?
#'cljs.user/is-even?
#'cljs.user/is-odd?
cljs.user> (is-even? 4)
true

And here’s how a Piggieback-powered REPL behaves:

cljs.user>
(declare is-odd?)
(defn is-even? [n] (if (= n 0) true (is-odd? (dec n))))
(defn is-odd? [n] (if (= n 0) false (is-even? (dec n))))
#'cljs.user/is-odd?
cljs.user> (is-even? 4)
Compile Warning   <cljs repl>   line:1  column:2

  Use of undeclared Var cljs.user/is-even?

  1  (is-even? 4)
      ^---

#object[TypeError TypeError: Cannot read property 'call' of undefined]
	 (<NO_SOURCE_FILE>)
cljs.user>

This difference comes from a performance optimization in Piggieback, which avoids creating different REPLs for each ClojureScript form it evaluates.

You can learn more about this difference here.

Dealing with Dependencies

CIDER doesn’t handle automatically ClojureScript REPL dependencies when you’re doing cider-jack-in-cljs. You’ll have to configure those manually yourselves as documented in the subsequent sections of this manual.

Actually, CIDER will handle automatically the most important dependency - namely Piggieback. The problem with the other dependencies, however, is that you might need to install some external tools (e.g. node, shadow-cljs) and that ClojureScript development tools like Figwheel and shadow-cljs also require some setup to be useful.

CIDER will try to help you identify missing requirements by running a check, right before attempting to upgrade a Clojure REPL to a ClojureScript REPL. The nature of this check differs for the different REPL types:

  • For a node REPL we check whether the node binary is on your exec-path (Emacs’s version of PATH)

  • For tools like figwheel we check whether they are available on the classpath (by trying to require some of their namespaces)

We’ll discuss those checks further in the upcoming sections.

Limitations

CIDER currently doesn’t support self-hosted ClojureScript implementations. The reason for this is that there’s no self-hosted version of nREPL (implemented in ClojureScript) available today.

Another unsupported REPL is Rhino. Supporting in it Piggieback required a lot of ugly hacks and eventually it was decided we were better off without Rhino. Given the abundance of better solutions today, I doubt anyone’s going to miss Rhino anyways.

Additionally, some of CIDER’s features can’t apply to ClojureScript at all - see Feature support above for the details and the recommended alternatives.

Next

In the next section we’ll show you how to start a ClojureScript REPL with CIDER.