Green River Staff

Rails Asset Pipeline, CoffeeScript edge cases and solutions

The Rails asset pipeline is awesome when it works, but it doesn't always work like one would expect.

Users of CoffeeScript in the Rails asset pipeline eventually run into one of three problems:

  1. Generating a coffeescript file outside of application.js
  2. Sharing declarations across CoffeeScript files
  3. Load order dependency issues

In this blog entry I'll outline solutions to all three of these problem, and additional CoffeeScript resources for your Rails projects.

Problem One: Generating a CoffeeScript file outside of application.js

Scenario: I have a CoffeeScript file that I want to compile, but I want the asset pipeline to generate a seperate file instead of including it in the main application.js

A good scenario is if we have a bunch of files that are needed sometimes, like Javascript used only in the admin section. Splitting files up this way is a good way to keep the main application.js file small for normal situations, then pull in admin.js to add additional functionality only when the user needs it (in this case, when they browse the admin section.)

Another scenario is some Javascript for a bookmarklet, where you only want to pull in Javascript related to your bookmarklet, not the Javascript for your entire app.

Simply add a file to config.assets.precompile like so (in your environment/production.rb:

config.assets.precompile += %w(admin.js)

admin.js can use Sprocket (//= require) directives to pull in admin related Javascript files, or it can be admin.js.coffee a simple file that just needs to be processed outside of application.js. (Or hey, it could be both!)

Problem Two: Sharing variables (or separating concerns) across CoffeeScript files

While investigating Problem one above, I ran across another problem: I found myself wanting to share things across files in CoffeeScript, but couldn't.

The CoffeeScript compiler wraps the contents of every file inside an anonymous function/namespace. Because every file is wrapped up in its own namespace you can't call a function in another file (namespace).

It's important to note here that this isn't just an asset pipeline problem, but something you have to solve on any CoffeeScript project with multiple files.

Scenario: I have a utils.CoffeeScript file which contains common library functions for my project. Perhaps you have a utility function that detects if the user is running IE, or provides a unified way to show progress during an AJAX request to the server.

The easiest way is to shove this into the global namespace:

@.is_user_running_ie = ->
   $.browser.msie == true

Now is_user_running is shoved into the context of the anonymous function that CoffeeScript wraps your file with. The context is the window or global object, so that's what this/@ is.

You might be a little worried about shoving functions into the global namespace like this, but you could wrap it in a namespace like:

@.MyProject ?= {}    # create MyProject if it doesn't exist already
@.MyProject.is_user_running_ie = ->
  $.browser.msie == true

Now you have a project namespace MyProject for all your utility functions. I used a project namespace like this on a previous project and thought it was a great idea (I'm a big namespaces fan).

Other options

You could also use Sprockets + CommonJS to wrap everything up. Or the requirejs-rails gem.

Another option is to use coffee's --bare command line option to not add an anonymous namespace function to files. Probably the easiest way to do this is to use the sprockets-blackcoffee gem. Any CoffeeScript files you want compiled bare you name your file with the .black_coffee extension. This is probably not a good idea in general, but it can help in some very specific cases (Jasmine spec helpers, for example).

sprockets-blackcoffee is great because it is a whitelist: you want file X, file Y, and file Z bare, and all other CoffeeScript files will be included inside an anonymous namespace/function like normal.

Problem Three: My CoffeeScript file now depends on other things in other files!!

If you followed the advice in Problem 2 about putting things in the window object, now your CoffeeScript file depends on other things in other classes. You now potentially have a load order problems.

In the Rails asset pipeline, require_tree loads assets in alphabetical order. Perhaps you have a file, my_project_utils.js.coffee, that defines a common class you want all your CoffeeScript classes to extend. You define this class and add it to the global namespace, or use one of the other techniques detailed above.

However, now you want to use this common subclass in an exercise.js.coffee. Because E comes before M in the alphabet, your common class doesn't exist in the global namespace when exercise.js.coffee get processed.

There are two solutions to this:

  1. Add //= require my_project_utils before //= require_tree in your application.js
  2. in excersise.js.coffee (and any other CoffeeScript file that references your common class), add #= require my_project_utils.

IMPORTANT NOTE: Even with the #= require syntax, separate CoffeeScript files are still namespaced. You'll still need to use the global namespace to access elements defined in other files.

In both cases, the asset pipeline knows to only include a file once. It will figure out the dependency chain, and it will insure that my_project_utils gets included before exercise.js.coffee.

Other Asset Pipeline resources

Asset Pipeline, CoffeeScript and apps that manipulate the DOM a lot

Eric Menge wrote an interesting blog post which presents an approach for using modular CoffeeScript techniques to make the HTML elements themselves responsible for registering the required Javascript callbacks. Essentially this uses a command pattern to make sure Javascript callbacks are always registered helpful in web apps where a lot of DOM manipulation happens.

The blog entry: Rails and Modular CoffeeScript

Other articles on organizing CoffeeScript and/or the asset pipeline

Manas.com has a good blog entry on organizing CoffeeScript in a Rails app. It does have some typos (a few places they forgot about a needed @), but it's a good article.

The best part of the article is the end, where they summarize concepts involved with CoffeeScript and the asset pipeline, and to quote from the entry:

All your javascript code will always execute unless you execute it conditionally based on the HTML being rendered

Don't assume that just one particular CoffeeScript file will be loaded when your page is rendered - these files act just like Rails helpers in this regard - all files are always loaded.

SASS/SCSS

While this article focused on the asset pipeline and CoffeeScript, CoffeeScript is just one part of the Asset Pipeline. You may have problems with the CSS side of the asset pipeline and found this page instead.

In that case, perhaps you are Having problems sharing variables across SASS files?

Conclusion

With these tools in hand you should be able to conquer all your CoffeeScript problems, so go forth and arrow!