Rails plugin: Blazing fast page loads through bundled CSS and Javascript

posted: April 14th, 2007 · by: Sven

in: Programming · tagged as: , , , , , ·  16 comments »

I recieved an interesting Sitepoint newsletter the other day which talked about bundling CSS and Javascript ressources (or “assets” like that’s called in Rails talk) to achieve faster page loads. This caught my attention specifically because I experienced the need to repackage many small CSS files on the fly a while back when I worked on a large CMS-type system.

Skimming through the PHP source article and code I thought that something similar could be done as a Rails plugin pretty easily and elegantly. I couldn’t resist, sat down and started coding away … so here are my results.

What does it?

Basically, what the plugin does is:

  • combine multiple CSS or Javascript files into one big file (bundle) on the fly
  • compact this bundle by removing whitespace, comments and the like
  • cache the bundle by using Rails default page caching mechanism
  • create an “accesspoint” to the bundle by adding an appropriate route

It thereby removes the HTTP overhead for requesting several CSS and Javascript files to probably just request one CSS stylesheet and one Javascript file. There are some firebug screenshots in the Sitepoint article linked above which demonstrate quite nicely how significant this load overhead is in terms of user (speed) experience.

How to access the bundles?

Rakaz proposes the following syntax in his article to define a bundles contents and access them through an URL:


http://example.org/bundle/one.css,two.css,three.css

… which I think is ok, but can be done more concise - we don’t really need the repetition of .css because we wouldn’t allow to combine CSS files with JS or other filetypes anyways.

It makes sense though to append the information that we’re dealing with CSS files here as a regular file extension (.css) because this helps with webserver configurations and the like. So this is preferable over:


http://example.org/bundle/css/one,two,three

Also, following Rails conventions with plural directory names like “stylesheets” and “javascripts” the better choice is “bundles” rather than “bundle” within a Rails plugin. So, combining these considerations I settled with this synatx:


http://example.org/bundles/one,two,three.css

What will be bundled?

Now, if you access the URL above and there are the files one.css, two.css and three.css in your public/stylesheets directory, the plugin will combine them into a single bundle file, compress them and page-cache them for future requests in the public/bundles directory.

If there’s a subdirectory one (instead of a file one.css), a recursive glob will be performed and and all CSS files in the subdirectory (and recursively all of its subdirectories) will be collected and merged into the bundle as well. This makes sense when you have a large number of separate files: you can access them through their directory name then.

If there’s no correspondent file one.css or directory one at all this part silently will be ignored.

How does the compression work?

It’s not really compressed, but compacted. I haven’t build in any gzip/deflate compression because I can use Apache’s mod_deflate for this task. This could be added easily though I guess.

Instead the plugin uses:

… to remove whitespace, comments and the like from Javascript and CSS files.

How to install?

You can install this plugin simply by using Rails script/plugin installer. Standing in your Rails root directory do:


script/plugin install http://svn.artweb-design.de/stuff/rails/bundled_assets/

Also, up to now you’ll have to add a route to your routes setup manually. Therefor edit your config/routes.rb file and add the following line (somewhere above Rails’ default routes):


map.connect 'bundles/:names.:ext', :controller => 'assets_bundle', :action => 'fetch', :ext => /css|js/, :names => /[^.]*

How to use?

After you’ve successfully installed this stuff (do not forget to restart your server) you can use it in your templates (again, do not forget to invalidate your page caches if necessary). E.g., the layout template of this blog contains the following lines:


<link rel="stylesheet" href="/bundles/layout,styles,search-tag.css" type="text/css" media="screen" charset="utf-8" />
<script src="/bundles/prototype,tools.js" type="text/javascript" charset="utf-8"></script>

Update: In the comments, Nick Pearson posted a super-useful tip about how to use this plugin together with the built-in Rails helpers for javascripts and stylesheets. I’ve posted a separate article about this setup here: How to use the Bundled assets plugin with Rails’ built-in tag helpers.

Possible improvements? Problems?

Besides integrating basic gzip output compression the plugin could use some configuration options such as whether or not subdirectory contents should be bundled, whether or not the code should be compacted (and how).

Update:In the meantime some work has been done and some of these options have been added. See this article (also for much improved performance through a compiled version of JSMin): Serve CSS and Javascript even faster: improved Rails bundled_assets plugin.

Also, there’s currently no solution for a possible problem with relative image URLs in CSS files that are bundled from within subdirectories (and this way “transferred” to upperlevel directories). Using absolute URLs should work in this case though.

Feedback?

What do you think?

Leave a comment

16 Comments

  1. Paul Annesley said April 14th, 2007 at 02:58 PM  

    Nice work Sven.

    I agree with your decision to delegate compression to a web server module.

    In my Lua implementation for lighttpd I store the bundle to disk and then rewrite the HTTP request to point at it. That way, subsequent modules like mod_compress will transparently handle compression and their own caching without even being aware that crazy bundling is going on.

  2. Saimon Moore said April 15th, 2007 at 10:13 AM  

    Hi Sven,

    Have you had a look at this?:

    http://synthesis.sbecker.net/pages/asset_packager

  3. Sven said April 15th, 2007 at 04:59 PM  

    Hey, Saimon!

    Yes, actually I’ve borrowed some code from Scott (some regexps for compacting CSS). AssetPackager achieves something similar but goes a different route by introducing a Rake build phase. I wanted to leverage Rails’ page caching mechanism instead. Also, I prefer the one,two,three.css style access over being bound to some kind of Rails view helper.

    But that’s all a matter of taste, I guess.

  4. Sven said April 16th, 2007 at 01:34 AM  

    Hey, Paul!

    Year, that very much sounds like what my plugin does, too … except that I’m using the standard Rails infrastructure of course.

    Thanks a lot for your article!

  5. Nick Pearson said September 2nd, 2007 at 03:33 AM  

    Hi Sven, thanks for the great plugin. It was a breeze to get it installed and set up.

    By the way, I like using the built-in helpers for style sheets and JavaScript files, and I thought I’d post how I do it in case it helps anyone else.

    My main reason for using it this way is that I don’t like the idea of having to run a Rake task to update the bundles every time the CSS/JavaScript files change (as with the AssetPackager plugin), and I don’t like having to configure which assets are bundled together outside my pages. The assets are bundled in the context of each page, so each page should be deciding which assets it should use.

    First, in routes.rb, I’ve got this:

    map.connect &#8216;:asset_dir/:names.:ext&#8217;,
                :controller => &#8216;assets_bundle&#8217;,
                :action => &#8216;fetch&#8217;,
                :asset_dir => /(stylesheets|javascripts)/,
                :ext => /(css|js)/,
                :names => /[^.]*/
    

    The only thing different about my routes.rb rule is that it allows the bundles to “exist” inside the stylesheets and javascripts directories, where the Rails helpers point by default.

    Next, in my .rhtml templates, I can use the standard helpers, like this:

    <%= stylesheet_link_tag 'main,content,forms' %>
    <%= javascript_include_tag 'common,forms' %>
    

    That’s it! As you’re well aware, the rest of the work has already been done. Thanks!

  6. Sven said September 7th, 2007 at 01:18 PM  

    Hey Nick,

    that’s pretty cool! I mostly just use hardcoded link/include-tags so I haven’t been aware that it’s that easy.

    Thanks for posting the great tip. I guess I’ll add it to the post above to make it more visible.

  7. Jigar Gosar said November 18th, 2007 at 06:35 AM  

    How do I disable bundling in development mode? Its its very irritating during development.

  8. Sven said November 18th, 2007 at 09:00 PM  

    Hey Jigar,

    The plugin uses the standard Rails pagecaching mechanism. So, if you just stay in the development mode, there’s no problem. If you switch to production though, call some pages and re-switch to dev mode, you have to clear the cache, i.e. delete the physical files that have been stored in public/ in the meantime.

    That’s just the way Rails works and I wouldn’t see a way to change this from within the plugin in a reasonable way. Would you?

  9. Pete said February 18th, 2008 at 06:49 PM  

    Hev Sven,

    Great plugin! Works wonders! And now for the but…

    I’m trying to run my app in test mode and noticed it totally breaks! I looked into the plugin and see you check ENV[‘RAILS_ENV’] == ‘test’ when checking to load the files and if its test mode then you set the base dir to the plugins test directory. Why oh Why?!? Just teasing…But there has to be a better why to check when running the plugins unit tests and not hog the plugin’s applications test mode.

  10. Vincent said May 28th, 2008 at 09:59 AM  

    Ey,

    Don know what ‘m doing wrong, but i followed all the steps shown in the readme and on this site, but for some reason i am having errors.

    Maybey it because it’s going wrong because i am testing it locally. When i go to http://localhost:3008/bundles/default.css for instance i get the error: NameError in Assets bundleController#fetch –> uninitialized constant AssetsBundleController.

    Is it because of Rails 2.0?

    Hope someone can help me.

    Thanks in advance!

  11. Melchers said September 17th, 2010 at 08:31 AM  

    I’m glad i found ur blog.Not everyone can provide information with proper flow. Good post. I am going to save the URL and will definitely visit again. Keep it up.

  12. jack said January 23rd, 2011 at 11:38 AM  

    thanks for that headsup. That’s a useful tip! I’ve never ran into that, but for sure that’s something quite some people will need a solution for. cheap vps

  13. chat said March 31st, 2011 at 07:31 PM  

    Here is a fix for the CSS and javascript plugin:

    The following cleaned up the issue:

    Dependencies.loadoncepaths -= Dependencies.loadoncepaths.select{|path| \ path =~ %r(^#{File.dirname(FILE)}) }

  14. Okey oyunu said May 12th, 2011 at 04:05 PM  

    Thanks for this article. Tüm dünya artik okey oyunu oynuyor. Yillardir bir çok oyun programi olmasina ragmen, içlerinden en güzeli olarak nitelendirebilecegimiz tek bir site göze çarpmaktadir. Diger tüm okey oyunu programlarinin aksine ücretsiz olmasi ve 3 boyutlu olarak hizmet vermesi mükemmel bir gelismedir. Sizlerde www.okey-oyunu.com adresinden bu essiz okey oyununu indirebilirsiniz. Kullanimi çok basit ve Türkçe dil seçenegi ile kolaylikla oyuna baslayabilirsiniz. Ister kendi ülkenizden, isterseniz dünyanin tüm farkli bölgelerinden dilediginiz oyun odalarini seçerek, oyuna hemen baslayabilirsiniz. Okey oyunu oynamak için artik arkadas bile aramaniza gerek kalmadan, bilgisayarinizdan 100 binlerce üye ile online olarak okey oyununu oynamanin zevkine varabilirsiniz.

  15. porno said May 22nd, 2011 at 01:34 PM  

    I do agree with all of the ideas you have presented in your post. They’re really convincing and will definitely work. Still, the posts are too short for newbies. Could you please extend them a bit from next time? Thanks for the post.

  16. porno said May 22nd, 2011 at 02:25 PM  

    good comment. thanks you friends.

    I’ve surfed the net more than three hours today, however, I haven’t found such useful information. Thanks a lot, it is really useful to me

Sorry, comments are closed for this article.

artweb design
Sven Fuchs
Grünberger Str. 65
10245 Berlin, Germany


http://www.artweb-design.de

Fon +49 (30) 47 98 69 96
Fax +49 (30) 47 98 69 97