When I started the Chlorine project, I just thought it would be great if I could target all Clojure-like REPLs that already exist but didn’t have tooling support. At the time, this would include Lumo and Plank, mostly. Also, Shadow-CLJS and Figwheel have some “clunky run-some-code-and-transform-in-cljs” way of working that simply didn’t click with me.
Now, almost a year later, Chlorine supports Clojure, ClojureScript (Shadow-CLJS, Lumo, Plank, or even over clj
), ClojureCLR, Arcadia, Babashka, Clojerl (Clojure on Erlang) and Joker (Clojure on Go, also a linter). But the reality is that working with a pure Socket REPL is really hard – a socket REPL works exactly like a regular one, printing namespaces after each code, and so on. Also, there are some strange decisions on some REPLs, mostly likely ClojureScript (that is the second most used Clojure flavor), so things are not always easy. To put things in perspective, currently Chlorine uses 3 ways to evaluate code: It uses unrepl, that only works on Clojure, or uses internal APIs of shadow-cljs (that obviously only works for shadow-cljs), and for other implementations it uses a kind of a hack – it evaluates the code, inside a try
…catch
, and it returns a vector where the first element is a symbol in a specific format that Chlorine will understand and then link that with the response. This “hacky way” is currently being used for every other implementation except Clojure and Shadow-CLJS. Things work (autocomplete works too), but it is not pretty and sometimes have strange results.
As a matter of fact, I was already thinking about removing UNREPL (it’s really hard to implement new features on it, and some good ideas only work in theory – for example, the ability to evaluate long strings / collections and render only a part at a time aren’t that good with lots of edge-cases) and, to do it, I though about a better, non-hacky way to evaluate things on some Socket-REPLs (that, again, would only work on some REPLs – ClojureScript REPLs will probably never support “upgradable REPLs” because of the way they work) – the only thing that I had to understand is how to implement this “upgraded REPL”…
Then, recently, Babashka added an initial support for nREPL, with an insane low amount of lines. So, I’ve tried to implement a way to evaluate code over nREPL… and it was really simple to do it, using a npm library that already did it. But implementing like this meant that the user would need to know if the host/port to connect is a Socket REPL, or a nREPL (and the user does not know – lots of tools like lein
and shadow-cljs
show an nREPL port to be connected).
(more…)