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.
Diff. I generated package.json
and 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 clj -M:dev 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 clj -M:dev 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 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 the dev
, deploy
, soft-deploy
, and uberjar
tasks so they compile your CLJS code. We also need to make Dockerfile install npm
since it's used for CLJS compilation.
The general approach is that for each task we add a custom function to dev/tasks.clj
, have it run the Shadow CLJS command, and then have it delegate to the original task function for the remainder. If we had to make deeper changes, we could copy the task functions' implementations into dev/tasks.clj
instead of delegating.
We also update the :biff.tasks/deploy-untracked-files
config option so that the deploy
and soft-deploy
tasks will copy our compiled CLJS to the server.
Published by Jacob O'Bryant on 5 Dec 2023