Over the last couple of weeks I have been working on a project for a client that involved building a couple of small applications in Sinatra (a light web framework for Ruby). Both applications have a RESTful interface, as is the nature of Sinatra, and a javascript frontend using jQuery to make asynchronous requests. I made the decision to run with CoffeeScript as it makes creating js classes a doddle and keeps the client side code readable and uniform in style.
Originally I had stuck to one big coffee script file, but this began to grow a little out of control as the functionality of the app expanded, so I decided to break things up. Using the coffee-script gem, I just added a couple more coffee script files, one for my classes and one for general utility type functions. Two problems quickly made themselves apparent;
1. Coffeescript, by default, generates javascript files inside a closed scope. Not sure this is the correct way to phrase it, but I will show you what I mean. All the javascript it generates is enclosed in this (function() { ... }).call(this);, this effectively places everything inside its own scope, which means if you define a class for use by the application it won’t be accessible by anything unless its defined in the same .coffeescript file. The coffeescript compiler has an option to remove these called –bare, I needed to make sure that is run for class type coffeescript files.
2. I found that for some reason (at least in OS X) when Ruby tried to compile more than one coffeescript at a time, it would choke. I was using the “therubyracer” gem along with “coffee-script”, so I assume the coffee-script gem was using V8 to compile the javascript. As you can imagine, when the page was initially loaded several requests were made for .js files that were actually the compiled output of a coffeescript file, the Ruby process would end up at 100%, choked!
Whats more, there is really no real need to have the coffee-script compiler involved at all in a production environment, so here is what I wanted to do. When the application was run in development, generate and cache the javascript files, that way I could deploy those to live and when it was run in production skip the coffee-script part of the application all together. I started off by swapping out just using the coffee-script gem and opted if for a Rack filter called rack-coffee, git repo is here and my fork of it is here. This gem, rather than compiling the coffeescript from the Sinatra application itself, will do it when Rack is handling the initial request for the .js file, it’s a pretty straight forward class and does the job very nicely, handing compilation of the coffeescript off to the command line compiler itself.
At that point, rack-coffee never actually compiled the script to the filesystem, just to STDOUT when it was out of date or returned a 304 (Not Modified) if it was in date. So, first step, cache to the filesystem. Below you can see the changes I made to rack-coffee;
The main two changes to notice are firstly the brew function, that now waits for the coffee-script command to finish running and the changes to the call function that add the –bare parameter if its compiling a file in the set class_urls path and also read the compiled .js file and return it as the output for the Rack request.
So from then on, every time the app compiled a coffee-script file it would leave a compile .js file in the path set by the output_path setting. To use this in a sinatra application we would include it like so;
use Rack::Coffee, {
:root => File.dirname(__FILE__) + '/coffee',
:urls => '/app_scripts',
:class_urls => '/app_scripts/classes',
:output_path => '/public'
}
Assuming that coffee-script and indeed the rack-coffee library is referenced correctly, every time a .js file is requested from either /app_scripts or /app_scripts/classes it compiles and stores a javascript file to the output_path without or with the –bare flag respectively. The :root parameter shows the library where to find the actual coffee-script source.
So all that is left now is to tell Sinatra to skip this Rack filter when in production mode, this is easy! I use bundler, so my Gemfile looks like this;
source 'http://rubygems.org'
group :development do
gem 'coffee-script'
gem 'rack-coffee'
gem 'therubyracer'
end
group :default do
gem 'sinatra'
gem 'json'
gem 'oauth'
gem 'mechanize'
gem 'multipart-post'
end
So, here we can see all the coffeescript libs are only required in a development environment. As for the application itself, just pop an if statement round your call to use the Rack filter, like this;
if ENV['RACK_ENV'].to_sym == :development
puts "This is development, all coffeescripts are generated..."
require 'coffee-script'
require File.dirname(__FILE__) + '/lib/coffee'
use Rack::Coffee, {
:root => File.dirname(__FILE__) + '/coffee',
:urls => '/app_scripts',
:class_urls => '/app_scripts/classes',
:output_path => '/public'
}
end
There we have it, a simple set up to compile coffeescript in development but fallback to the cached js output in production. This of course is just my solution, if you have an even better one, then please feel free to let me know.
UPDATE : Having spoken on Twitter with the creator of CoffeeScript, @jashkenas, I learnt that rather than using –bare to expose my classes to the Global scope, its better just to expose the interface by declaring them using the browser ‘window’ object. Like this;
class window.MyClass
say_hello: () ->
alert 'hello'
This would still be referable in javascript as simply ‘MyClass’, much better!