Supercharge Your JS/TS Project with ClojureScript REPL
Tags: javascript, clojure, beginners, productivity
Published: 2023-04-15 09:27:49

If not yet, first install the [Java SDK](https://adoptium.net/) on your machine, as well as [node](https://nodejs.org/) and [pnpm](https://pnpm.io/) itself.

You may have existing project that you can supercharge. As an example, here we start by creating brand new [Vite](https://vitejs.dev/) project.

```bash
$ mkdir -p /tmp/hi && cd /tmp/hi
$ pnpm create vite@latest . --template react-ts
```

We now have a working project, verify with e.g. `pnpm run build && pnpm exec vite preview --open`.

Now, add [shadow-cljs](https://github.com/thheller/shadow-cljs).

```bash
$ pnpm install --save-dev shadow-cljs
$ pnpm exec shadow-cljs init
$ pnpm exec shadow-cljs info
```

The last command will take some time while grabbing all the required dependencies. Be patient, after this, if all the installation goes right, all the doubts will be paid off.

If your result is looks more or less like below, then congratulations, you have taken the very first step towards enlightenment.

```
shadow-cljs - config: /private/tmp/x3/shadow-cljs.edn
=== Version
jar:            2.22.10
cli:            2.22.10
deps:           1.3.4
config-version: 2.22.10

=== Paths
cli:     /private/tmp/x3/node_modules/.pnpm/shadow-cljs@2.22.10/node_modules/shadow-cljs/cli/dist.js
config:  /private/tmp/x3/shadow-cljs.edn
project: /private/tmp/x3
cache:   .shadow-cljs

=== Java
openjdk version "19.0.2" 2023-01-17
OpenJDK Runtime Environment Homebrew (build 19.0.2)
OpenJDK 64-Bit Server VM Homebrew (build 19.0.2, mixed mode, sharing)

=== Source Paths
/private/tmp/x3/src/dev
/private/tmp/x3/src/main
/private/tmp/x3/src/test

...
=== Dependencies
shadow-cljs - updating dependencies
Retrieving...

...
...

...
shadow-cljs - dependencies updated
[thheller/shadow-cljs "2.22.10" :classifier "aot"]
[com.bhauman/cljs-test-display "0.1.1"]
[cider/piggieback "0.5.3" :exclusions [[org.clojure/clojure] [org.clojure/clojurescript] [nrepl]]]
[io.methvin/directory-watcher "0.17.1"]
  [net.java.dev.jna/jna "5.12.1"]
  [org.slf4j/slf4j-api "1.7.36"]
[org.clojure/clojure "1.11.1"]
  [org.clojure/core.specs.alpha "0.2.62"]
  [org.clojure/spec.alpha "0.3.218"]
[com.cognitect/transit-cljs "0.8.280"]
  [com.cognitect/transit-js "0.8.874"]
[org.clojure/google-closure-library "0.0-20230227-c7c0a541"]
[thheller/shadow-cljsjs "0.0.22"]
[org.clojure/core.async "1.5.648"]
  [org.clojure/tools.analyzer.jvm "1.2.2"]
    [org.clojure/core.memoize "1.0.253"]
      [org.clojure/core.cache "1.0.225"]
        [org.clojure/data.priority-map "1.1.0"]
    [org.clojure/tools.analyzer "1.1.0"]
    [org.ow2.asm/asm "9.2"]
[com.google.javascript/closure-compiler-unshaded "v20230228"]
  [com.google.code.gson/gson "2.9.1"]
  [com.google.guava/guava "31.0.1-jre"]
    [com.google.code.findbugs/jsr305 "3.0.2"]
    [com.google.guava/listenablefuture "9999.0-empty-to-avoid-conflict-with-guava"]
    [com.google.j2objc/j2objc-annotations "1.3"]
    [org.checkerframework/checker-qual "3.12.0"]
  [com.google.guava/failureaccess "1.0.1"]
  [org.jspecify/jspecify "0.2.0"]
  [com.google.auto.value/auto-value-annotations "1.6"]
  [javax.annotation/jsr250-api "1.0"]
  [args4j "2.33"]
  [com.google.errorprone/error_prone_annotations "2.15.0"]
  [com.google.protobuf/protobuf-java "3.21.12"]
  [org.apache.ant/ant "1.10.11"]
    [org.apache.ant/ant-launcher "1.10.11"]
  [com.google.re2j/re2j "1.3"]
[ring/ring-core "1.9.6" :exclusions [[clj-time]]]
  [commons-fileupload "1.4"]
  [commons-io "2.11.0"]
  [crypto-equality "1.0.1"]
  [crypto-random "1.2.1"]
    [commons-codec "1.15"]
  [ring/ring-codec "1.2.0"]
[hiccup "1.0.5"]
[thheller/shadow-undertow "0.3.1"]
  [io.undertow/undertow-core "2.2.4.Final"]
    [org.jboss.logging/jboss-logging "3.4.1.Final"]
    [org.jboss.threads/jboss-threads "3.1.0.Final" :exclusions [[org.wildfly.common/wildfly-common]]]
    [org.jboss.xnio/xnio-api "3.8.0.Final" :exclusions [[org.jboss.threads/jboss-threads]]]
      [org.wildfly.client/wildfly-client-config "1.0.1.Final"]
      [org.wildfly.common/wildfly-common "1.5.2.Final"]
    [org.jboss.xnio/xnio-nio "3.8.0.Final" :scope "runtime" :exclusions [[org.wildfly.common/wildfly-common]]]
[thheller/shadow-client "1.3.3"]
[fipp "0.6.26"]
  [org.clojure/core.rrb-vector "0.1.2"]
[org.clojure/google-closure-library-third-party "0.0-20230227-c7c0a541"]
[org.clojure/clojurescript "1.11.60" :exclusions [[com.google.javascript/closure-compiler-unshaded] [org.clojure/google-closure-library] [org.clojure/google-closure-library-third-party]]]
[org.clojure/tools.reader "1.3.6"]
[org.clojure/tools.cli "1.0.206"]
[org.clojure/data.json "2.4.0"]
[nrepl "1.0.0"]
[com.cognitect/transit-clj "1.0.329"]
  [com.cognitect/transit-java "1.0.362"]
    [com.fasterxml.jackson.core/jackson-core "2.8.7"]
    [javax.xml.bind/jaxb-api "2.3.0"]
    [org.msgpack/msgpack "0.6.12"]
      [com.googlecode.json-simple/json-simple "1.1.1" :exclusions [[junit]]]
      [org.javassist/javassist "3.18.1-GA"]
[expound "0.9.0"]
[thheller/shadow-util "0.7.0"]
```

Oh well, please don't feel intimidated. Those Clojure dependencies is much much more simple if you compare with all those in npm node_modules (e.g. `pnpm ls --depth 15`).

---

Ok, for now, i assume you already understand and done with the JS or TypeScript part, its the same business as usual.

Now, the **supercharge** part is, how we can have Clojurescript REPL to begin the enlightenment.

```bash
pnpm exec shadow-cljs browser-repl
```

If all goes well, we'll be welcomed by the cljs repl as well as an open browser window where every evaluation we put on the repl will be executed in this browser window.

```
shadow-cljs - config: /private/tmp/x3/shadow-cljs.edn
shadow-cljs - server version: 2.22.10 running at http://localhost:9630
shadow-cljs - nREPL server started on port 65047
[:browser-repl] Configuring build.
[:browser-repl] Compiling ...
[:browser-repl] Build completed. (119 files, 118 compiled, 0 warnings, 10.19s)
cljs.user=>
```

This repl is pretty much has the same purpose like the browser console, where we can type a valid expression to quickly check the result. However, this is more or less of a powerful [LISP](https://en.wikipedia.org/wiki/Lisp_(programming_language)) [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) instead of JavaScript console.

With this repl, we can do anything allowed in real time, interacting with the live runtime, call all available JS apis as well as modifying every aspect of it **live**, -- without restart, reload or anything like that.

We'll get the best experience interacting with this repl with our own lovely editor, e.g. [Emacs](https://www.gnu.org/software/emacs/) or [VSCode](https://code.visualstudio.com/). If you just starting, i suggest to setup [Calva](https://calva.io/), the [get started](https://calva.io/getting-started/) guide is so newbie friendly. 

For this post, we'll simply interact with the command line repl to show some demonstration. 

Paste this in the repl: 

```clojure
(ns my.app
  (:require ["react" :as react]))
```

Now the `namespace` named `my.app` has been created and our active namespace has moved from `cljs.user` to `my.app`, as shown by the prompt.

```
my.app=>
```

This namespace has now have access to react js module via a symbol named `react`. The process of binding symbol to value within a namespace called as *interning*. 

Lets try install new npm package, e.g. [uid](https://github.com/lukeed/uid). A tiny (130B to 205B) and fast utility to randomize unique IDs of fixed length.

Open new terminal shell and do: 

```bash
$ pnpm install --save uid
```

Back to our cljs repl, we now can update the `my.app` ns to intern this new installed library.

```clojure
(ns my.app
  (:require ["react" :as react]
            ["uid" :as uid]))
```

Now that we have access to `uid` module, we can call the `uid` function provided by the `uid` module, like so:

```clojure
;; call uid function
(uid/uid)

;; call it 100 times, return a list of 100 random uid
(repeatedly 100 #(uid/uid))
```

Next, we define a function named `new-rand-div`, it will return new HTMLDivElement with a random uid string inside.

```clojure
(defn new-rand-div []
  (let [x (. js/document createElement "div")]
    (set! (.-innerText x) (uid/uid))
    x))
```

And test calling to verify.

```clojure
(new-rand-div)
```

Now, lets activate the browser window that is opened during the start of the repl, if you inspect that window, it has div element named "app". We will modify the dom to show how we interact with the runtime to modify the page with ease.

```clojure
(let [x (. js/document getElementById "app")] 
  (dotimes [i 100]
    (. x append (new-rand-div))))
```

Verify that the browser window now displays those 100 random uids.

Okay, that is for now. I hope it is enough to convince how we have been supercharged, even if only just by a litle. 

Be nice, save some resource, we should now quit the repl to stop it.

```clojure
:cljs/quit
```

Thank you for reading, let me know if you need more of this kinds, i'll spare some time to create follow up posts, which should be moar interesting.