ClojureScript, D3, and Reagent
I recently set out to see what a small ClojureScript app might look like. I have a need at work for a React-based app that will ingest quite a bit of data and display it in a dashboard format for the end-user. I figured it would be a good time to take a another look at ClojureScript and see how things have evolved and whether the ecosystem is stable enough to support us.
A Few Words About ClojureScript
I should start by saying that I’ve been looking for an excuse to try ClojureScript out. There’s a great deal I like about it, not the least of which is bringing some sanity to an insane language. (To see that insanity, watch the Wat lightning talk by Gary Bernhardt starting near 1:22 into the movie. It’s a short video and well worth watching, if not for laughs then for the sheer craziness of some languages.)
I also like that:
- Data is immutable by default
- There is a wealth of operations you get to operate against said data
- There is potential for smaller, faster code due to the Google Closure compiler being used under-the-hood. One of the many advantages is that Google Closure can do dead-code elimination, allowing anything that isn’t being used to be removed
- It’s a functional language
I should also mention I tried ClojureScript a while back, but didn’t feel like ClojureScript itself was polished enough yet and that the tooling around developing ClojureScript apps was fragile. Kudos to those who kept at it though, because I think the tool support is much better today–though there is still room for improvement.
That said, there are some downsides:
- You need to pull in a fairly large Java ecosystem to use ClojureScript
- The stack traces tend to suck
- If you’re not used to reading Lisp, it can be a hurdle. I still don’t know what elegant, documented ClojureScript looks like. Most of what I find is hard to follow, dense, and lacks any source code comments
- The compiler seems to suffer some fairly serious size regressions occasionally. My app was about 280 KiB, and then jumped to nearly 450 KiB due to several changes in the compiler.
UPDATE: The size regression is not a regression. The compiler did indeed have a bug where it was not including foreign libraries correctly. So it seems I’m paying about 130 KiB in overhead to have ClojureScript involved on this otherwise small app. However, I am getting the full benefit of immutable data structures (which you would have to include something like Immutable.js to get, adding another 55 KiB to the size), and better infrastructure for future optimizations when the app grows (and it will grow) and the Google Closure library which is optimized too. So the cost/benefit analysis still isn’t clear.
There’s a pretty large gallery of examples available for perusing–just to give you an idea of what is possible.
For my app, I need smaller (roughly 208px by 208px) graphics that are rich with information about that part of the system. In some cases, I have 4 or 5 pieces of relevant data that need to go into that small area that are easy to parse at a glance, and D3 was an excellent solution for that. Later, as we incorporate more metrics, we’ll likely lean on D3 again to help us draw a few more charts.
React and Reagent
On real hangup with React is the integration with D3. D3 really wants a DOM
node, but you don’t get real DOM nodes in the render routine. The standard
practice is to implement a
componentDidUpdate() method that will call out to
your D3 code to draw the visualization. This wasn’t too hard to coordinate,
though it does involve a bit of boilerplate.
With ClojureScript in the picture, it also made sense to look at Om and Reagent. Both build on top of React, but each takes a different strategy to exposing React. I don’t think Om is the right API, and apparently, neither does David Nolen. He’s been working hard on Om.Next, and I have to say that I really like the direction it’s going in. Unfortunately, it’s in an alpha state right now, and I can’t honestly propose it for use yet in our project. I’ll be keeping an eye on it though.
So, I chose to use Reagent instead. Reagent encourages keeping your global state in an atom, and then divvying out that state to the various components. It’s not hard to see how this fits our application well: we have a large amount of state that we want to partition between many components. However, some components need to interact with D3, and that’s where the pain began.
It turns out that it’s hard to find a good example of what to do here, so I relied on my previous work with React to help guide the way. That only got me so far though, as Reagent’s model is different. It likes to use a special kind of atom and dereferencing that in the render method is how Reagent pays attention to whether a component should be re-rendered or not. While there’s a quite a bit said around this, none of the documentation comes and states specifically that it has to be dereferenced in the render method. My guess is that in the 90% use-case, the function you are writing is the render method, so it doesn’t need any more clarification. But if you need to create something more specialized, this fact is really important.
In the end, I settled on a form like this for my D3 components:
It took a while to really understand everything that needed to happen, so let me
explain it. First, we really need a dom node to render the D3 gauge. To
capture this, we use a Reagent atom to capture the dom node and we set the value
component-did-mount function using
reset!. Unfortunately, just
setting this won’t cause Reagent to try and re-render our component so that we
can draw the gauge. Instead, we must deref
dom-node in the
method so that Reagent knows that we depend on it. Also note that a
Hiccup-style notation is used to
specify the HTML elements in the
Now that we have that in place, we can draw the D3 component when an update
happens. Next, we add a
component-did-update method to draw the gauge. Now,
I tried lots of things involving cursors into the state, but it was all overly
complicated, and none of it was as efficient as it could be. I had to dig into
Reagent’s source and really examine what was happening. It turns out that
Reagent–probably rightfully–doesn’t peek inside arguments that are cursors to
see the value. Instead, it works best to use the cursors in the parent, and
deref them in the parent’s render routine. In my case, it may look like:
The advantage of this is that Reagent will do some really smart things. First,
should-update-component default method will do a lot for you. It’ll
compare the previous args to d3-gauge to the current ones to determine if the
component needs to be re-rendered. Since we have a substantial amount of state,
we really want this–I don’t want anything to be re-rendered unless the data
behind it has changed. Creating cursors in the render method and the derefing
them defeated this optimization, which is why I steered away from doing it.
The next headache was that
component-did-update provides the old arguments to
the routine, but not the new ones. It’s easy to get to, you just need to use
argv function to obtain them.
argv will return everything,
not just the arguments to the function, but a reference to
d3-gauge as well
(it’s the entire contents of the Hiccup form). You’ll need to peel off the
first value and discard it, then pass the rest to the function that will
actually draw the D3 visualization, along with the dom node to use as the
I felt like I spent far to long trying to figure out how to make this work–a better example would have done wonders to improve the situation. Hopefully, someone can benefit from my pain and find some use for what I’ve managed to work out. Now that I finally understand what is happening, how it’s happening, and why, I think most everything should fall into place. I’m still not quite convinced ClojureScript is the solution I want to go forward with though–the size regression mentioned earlier was really disappointing and is eating away the majority of the reason for considering ClojureScript in the first place. I filed a ticket for the issue, so let’s hope that there’s an easy resolution to the problem, and we can keep moving forward with this solution.
Despite some of the heartache, and the incredible amount of time I spent tracking down all kinds of little issues, it’s been quite fun. I find that I like LISP-y languages–they remind me an awful lot of Python. In the case of Clojure, I feel like it’s a better Python–you get the power of the JVM and its JIT engine, along with the optional typing facilities to help speed things along. It was really nice to see how much the tooling around ClojureScript has come along. There are still snaggles but, on-the-whole, it’s worlds better than it was.
Best of all, it was fun to see how working with such a functional language changed the design. In many ways, I believe it’s more succinct. In others, I believe it’s worse–the “what does elegant Clojure code look like” problem. I don’t think it’s anything that can’t be addressed, but it’d be nice to have some help paving that path with useful style guides and examples.