Ever since I first ran into the Metosin libraries, I’ve been using many of them. One of the most known and used of those is probably reitit, a routing library that can be used both on the server and in the browser. Recently I’ve had more opportunities using ClojureScript in the browser, and I noticed something that was confusing for a few moments.
In kitsune I use shadow-cljs to handle the ClojureScript side of things. It deals with dependencies, it has a very smooth development experience with hot reloading and tons of ways to tweak everything to my needs. I only have a faint understanding of how it handles hot reloading, but the internals aren’t really important for now. What is, is understanding that reitit “compiles” its routes when the router is made, and this can lead to some tricky situations—I ran into something similar with using the ring session middleware.
In the frontend, reitit routes look something like this:
(def routes
[["/kikka" {:view views/root-component}]])
The root-component
is expected to be a reagent component for example. What I was noticing is that when I’d update this root-component
, hot reloading didn’t work. Interestingly though, when I’d update other views referenced in root-component
, hot reloading worked just fine. It took the frustration of a few slow full page reloads to get a change reflected, that I recalled the case of the session middleware and it clicked.
When reitit compiles its routes on “boot”, it copies things around to each route, and I figure it also uses the actual root-component
function itself, instead of the symbol that points to the function. So even when shadow-cljs recompiled the views
namespace thus updating the actual function, the old version lived on copied into reitit’s routes. Either the page had to be reloaded, or the namespace where the routes are initialized had to be recompiled.
Luckily, in this case the solution is not complicated: don’t let it copy the component function. The way to pull this off is to give it the var, the “pointer” to the function instead of the function itself. In this case the routes will look something like this:
(def routes
[["/kikka" {:view #'views/root-component}]])
This works just as well and while I guess there is some runtime cost for dereferencing the var, at this point I’m happy with paying that cost if it means hot reloading works with views no issue. Also considering that this works out of the box, I assume that it’s an intentional feature of the library to work around this issue.