Making Rails asset pipeline faster

We all know that for a rather large Rails app where the front-end is quite stuffed, the asset pre-compilation and more importantly - development mode changes are far from instantaneous.

The front-end we're referring to is the kind that makes heavy use of sass, sass libraries (Compass, Bourbon, etc.) and transpiled JavaScript languages (like the old CoffeeScript, ES6, etc.)

Replacing Ruby Sass with libsass

On the Sass front for quite some time there's a pure C/C++ implementation of the Sass CSS precompiler: libsass . Clearly, being implemented in C/C++ this means it should run orders of magnitude faster than the Ruby implementation.

All we need is a gem that uses it: enter sassc-rails which is a drop-in replacement for the sass-rails gem.

How fast is sassc-rails compared to the vanilla gem in the real world? (benchmarks straight from their repo)

# Using sassc-rails
[1] pry(main)> Benchmark.bm { |bm| bm.report { Rails.application.assets["application.css"] } }
       user     system      total        real
   1.720000   0.170000   1.890000 (  1.936867)

# Using sass-rails
 [1] pry(main)> Benchmark.bm { |bm| bm.report { Rails.application.assets["application.css"] } }
       user     system      total        real
  7.820000   0.250000   8.070000 (  8.106347)

Not really orders of magnitude faster but then again a 4.5x is not bad at all.

The only issue with this is that it doesn't fully work with the usual sass gems like Compass and more i.e. before using it in production make sure it can actually compile your scss files without issues.

To help with that you can check the Sass Compatibility website and run your app with the gem installed in a different branch to test things out.

Compass intermission

What grinds my gears is that Compass doesn't work at all with sassc-rails:

Also as a side note Compass seems, well, kinda dead:

I think I'm going to migrate my front-ends that still use Compass to Bourbon and maybe autoprefixer-rails

Replacing the JS transpiling/minification/etc. with mini_racer

The problem with therubyracer is that it uses version ~> 3.16.14.0 of libv8 ("a gem for distributing the v8 runtime libraries and headers in both source and binary form") whereas mini_racer uses ~> 5.1 which as one might expect is quite faster than the former.

Some quick benchmarks from their repository:

$ ruby bench_uglify.rb

Benching with MiniRacer
MiniRacer minify discourse_app.js 13813.36ms
MiniRacer minify discourse_app_minified.js 18271.19ms
MiniRacer minify discourse_app.js twice (2 threads) 13587.21ms

Benching with therubyracer
MiniRacer minify discourse_app.js 151467.164ms
MiniRacer minify discourse_app_minified.js 158172.097ms
MiniRacer minify discourse_app.js twice (2 threads) - DOES NOT FINISH

Killed: 9

Here we are talking about a 10x improvement i.e. really nice gains and thus an order of magnitude faster. On the compatibility side this seems to be a drop-in replacement i.e. so far in the apps I've added it in the Gemfile there were no issues whatsoever.

Alternative to mini-racer for best possible performance

Sprockets uses ExecJS which “picks the best runtime available to evaluate your JavaScript” this means that if you don't mind having an extra dependency installed you can just use Node.js 6.x to get the maximum performance.

Since I deploy my assets by precompilling them locally I just installed Node.js 6.2.0 via brew brew install node and ran time rake assets:clobber assets:precompile on a 2K lines CoffeeScript JS file. Without further ado, here are the results:

node.js 6.2.0

rake assets:clobber assets:precompile 7.30s user 1.27s system 102% cpu 8.358 total

mini-racer with libv8 5.x

rake assets:clobber assets:precompile 14.96s user 1.20s system 101% cpu 15.943 total

That's another 2x speed-up! Also, I tried it with 10K lines of CoffeeScript - in this case mini-racer didn't even finish.

Kudos goes to @attilagyorffy for pointing this out.

Should I use these in production?

In a set-up where the assets are pre-compiled locally then uploaded to the actual VM this is not a big issue i.e. if something fails just revert to the original gems.

Mini-racer looks rock solid so far whereas sassc-rails still has issues with some libraries i.e. not ready for production in all use-cases yet.

If you don't mind installing node.js and want maximum performance then by all means do it since it works flawlessly with ExecJs.

In the optimal case where you can integrate both into your Gemfile you will get quite a nice boost during deploys and more importantly: during development (in apps with large sets of assets of course).

Tagged under: