Supercharge Your JS/TS Project with ClojureScript REPL

reedho

Muhammad Ridho

Posted on April 15, 2023

Supercharge Your JS/TS Project with ClojureScript REPL

If not yet, first install the Java SDK on your machine, as well as node and pnpm itself.

You may have existing project that you can supercharge. As an example, here we start by creating brand new Vite project.

$ mkdir -p /tmp/hi && cd /tmp/hi
$ pnpm create vite@latest . --template react-ts
Enter fullscreen mode Exit fullscreen mode

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

Now, add shadow-cljs.

$ pnpm install --save-dev shadow-cljs
$ pnpm exec shadow-cljs init
$ pnpm exec shadow-cljs info
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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.

pnpm exec shadow-cljs browser-repl
Enter fullscreen mode Exit fullscreen mode

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=>
Enter fullscreen mode Exit fullscreen mode

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 REPL 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 or VSCode. If you just starting, i suggest to setup Calva, the get 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:

(ns my.app
  (:require ["react" :as react]))
Enter fullscreen mode Exit fullscreen mode

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=>
Enter fullscreen mode Exit fullscreen mode

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. A tiny (130B to 205B) and fast utility to randomize unique IDs of fixed length.

Open new terminal shell and do:

$ pnpm install --save uid
Enter fullscreen mode Exit fullscreen mode

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

(ns my.app
  (:require ["react" :as react]
            ["uid" :as uid]))
Enter fullscreen mode Exit fullscreen mode

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

;; call uid function
(uid/uid)

;; call it 100 times, return a list of 100 random uid
(repeatedly 100 #(uid/uid))
Enter fullscreen mode Exit fullscreen mode

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

(defn new-rand-div []
  (let [x (. js/document createElement "div")]
    (set! (.-innerText x) (uid/uid))
    x))
Enter fullscreen mode Exit fullscreen mode

And test calling to verify.

(new-rand-div)
Enter fullscreen mode Exit fullscreen mode

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.

(let [x (. js/document getElementById "app")] 
  (dotimes [i 100]
    (. x append (new-rand-div))))
Enter fullscreen mode Exit fullscreen mode

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.

:cljs/quit
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
reedho
Muhammad Ridho

Posted on April 15, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related