Using the Asset Pipeline outside of Rails – Serving CoffeeScript and SASS

8.28.11 by eric

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.

Serving CoffeeScript or SASS using 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.

Creating a custom Sprockets Environment

First you need to require sprockets and supporting gems, just add this to your Gemfile:

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

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

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

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

<br /><br /><br />
assets.append_path('/Full/Path/To/assets')<br /><br /><br />

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

<br /><br /><br />
# in Gemfile<br /><br /><br />
gem 'rack-test'</p><br /><br />
<p>  # in project<br /><br /><br />
session = Rack::Test::Session.new(Rack::MockSession.new(assets))<br /><br /><br />
session.get('application.js')<br /><br /><br />
# Served asset /application.js - 404 Not Found<br /><br /><br />

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:

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

And use session to request it again:

<br /><br /><br />
session.get('application.js')<br /><br /><br />
session.last_response.body<br /><br /><br />

That should return this beautifully transpiled javascript:

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

It get’s cooler! You can stick a .erb on the end of the application.js.coffee and sprockets will run it through ERB before converting it to javascript. Try this:

<br /><br /><br />
# in application.js.coffee.erb<br /><br /><br />
$(document).ready -><br /><br /><br />
$('img').attr('src', "<%= asset_path('lolwut.png') %>")<br /><br /><br />

And request it again:

<br /><br /><br />
session.get('application.js')<br /><br /><br />
# NoMethodError: undefined method `asset_path'<br /><br /><br />

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:

<br /><br /><br />
module AssetHelpers<br /><br /><br />
def asset_path(name)<br /><br /><br />
"/assets/#{name}"<br /><br /><br />
end<br /><br /><br />
end</p><br /><br />
<p>  assets.context_class.instance_eval do<br /><br /><br />
include AssetHelpers<br /><br /><br />
end<br /><br /><br />

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

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

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

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

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:

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

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!

  1. Trevor says:

    Cheers for that! For a more details on the asset pipeline itself, check out Ryan Bates’ screencast: http://railscasts.com/episodes/279-understanding-the-asset-pipeline?autoplay=true

  2. Joao says:

    Hi Thanks for this clear examples, but i would like to ask how to disable some filters, as i have an app which users can upload themes and i would like them to use sass and coffeescript via Asset Pipeline but i don’t want them to use erb or any server side code.

    Thanks Eric

  3. eric says:

    Joao,

    You can control which processors get used by the filename. So just don’t allow filenames that have .erb in them, for example, and they won’t be able to fun any ruby code on the server.

  4. Joao says:

    Thanks eric ,

    here is how i done it as erb is a Sprockets engine linked to Tilt, so i did not found any processors but i did manage it.

    module Sprockets
    module Engines
    def un_register_engine(ext)
    ext = Sprockets::Utils.normalize_extension(ext)
    @engines.delete(ext)
    end
    end
    end

    env.un_register_engine(“.erb”)

    Thanks

  5. [...] Using the Asset Pipeline outside of Rails – Serving CoffeeScript and SASS « Envy Labs (tags: ruby rails coffeescript programming) [...]

  6. Kristoffer says:

    Great post! Are you guys getting Sprockets to minify/compress? In my setup there is no such mojo :/

  7. [...] Just some scribbles and jots. These are just a few of the notes, videos, and screencasts taken by the team at Envy Labs. You're welcome to join in and contribute to our experiments. « Using the Asset Pipeline outside of Rails – Serving CoffeeScript and SASS [...]

Leave a Reply

* Required Fields