Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Taking the asset pipeline out of Rails

While exploring other languages, and frameworks, I found that I tend to miss some of the tooling that Rails provides. In some cases I’ve leveraged what was in that community, and in others I’ve tried to bring across some tools of Rails. While building a web application in Clojure, I wanted an asset pipeline so that I could use familiar tools such as SASS for stylesheets along with Compass, Bourbon, Neat and Groundwork for style libraries.

Let’s take a look at how an asset pipeline can be applied to non Rails projects. We’ll start with Rails itself, work our way down, and build it back up again.

Rails

The asset pipeline in Rails was extracted into a Gem sprocket-rails, which has the Rake tasks, URL helpers for Rails applications and the configuration for the Rails conventions of where files are, and how to handle them. More information on configuring Rails applications can be read in the asset pipeline guide. This is included by default when you depend on the Rails gem.

Sprockets

The sprockets gem is where the work takes place. Work such as compiling assets, creating digest files and manifests of output. It also includes directives such as //= require jquery, which are not in the Javascript or CSS specifications.

During my efforts to use Sprockets in Clojure, I did come across sprockets-standalone but I think with a little work, an application could just depend upon sprockets alone to build the assets.

Sprockets integrates with various libraries such as the YUI Compressor binding or Ulgifier binding to minimize and compress Javascript and CSS. Both of these tools ultimately depend upon a Javascript runtime such as Rhino or Node.

The sprockets gem does include Rack, Coffeescript and other compilers your non Ruby application may be interested in, so let’s go one level deeper.

SASS

Sass is a library which extends CSS to include variables, mixins and other fun stuff, and it’ll compile those files into CSS. It also supports compression which can be used in Rails through the rails-sass Gem, and configured using sprockets-rails for a Rails application.

Now Sass was originally written in Ruby, but now also has a C/C++ port called libsass.

Putting it back together again

There are broadly two ways to start using these tools in a non Ruby application: use some Ruby through either Rake, another Ruby based build tool or a custom Ruby script; or use a non-Ruby build tool like Grunt. This can prove to be slightly problematic as I found out. With new features introduced with new versions of both SASS and Compass, it can be troublesome to know which versions you need for your application to all work together.

I had originally started using Rake, but also explored Grunt, and I’ll detail what I found here.

A note on dependencies

You may see that the dependencies include Rails like app/assets/stylesheets paths. This is a consequence of some of these libraries being built for Rails almost exclusively. Looking at Bourbon, it’s Rails by default although it does show how to use it without Rails. Neat only shows how to run on Rails. If you compare that to Groundwork, it has a project which is just the framework, and another for the Rails implementation. I think this is a better approach, although a little more work for the maintainers.

Rake

Dependencies with Bundler

I had used Bundler to install dependencies, but now needed to know where the dependencies were installed into so I can add certain files onto the SASS load path. I’m sure there’s a way to programmatically use Bundler, but I shelled out to get the paths to load.

The Gemfile includes the libraries I want to include:

    gem 'bourbon'
    gem 'neat'
    gem 'compass'

The Rakefile adds those to the build path:

    require 'sass'
    bourbon_path = `bundle show bourbon`.strip
    Sass.load_paths << File.expand_path("#{bourbon_path}/app/assets/stylesheets")

If you’re installing dependencies into a vendor path, you’ll know where these files end up and won’t have to shell out to figure that out.

Dependencies with Bower

My Bower dependencies included the Stylesheet libraries I wanted to use. Notice I’m using compass-mixins which is a Github repository which is a fork of Compass to make it available to Bower.

    {
      …
      "dependencies": {
        "bourbon": ">=3.1",
        "neat": "1.1.0",
        "compass-mixins": "latest"
      }
    }

Then in the Rakefile, I could load those paths.

    require 'sass'
    Sass.load_paths << File.expand_path("bower_components/bourbon/app/assets/stylesheets")

Compiling

Now the paths are setup, we can compile. I’m using sprockets-standalone, but with a little extra work this could be use Sprockets.

    require 'sprockets/standalone'

    Sprockets::Standalone::RakeTask.new(:assets) do |task, sprockets|
      task.assets   = %w(application.css)
      task.sources  = %w(resources/sass)
      task.output   = File.expand_path('resources/public/assets', Dir.pwd)
      task.compress = true
      task.digest   = true
    end

This gives the assets:clean and the assets:compile Rake tasks, which compile and fingerprint the assets.

Bower + Grunt

Now with Grunt, we can use the Bower dependency as outline above. The Grunt plugins for the moment tend to favor small single use plugins so I found one grunt-contrib-sass to compile the SASS, and grunt-asset-fingerprint to add a MD5 based fingerprint to the file name.

    module.exports = function(grunt) {

      // Project configuration.
      grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        sass: {
          dist: {
            options: {
              loadPath: [
                "bower_components/bourbon/app/assets/stylesheets/",
                "bower_components/neat/app/assets/stylesheets/",
                "bower_components/compass-mixins/lib/"
              ]
            },
            files: {
              'resources/public/assets/application.css': 'resources/sass/application.sass'
            }
          }
        },
        assetFingerprint: {
          options: {
            algorithm: 'md5',
            manifestPath: 'resources/public/assets/manifest.json'
          },
          dist: {
            src: ["resources/public/assets/application.css"],
            dest: "resources/public/assets/application.css"
          },
        }
      });

      grunt.loadNpmTasks('grunt-contrib-sass');
      grunt.loadNpmTasks('grunt-asset-fingerprint');

      // Default task(s).
      grunt.registerTask('default', ['sass', 'assetFingerprint']);

    };

Conclusions

What have we learnt? We’ve taken what I think is a pretty important part of developing and productionising a web application, and made it available to other consumers. Let’s be honest, Rails makes this look easy, but when we’ve pealed back the layers we can see it’s doing a lot for us. Making it available to non Rails code bases requires some work, but once setup it shouldn’t require much tweaking. We’ve also seen that others have replicated some aspects such as fingerprinting of the files.

I think we’ve also learnt that we shouldn’t be so afraid of exploring other frameworks, and rolling your sleeves up and figuring how how something works under the hood.

My other take away is that library maintainers should consider the wider community that a current framework that may be targeted. Looking at Bourbon and Neat, both projects are Rails centric, whereas Groundwork is agnostic but layers on a Rails specific gem for those consumers. I think that’s a nicer approach to adopt.

Comments
  1. Brent Dearth says:

    This was a great write-up of a really interesting endeavor. I recently had the fortune of doing a similar dive:

    http://blog.pedago.com/2014/01/21/goodbye-sprockets-a-grunt-based-rails-asset-pipeline/

    Our method worked well for our (mostly single-page Angular app, but I could see some of the Grunt-work getting more complex on a project with more Rails view dependencies.

  2. Robbie Clutton says:

    Thanks for sharing Brent, looks like other people are trying to do similar things. One of the important integration points for me was creating a manifest file of assets and using that in my application. I detail how I got that working in a Clojure app here: http://pivotallabs.com/clojure-asset-pipeline/

  3. Lea says:

    Hi,
    I’m not a rails or ruby developer and I got this working for SASS. Thanks for the great post. How would I do that now for coffeescript with the rake task? I managed to add everything necessary to the task itself, but we have a #= require jquery.cycle2 in one of the files and it does not find it. How do I configure the load path for coffeescript?

    • Robbie Clutton says:

      I’m not entirely sure, maybe there’s some configuration to tell it to look for coffeescript files?

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *