I had the same problem a few years ago when I started learning web development in Clojure/Script. At the time I tried learning from a “Clojure Web Development Essentials” [Packt], which covers a much older version of Luminus. What I discovered was what you seem to be getting at: Luminus does not teach web development from the ground up. It’s more a template of defaults, and in fact I found that it actually hides a lot of important things from me. More than anything else, the book just introduced a tonne of dependencies and while it tried to explain each one, I didn’t come away with a good sense of how they all fit together.
I then chanced upon this tutorial:
http://clojure-doc.org/articles/tutorials/basic_web_development.html
(Sidenote: this site is brilliant for learning various aspects of the Clojure language.)
The above tutorial taught me everything about the core of what I need to know about: Ring. Once you understand Ring, you can understand how everything else which is built on top of Ring works.
Now, getting back to the meat of your question, as dorab mentioned, you basically need two things, a :dev
alias, and the most important thing in the whole universe, a dev
directory under your project root, containing at the very least a user.clj
file which defines the user
namespace. I can’t remember where I found out about this originally, I think it was in a post either by Stuart Sierra or referenced in one of his posts, but once I learned you could do this, everything fell into place afterwards. Let me elaborate a little.
My web projects are generally organized as follows:
- src/ - contains all server-side namespaces
- cljs-src/ - contains all CLJS code and .cljc files
- dev/ - contains user.clj which defines the user ns
- test/ - contains server-side and client-side tests, including UI tests
My deps.edn file will contain under :deps
all the namespaces that the production website required. Under the :aliases
section I will additionally define e.g. the following:
Excerpt from deps.edn
:aliases
{:fig {:extra-deps
{
;; NOTE: Cider Jack-in CLJS includes piggieback implicitly;
;; this is so that we can start it from a CLJ REPL;
cider/piggieback {:mvn/version "0.5.2"}
com.bhauman/figwheel-main {:mvn/version "0.2.14"}
com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}}}
;; This will feature extra data which we only want our rftest to pick up for now;
:dev {:extra-paths ["dev"
"test/core"
"test/unit"
"test/ui"
"test/main"]
:extra-deps {ring/ring-devel {:mvn/version "1.9.4"}
etaoin/etaoin {:mvn/version "0.4.6"}
lambdaisland/kaocha {:mvn/version "1.0.902"}
entmorph/test-utils {:local/root "../test-utils"}
}}
:build {:extra-paths ["build"]
:extra-deps {io.github.seancorfield/build-clj
{:git/tag "v0.3.1" :git/sha "996ddfa"}}
:ns-default build}}
The :fig
alias contains everything I require to start a figwheel CLJS REPL with hot-reloading. The :dev
alias contains everything I require to launch my dev CLJ environment. I run these together, so from the command-line it would be:
clj -A:dev:fig
(I actually run this through emacs so I can have my REPLs accessible from within emacs. If you want me to elaborate on how to do this, please let me know.)
So, one important aspect of my :dev
alias is the :extra-paths
key which includes "dev"
. This is responsible for loading my user.clj
file, which corresponds to the user
namespace in which you start when you first fire up a REPL. So the big question is, what do you put in here? Really, anything you want which you only require for development. In my case, I define the following:
- functions to start, stop and restart a ring server
- a set of development/testing routes which are only for me to play around with and which should never make it to production
- functions for launching various figwheel builds, specifically one dev and once test version
- a function which I call from as part of a figwheel post-build hook which waits until the test figwheel build is finished and then runs some UI tests to make sure that none of the changes I have made in my client-code break anything
But the main thing to note is that your entire application entry point during development centres around this user.clj file and what you put in it. And the best part is that you can :require
any namespace from your application and any vars from those namespaces, and pretty much spend most of your time developing from within the user namespace.
One more thing worth mentioning: you’ll notice I have included ring/ring-devel
as a dependency under :extra-deps
in the :dev
alias. This is the correct way to use this library under deps.edn
. Again, I only include it (and also for example reitit.ring.middleware.dev
which comes from the excellent reitit
routing library) in my user.clj
.
And so this provide a clean separation between what is build in production, and what is built in development. In terms of environment, you can pretty much use EDN config files in the root of your project directory, or go for something like environ
.