Lots of Javascript

In more than one of my projects, I have javascript or stylesheet files with several hundred lines. Also, I rotate between several active projects and forget exactly where I put things. :( What I want to do is break these files into smaller ones by responsibility but be able to concatenate them for production to reduce requests.

Rails has an excellent way of accomplishing the concatenation: the :cache option that works for both javascript_include_tag and stylesheet_link_tag. With it, you can write:

javascript_include_tag “effects”, “controls”, “builder”, “scriptaculous”, :cache => “scriptaculous_cached”

Rails will even let you shorten this by using register_javascript_expansion so that you can write:

javascript_include_tag :scriptaculous, :cache => “scriptaculous_cached”

That’s pretty awesome; but not as awesome as

helper :all

Automagic

I love how the macro for ActionController scours my helpers directory and includes whatever I place there. If I add a helper or refactor my helpers, I don’t have to remember update a list of helpers that lives somewhere else. That’s what I’d like to see from javascript_include_tag. So here’s what I came up with:


def include_javascript_dir(path)
  javascripts = Dir.glob_in(File.join(RAILS_ROOT, “public/javascripts”, path), “*.js”)
  javascripts = javascripts.map{|file| File.join(“/javascripts”, path, file)}
  javascript_include_tag *(javascripts + [{:cache => “#{path}/all”}])
end

(NB: The above method uses a customization of Ruby’s Dir class, glob_in which executes glob in a relative path.)

This method includes every file with the extension .js in a given directory. Thanks to Rails, they’re each included separately in the development environment but are concatenated into the file all.js in production. Sweet!

Order

We’re not done yet, though. If I move all of the Scriptaculous files to /public/javascripts/scriptaculous and then put include_javascript_dir "scriptaculous" in my layouts, the files will get loaded: but there’s no guarantee that “effects” will be loaded before “controls” and JavaScript errors will be thrown.

We need to supply more information to include_javascript_dir; but I don’t want to have to spell out the order the files should be in or there won’t be any point to having this method discover them automatically. Here’s what I settled on:

((order & javascripts) + (javascripts - order))

Here order is a list of script file names in the order you care about and javascripts is a list of real javascript files that Ruby has discovered. The intersection of order and javascripts yields files-that-really-exist in the order you care about; and javascripts - order gives you all the rest of the real files.

I added another parameter to let you specify the relative path to your javascript files. (One of my projects uses “/public/js” and another “/public/javascripts”) Here’s what I’m using now:


def include_javascript_dir( path, options={} )
  js_path = options[:path] || “javascripts”
  javascripts = Dir.glob_in(File.join(RAILS_ROOT, “public”, js_path, path), “*.js”)
  if (order=options[:order])
    order = order.map{|s| “#{s}.js”}
    javascripts = ((order & javascripts) + (javascripts - order))
  end
  javascripts = javascripts.map{|file| File.join(“/#{js_path}”, path, file)}
  javascript_include_tag *(javascripts + [{:cache => “#{path}/concat”}])
end

I’ve wrapped it up with the helper methods I use in all my Rails applications: ttp://github.com/boblail/lail_extensions