In my continuing trend of trying to create a polyglot application, I’ve been working on introducing an asset pipeline into my small Clojure application. There’s a Clojure plugin, lein-haml-sass, that’ll do a good job, but it depends on a JRuby runtime during development and a number of Rubygems, namely haml and sass to compile those targets.
Plugins
I got this working fine by adding the plugin to my project.clj
file:
:plugins [[lein-haml-sass "0.2.7-SNAPSHOT"]]
And also adding some config, telling the library where the source is, and where to compile the results too.
:sass {:src "resources/sass"
:output-directory "resources/public/assets"
:output-extension "css"}
Jolly good, except I wanted to follow Rails example and use fingerprinting of the file name. Over on the Rails site, it details why this is a good idea. So I was satisfied with the solution thus far, but wanted to get a little more out of it.
Asset pipeline
Once again, a lot of this intelligence is wrapped up in the Rails source code and with it’s convention over configuration influence. Rails uses sprockets under the hood, and once again it seems someone has thought about this already, and created standalone-sprockets to mimic Rails without Rails.
First, add the gem to a Gemfile, I’m using a branch as I found an issue and am waiting for it to get merged and released to Rubygems.
source 'https://rubygems.org'
gem 'sprockets-standalone', github: 'robb1e/sprockets-standalone'
Then in your Rakefile
require 'sprockets/standalone'
Sprockets::Standalone::RakeTask.new(:assets) do |task, sprockets|
# what assets to compile
task.assets = %w(application.css)
# what directory those assets are in
task.sources = %w(resources/sass)
# where to copy the compiled files to
task.output = File.expand_path('resources/public/assets', Dir.pwd)
# also create a .gz version
task.compress = true
# create the fingerprint in the filename
task.digest = true
end
Now when you run rake -T
you’ll see the following tasks:
rake assets:clean # Clean old assets
rake assets:clobber # Remove all assets
rake assets:compile # Compile assets
In my case, I have a file resources/sass/application.sass
which compiles to resources/public/assets/application-FINGERPRINT.css
and resources/public/assets/application-FINGERPRINT.css.gz
when rake assets:compile
is run.
So far so good. This process also generates a JSON manifest file which creates a key/value table of original to compiled filenames, i.e. application.css
is now application-b732b413fd9399adc58caf40c3356709.css
.
We need to ensure org.clojure/data.json
is included in the dependencies in our project.clj
:
:dependencies [[org.clojure/data.json "0.2.4"]]
I used the manifest in my layouts namespace, and start by requiring clojure.data.json
:
(ns robb1e.views.layout
(:require [clojure.data.json :as json]))
Now we can create a def
which reads the manifest file:
(def assets
((json/read-str (slurp "resources/public/assets/manifest.json")) "assets"))
We can build upon that to retrieve the absolute HTTP path of that resource:
(defn asset [file]
(str "/assets/" (assets file)))
We can even go one further but creating an additional def
helper
(def application-css []
(asset "application.css"))
Deploying to Heroku
Heroku recommends that an application check it’s assets in to Git before pushing, and this will work here as well. It’s a little cumbersome, but does work. There is also copying this to a CDN as part of a deployment process and including the CDN domain name in the code which concatenates the URI for the resource.