Is Clojurescript still worth it for a JS React dev?

I plan on using Clojure for backend - Datomic + Ions makes learning curve / investment in CLJ clearly worth it.

But I’m unsure of whether to use CLJS for front-end. I’ve explored ecosystem, and see how it could have definitely been a great choice a couple years ago – with Reagent/Re-frame or Fulcro. Prior to React’s hooks / context API, I’m sure this solved a lot of headaches. I was actually surprised to see how complete the dev story was. But as of now, these tools haven’t yet caught up to modern React and its new concurrent mode features. And while there’s new libraries like @lilactown’s hx or helix, they’re still relatively new.

Also, while react hooks might make easier to use React in cljs, they also make it less painful to use in js. With eslint (linting), typescript (static typing), jest (testing) and apollo/relay (data-fetching) the development experience is pretty good.

Which is why I wanted to discuss: is Clojurescript still worth it for a JS React dev? It’s a genuine question, and I’d love to be convinced that it is.

To be clear, I’m coming from a place where I’m confident with JS front-end development. But because I’m learning Clojure for backend, I’d also like to use it in front-end if trade-offs are worth it.

My main concern is the extra overhead involved in not embracing the platform, and in wrapping so many JS libraries for usage.

@thheller’s shadow-cljs provides better javascript interoperation, and he’s even added react native and expo support! But he’s only a single maintainer, so while I do believe he’s going a great job, I’m hesitant to build the backbone of my app on it. Plus, if there’s unsupported use-cases, such as using cljs with next.js, I likely won’t be able to contribute that work myself since I’m just getting started.

Here are three factors I find clearly superior in cljs development:

  • repl experience
  • immutability
  • clojure’s lisp syntax for composing components (rather than clunky jsx and js tertiary experiences)

I won’t be able to replicate the repl in JS land, unfortunately. But immutability can be done with Immer.js - though obviously the functional utilities wouldn’t be as nice.

Factors I find superior in js development:

  • embracing the platform
  • static typing with typescript (useful since there’s a lot of glue code in front-end, specially when passing component props around)
  • linting of graphql queries with eslint (along with checking for errors, makes it easy to locate API warnings/deprecations)
  • more completely testing story n logging via jest
  • easily use more complex libraries like Relay, Prosemirror, and Storybook

In terms of performance, I’m unsure of impact cljs has nor could I find much discussion of it online. There’s of course the extra cljs code overhead. But that has to be balanced with any libraries I’d use to makeup for what clojure gives me in js land (so maybe immer and lodash). Though I’m sure wrapping so many js libraries also adds to bundle size, too.

Another factor to consider is error handling, though I haven’t gone deep enough in a project to comment, it is yet another platform layer to account for when debugging, so figured I’d mention it.

Anyway, I’d love to hear about experiences using or not using cljs in newer projects, and if you’d like to correct anything I mentioned above, or expand on it, please feel free to do so. Obviously we’re biased here but still would be great to hear what clojure devs have to say, thanks!

9 Likes

I won’t have the answer you are looking for.

From your post, I can see a pragmatic approach, you’re asking about concrete tooling and frameworks and libraries. And looking to see if what Clojure(Script) has to offer there is worth learning Clojure(Script).

My take on this is, if you’re a JS dev looking to just get things done, learning Clojure(Script) is foolish.

The reason I would use Clojure(Script) is because I like the language better and have more fun doing so.

The reason you should learn Clojure(Script) is to try it out, learn new concepts, expand your horizon, and get to see if you also like it better and have more fun with it.

You can also get things done with Clojure(Script) once you know it. And once you know it well enough, you might even get things done faster and with higher quality.

So my pragmatic take, if this is a side personal project, absolutely use Clojure and ClojureScript! Even use joker or babashka for all your scripting needs. And go ahead and learn yourself Emacs too! :wink::grin:

If this is a work project, thread carefully. In that case, I’d start with a little bit of Clojure first. So in your case, maybe go for backend only, do frontend still in JS for now. Or the other way around. When I started, I started even smaller, I did the backend in Java and only had component of it in Clojure. And then slowly over time did more and more in Clojure until I did a full backend in Clojure and then switched to some ClojureScript as well, etc.

I would say that for introducing any new language to work, but especially any radically different in style and paradigm language.

Just my 2 cents.

9 Likes

I’ve used CLJS to write several web apps over the past few years, and before that I worked with JS and TypeScript. I’d highly recommend using CLJS, IMO it’s still miles ahead of JS. In addition to the reasons you listed, I’d also consider:

  • re-frame gives you a fully data-driven architecture, parts of which are difficult to implement in JS (e.g. expressing effects as data).
  • DataScript is extremely good for storing your app state. I’m not aware of anything analogous for JS.
  • CLJS has a much simpler build system than JS. On every JS project I’ve worked on, I’ve had to fight the build system at least a little bit, but I’ve never had any problems in CLJS.
  • There is far less churn in the CLJS ecosystem. re-frame is now 5 years old, and AFAIK hasn’t changed fundamentally in that time.
  • clojure.spec is excellent for verifying your app state is always correct, and can be used to check constraints that are difficult/impossible to express in a type system (e.g. that a value should always be an integer above 0).
  • Since you’re using Clojure on the backend, you can share code between the backend and the frontend. This can be very useful for things like specs and validation.

I’d also echo what @didibus said about the advantages of learning CLJS. It’ll give you a new perspective on front end development, and make you a better JS programmer.

From what I understand of hooks, they’re a way of using local state in your components without having to use the JS class syntax. Since both Fulcro and re-frame advise that you to keep all your app state in a single place, it shouldn’t be necessary to use hooks. I’m not too familiar with concurrent mode in React, so I can’t really comment on that.

I wouldn’t worry about performance. As far as I know, the Closure compiler (which CLJS uses) is still the best compiler for optimising bundle size. I also wouldn’t worry about using shadow-cljs, it’s being actively maintained and funded by Clojurists Together.

With regards to wrapping JS libraries, you can use the libraries directly if they’re in cljsjs, as almost all popular JS libraries are. It’s very easy to do this, you can basically use the JS library like a CLJS library. However if a library you want to use isn’t in cljsjs, and you want to use advanced compilation, you’ll have to create externs files which can be quite difficult.

I hope that covers all your concerns, and gives you some food for thought :slight_smile:

7 Likes

I’m rebuilding our app (Java/Wicket, ~170KLOC) in Clojure/Script (currently ~33KLOC) using Re-frame. I find the Re-frame approach to be just so much simpler and more powerful for UI development than anything I’ve ever used in Java or JavaScript (or even tools like Visual Basic) that I’d have a tough time going back.

I don’t use many libs from the npm ecosystem because I don’t find much of a need to. So far it’s just React (via Re-frame/Reagent), a drag-and-drop lib, and a WYSIWYG editor lib.

Runtime performance is not an issue. Our app feels pretty snappy even in mobile browsers.

One thing I would really miss with a JS front-end is the ability to share code between the UI and back-end. About a third of our code is written in CLJC and it’s vital to our app architecture that we’re able to execute the same business logic in both the front- and back-end.

Hope this helps.

2 Likes

Learning and using Clojurescript may not make sense only in specific cases:

  • If you’re not using Clojure on the back-end. Clojurescript does not require to be used with Clojure, but its strongest appeal is when it is backed by it;

  • If no one in the team familiar with any kind of Lisp dialect and aren’t very excited to start using one;

  • If you’re writing an open-source library, that to be consumed from both - Cljs and JS. Even if it solves specific problems very efficiently, I’m afraid it won’t be very popular among JS devs;

In any other cases, Clojurescript is soooo much better:

  • True REPL gives you real, not theoretical, not imaginary, but quite bona fide productivity boost. And no, Javascript does not have a REPL, at best it has an “interactive environment”. REPL is much more than a thing where you type a command into it and it spits out some statements;

  • Immutability by default alone has huge benefits;

  • With Cljs there’s consistency. In Javascript, because you don’t have a standard library - there’s almost none. Instead, you have Lodash, Rambda, Immutable.js, folktale, crocks, fantasyland, etc. In Clojurescript for example, if you need to get the size of a set, vector, list, hash-map - there’s a single function. In JS you have to know - sometimes it’s .length, sometimes it is .size and there are many examples like that;

  • In Javascript, there’s a precedence table with over twenty items in it. Clojurescript doesn’t even need one. There are like seven different things that can be “falsy” in JS, in Clojurescript only two - false and nil. These may seem to be like small things, but all that adds up;

  • Add to that, an enormous churn from libraries and tooling, Webpack alone (even though it is really good) may frustrate you for hours and days;

  • And like front-end wasn’t enough, Javascript today is in the back-end as well, and although it is the same language, there are many inconsistencies you have to deal with, while Clojure and Clojurescript feel very much the same language, even though they are hosted on completely different platforms. You actually can share code, the promise that has never gotten fulfilled for me with Nodejs;

  • Also Javascript has syntax. And OMG what syntax it is. It has way more parentheses and also commas, semicolons and curly braces. In Clojurescript, parentheses give you structure and consistency, you stop noticing them after a while. In Javascript there’s no consistency - arrow function with multiple params requires parens, with a single param - parens can be omitted, but with no params - parens are still required. Before Prettier I never knew how to structure JS code for readability. Prettier simplifies things, but it still has so many options that vary from team to team, from a codebase to codebase. Single misplaced comma can make you want to smash your laptop. Write JSX for long enough and you’d get to a point where excitement from JSX turns into incurable frustration. In Clojurescript you don’t even think about these kind of problems:

Today, if I have to write anything in Javascript and not allowed to use Clojurescript (say for an interview challenge) I still would prototype it in Clojurescript and then by-hand convert it to Javascript. Although it feels like twice the work, I am able to quickly build correct solution way faster.

7 Likes

thanks for such a great write-up on this topic - i’ve been having this internal debate again recently :raised_hands:

i was in the JS world and pretty much satisfied with React (especially after Angular) and then got into ClojureScript because it felt good/right/exciting

since i am the CTO of my current and previous company i was able to choose Clojure and ClojureScript from the beginning - i loved it and i was convinced i would be more productive than in JS

at some point i started having doubts, especially when my previous company was not doing great and i knew many other entrepreneurs writing in RoR+React after some 3-month bootcamps, StackOverflowing their way out and actually building profitable companies

so i did some frontend JS again to see and compare - i ended up with these thoughts:

  • i can’t stand the syntax, even more since they introduced the class thingy or worse because now it’s all TypeScript
  • i can’t stand JSX, it’s just hard to read and unnecessary
  • i can’t stand the npm-breaking-changes mentality

i don’t know if there’s some research on that but i believe a happy programmer is a better programmer so just for that i’d stay with CLJS :smiley:

nevertheless, you raised a valid point regarding “embracing the platform”: when i wasn’t using Shadow i got frustrated by wrapper libraries and externs etc - but now if feel Shadow is pretty great we don’t need to wrap too many things and can use js directly as if it were a CLJS lib (and my company is sponsoring Thomas to help a little bit :slight_smile: )

for me re-frame is still the best way to build predictable UIs while enjoying doing so - “economy of expression” does spark joy!

and building a business with CLJS is an excellent filter to hire devs with higher standards :nerd_face:

2 Likes

Is this still the case? With shadow-cljs you can use any npm library + its externs inference makes away with the need to create externs with a few simple ^js annotations? (@thheller & other experienced shadowers would need to comment how often and how well it works in practice)

I’m just a noob but I never had any problems (besides learning to code :sweat_smile:) with npm stuff and shadow.

That’s true, I was forgetting about externs inference. I’ve had trouble with the CLJS compiler’s externs inference, but the inference in shadow-cljs does seem a bit better (although I haven’t used it extensively).

Using ClojureScript on the frontend gives you many small advantages that are often overlooked, but together add up to a significant boost. From my experience, having much (if not most) code in cljc files and sharing it between backend and frontend is one such boost. Another is having the same data structures, with the same validation (spec), or avoiding third-format serialization altogether (I use Sente to pass Clojure data structures back and forth).

I do agree that the JavaScript/React world is making great progress, but it always seemed to me they are mostly solving problems that I do not have in the first place.

2 Likes