Using the Asset Pipeline Outside of Rails - CoffeeScript + Sass

In our upcoming Rails for Zombies 2 Code School course, we have a couple of challenges that make use of Rails 3.1 new Asset Pipeline. The Asset Pipeline is the thing in Rails that serves up your JavaScript, images, and stylesheets. But it can also compile CoffeeScript into JavaScript, or Sass into CSS.

And so we wanted to teach people a little bit of CoffeeScript/Sass in Rails for Zombies 2. But that means we have to have a way to test CoffeeScript/Sass. We accomplished this by extracting the bits in Rails 3.1 that implement the pipeline and used them to serve and compile CoffeeScript into JavaScript and Sass into CSS.

Creating a Custom Sprockets Environment

The Asset Pipeline is powered by the sprockets gem by Sam Stephenson. It hooks into the Rails stack by inserting a Rack App at the very top of your routes collection. When a request for an asset hits your app it will be “routed” to the sprockets environment. The relevant setup code in Rails is located here.

First you need to require sprockets and supporting gems. Just add this to your Gemfile:

gem 'sprockets', :git =>'git://github.com/sstephenson/sprockets.git'
gem 'coffee-script'
gem 'sass'

After running bundle install, we can create a new Sprockets::Environment, setting the path to the root of your project and a logger:

require 'bundler/setup'
Bundler.require # this will require all the gems in your Gemfile
assets = Sprockets::Environment.new(project_root) do |env|
  env.logger = Logger.new(STDOUT)
end

Next we need to tell the environment about the paths it should use to lookup assets from the file system. The simplest configuration would just be a single path to all your assets:

assets.append_path('/Full/Path/To/assets')

We need a way to call the sprockets rack app we just created. One easy way is to use rack/test:

# in Gemfile
gem 'rack-test'

# in project
session = Rack::Test::Session.new(Rack::MockSession.new(assets))
session.get('application.js')

# Served asset /application.js - 404 Not Found

As you can see sprockets returned a 404 Not Found, because we haven’t actually created our file yet. To fix this, create a application.js.coffee file in your assets directory, something like this:

$(document).ready ->
  $('#foo').html("<p>bar</p>")

And use session to request it again:

session.get('application.js')
session.last_response.body

That should return this beautifully compiled JavaScript:

(function() {
  $(document).ready(function() {
    return $('#foo').html("<p>bar</p>");
  });
}).call(this);

It gets cooler! You can stick an .erb on the end of the application.js.coffee file and sprockets will run it through ERB before converting it to JavaScript. Try this:

# in application.js.coffee.erb
$(document).ready ->
  $('img').attr('src', "")

And request it again:

session.get('application.js')
# NoMethodError: undefined method `asset_path'

Unfortunately that asset_path method isn’t defined like it is in Rails. But we can easily define our own helpers that can be used in our assets served with .erb preprocessing, like this:

module AssetHelpers
  def asset_path(name)
    "/assets/#{name}"
  end
end

assets.context_class.instance_eval do
  include AssetHelpers
end

Now if you try the request again, you should get this result:

(function() {
  $(document).ready(function() {
    return $('img').attr('src', "/assets/lolwut.png");
  });
}).call(this);

We actually don’t have to change anything to serve Sass. Add an application.css.scss file to your assets directory and request it like before:

# application.css.scss
h2 {
  font-size: 10em;
  a {
    height: 64px;
    width: 50px;
    display: block;
  }
}
session.get('application.css')
session.last_response.body
# h2 {
  # font-size: 10em; }
# h2 a {
  # height: 64px;
  # width: 50px;
  # display: block; }

Sinatra

You can use this as a quick way to add the asset pipeline to Sinatra. Add gem 'sinatra' to your Gemfile and then add this sinatra route:

get '/assets/*' do
  new_env = env.clone
  new_env["PATH_INFO"].gsub!("/assets", "")
  assets.call(new_env)
end

This will invoke the asset pipeline for all GET requests under /assets, so http://localhost/assets/application.css will serve the application.css.scss asset. (We had to modify the PATH_INFO, otherwise sprockets would look for assets under /assets/assets/, which isn’t what we want.)

That’s it! For all the code in this blog post check out this gist. In a future blog post, I’m going to show how to run that compiled JavaScript against a DOM using execjs-async and jsdom, all from the comfort of Ruby.

Update: Sprockets 2.0.0 was just released and it’s got some great new documentation in the README, make sure to check it out!


Eric Allam
Eric Allam

Filed under: Build

Next article →

Running jQuery Code From Ruby Using ExecJS-Async

Eric Allam Eric Allam