In Understanding htmx I discussed some of the trade-offs of building a primarily server-side-rendered app. If you'd like to use Biff but for whatever reason you've decided that you'd prefer to use ClojureScript and React instead of htmx, I've created an example repo that demonstrates how to integrate re-frame with Biff. The steps are split up into separate commits.
Read on for commentary. You'll want to create a new Biff project before you start.
NOTE: This how-to was made with a pre-v1.8.0 version of Biff, so some of the steps are different now. I'll try to update this before too long.
Diff. I generated
package-lock.json by running
npm install react react-dom; npm install --save-dev shadow-cljs. Then I generated a separate re-frame project via
lein new re-frame reframe-template and mostly just copied and pasted the contents of that into my Biff project.
At this point, you can run
bb dev to start the backend, and then in a separate terminal tab run
npx shadow-cljs watch app to compile the CLJS code. Open
localhost:8080, sign in, and then you should see "Hello from re-frame." (I've structured the app so we still use server-side rendering for the landing page and authentication flows, and then after you've signed in you're taken to the re-frame app.)
In a subsequent step we'll have
bb dev start Shadow CLJS for us.
Diff. Ta-da... the app is now fully functional, including the websocket-powered chat. I went through the contents of
app.clj and re-implemented it in CLJS.
I'm no re-frame expert; I'd studied it a bit several years ago but have never built anything with it. So there may be better ways to do things, but this should at least get you started.
The websocket integration in particular is not very robust. If you're actually planning to build something with websockets, you'll at least want to add reconnect logic, and maybe use Sente.
Although Biff provides an
/auth/signout endpoint by default, that one redirects to the landing page afterward, which causes the browser to do an additional, unnecessary
GET / when you sign out. So I added a different signout endpoint that just returns an empty response.
Diff. We'll need to modify
bb deploy and
bb soft-deploy so that they also compile the CLJS code. But to do that, we'll first need to copy-and-paste the relevant functions from
com.biffweb.tasks into our project so we can modify them.
It feels a bit clunky that we have to copy over almost 100 lines of code just so we can add some CLJS compilation in three spots. Maybe in the future I'll make the default tasks more configurable so this isn't necessary. For now we'll roll with it.
Don't forget to update
bb.edn so that
bb uses the tasks in your project instead of the original ones defined in
Diff. Now that we have the task source where we want it, we just need to insert a few shell calls to
npx shadow-cljs. The default tasks already have a CSS compilation step (with Tailwind), so we can just look for the spots where CSS gets compiled and then do the same for CLJS.
We'll also need to remove the
bb prod-dev command from
bb.edn. Normally, that command watches the filesystem and then runs
bb soft-deploy whenever you hit save. However,
npx shadow-cljs release app is a bit too slow (20 seconds on my machine) for that to be a useful workflow. Instead you'll want to do your development locally with
bb dev and then run
bb deploy or
bb soft-deploy manually. You can still get an nREPL connection to the server with
(If you did want to still use
bb prod-dev, you would need to copy even more code over to your project so that the command would call our custom
Published by Jacob O'Bryant on 5 Dec 2023