clojure labs

Clojure and the asset pipeline

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.