A Note about version numbers
At the time of this writing, there were several newer versions of many of the dependencies used in Chestnut. In particular, the ClojureScript core team had fairly recently released a new version with vastly simplified REPL setup requirements which triggered changes to many of the related ClojureScript tooling libraries (Piggieback, and Weasel especially). So PLEASE! Use the latest version of Chestnut, or if you’re setting up your own project then look up the latest versions on Clojars
On to the Annotating!
As I wrote recently, I recently dove head first into doing web development with Clojure and Clojurescript. Along the way I learned a whole heck of a lot about how to actually set up a Leiningen project to support a nice workflow for such a project. However, most of my new-found knowledge has already been put together into a very nice package called Chestnut. However, at first glance (and second and third glance really) Chestnut projects are complex and intimidating.
This post is an annotated walkthrough of the configuration that a new
Chestnut comes with. The best way to follow along would be to start
off by running
lein new chestnut tour in shell and then exploring
the files as I talk about them.
Let’s start by looking at the base directory.
1 2 3 4 5 6 7 8 9 10 11 12
If you’re familiar with Clojure development at all, this should look
very familiar to you. But there a few unusual things: the
system.properties files, and the
env folder. The
is a file for letting Heroku know how to run your app, and the
contents of it are explained well in the
Heroku guide to getting started with Clojure.
If we use the awesome [tree][tree] program to visualize some of these
directories we can see some interesting stuff. First,
pretty standard for Clojure, with the addition of a second tree for
1 2 3 4 5 6 7
The same is true of the
test folder. But what’s really interesting
is what is in that new
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
What we see is that there are three top-level directories underneath
env, and below each of these we see what looks like normal Clojure
and Clojurescript source trees. Veeery, interesting.
Now let’s jump and take a look at the
project.clj. This is where the
heart of the action is. The first is boring normal project meta-data,
and we’re going to skip it. The next section is interesting though.
1 2 3 4
:repl-options is straightforward. It just increases the timeout
when launching a REPL. Presumably this is because Chestnut REPL’s are
so filled with awesome that they take longer to load ;) The
options are simple too. They just override the default place that
Leiningen looks for Clojure source and test files by default.
N.B. The version of Chestnut I generated this with actually has a bug, since it generated “spec/clj” as the test path, but no “spec” folder.
Next come the dependencies and plugins needed by this project.
1 2 3 4 5 6 7 8 9 10 11
Clojure and Clojurescript are obviously necessary, and when you’re using Clojurescript with Leiningen, you probably want the [lein-cljsbuild][cljsbuild] plugin for compiling your Clojurescript. Ring is the standard Clojure web application’s library. Ring-defaults is a library for providing standard configurations of Ring middleware. Compojure is a routing library built on top of Ring.
I’m not quite sure why the Clojurescript dependency is marked as
Enlive is a “a selector-based (à la CSS) templating library for Clojure.” Very cool stuff, and wildly useful for many other HTML processing/producing/consuming tasks than just templating. Unfortunately very under-documented. Om is the only Clojurescript specific dependency in the list, but it’s a pretty cool one. It’s a Clojurescript interface to Facebook’s React.js library.
Finally we have the Environ library, and it’s associated
lein-environ plugin. As their README says:
Environ is a Clojure library for managing environment settings from a number of different sources.
However, it is also the basis for a lot of the really neat things that Chestnut does.
Next up, we have some more basic configuration stuff.
1 2 3
These two settings are specifically to help with deployment to Heroku
which looks for the
:min-lein-version key to determine what version
of lein to build your app with. Chestnut also changes the uberjar name
so that the Procfile can specify a specific filename. Normally, the
uberjar name is derived from the project name and the current
version. However, this makes it a moving target. Every time you bump
your version number you would have to update the Procfile to stay in
sync. This way, the uberjar always has one name and the Procfile never
needs to change.
Finally, we have the basic cljs build configuration:
1 2 3 4 5 6 7
This is remarkably similar to the configuration from the lein-cljsbuild none example project, and a standard setup to enable source-maps.
So far, most of what we’ve seen is fairly standard clojure/clojurescript configuration stuff. But one of the things that makes Chestnut awesome is that it makes really good use of the Leiningen profiles feature. In particular, it uses profiles to concisely specify different clojurescript compilation settings and add dependencies that are only needed for development. If this is the first time you’ve heard about Leiningen profiles, you should probably go read about it. The basic summary is this though:
You can place any arbitrary key/value pairs supported by defproject into a given profile and they will be merged into the project map when that profile is activated.
Let’s start with the extra
:dev setups. It starts off nice and easy
just adding more clojure source and test paths and some extra
1 2 3 4 5 6 7
Notice that there is a similar entry under the
:uberjar profile for
:source-paths but that the
:test-paths are omitted (since the
uberjar is typically for distributing production code).
Figwheel is a very cool project that “pushes ClojureScript code changes to the client.” This enables a very smooth Clojurescript workflow. It’s so seamless in fact that in some ways it makes Clojurescript programming more enjoyable than working with Clojure!
Piggieback provides support for running a ClojureScript REPL on top of an nREPL session. Chas goes into the reasons of why this is a desirable thing in the README, so go check it out if you’re interested.
Finally, Weasel allows your ClojureScript REPL to use WebSockets to communicate with your chosen execution environment. Again, their README has good information about why this might be a thing you want.
The next two sections are pretty much just setup for Piggieback and Figwheel:
1 2 3 4 5 6 7 8
Then both the dev and uberjar profiles contain an
specifying either that
configurations hook into the Environ library and allow you to specify
the value of environment variables directly in the project.clj
file. This particular usage should be pretty straightforward; it’s
basically a simple switch that allows us to tell in our code whether
or not this is a dev build/run or not.
The only thing remaining in the dev profile now is the cljsbuild test
configuration. Though once again, notice that there is the small
addition of the
env/dev/cljs path as a source for our previosly
app ClojureScript build.
1 2 3 4 5 6 7 8 9
Compared to the
:dev profile, the
:uberjar is quite simple:
1 2 3 4 5 6 7 8 9 10
The main difference is the addition of three pieces of configuration
that make this a true production build. The
:hooks option causes
leiningen to run the
leiningen.cljsbuild/activate function to let it
hook into the defualt Leiningen tasks.
:omit-source simply directs leiningen not to include the
clojure source files in the resulting uberjar, and
:aot :all causes
the Clojure compiler to Ahead-of-time compile all your clojure
code. The idea behind both these configs is to make your final uberjar
as lean and performant as possible, at the expense of some (probably
not needed) flexibility.
So that’s it for Chestnut’s
project.clj. It looks intimidating at
first, but in the end it is quite approachable when you break it down
into it’s component pieces.