TESTING AGAINST INTERNET EXPLORER IS NEVER FUN. That’s an understatement.

Even though at Bindle we limit our support to IE9, we still fear the obscure IE bug. It leads to an aversion to booting up a Windows 7 VM. jQuery + Compass have (greatly) reduced the worries over IE quirks, but from time to time I am still left shaking my head.

These days I find I can get away with deferring IE testing until towards the end of a release process, as most of the time all we need to do is double check things look and behave right. So imagine my surprise when seeing this while loading our latest release:

As you can see the majority of our stylesheets are not being interpreted. It turns out the problem is due to some internal limitations in IE, versions up to 9. Such browsers allow:

up to 4095 selectors per stylesheet [this is the one that was causing us the problem].

up to 31 @import ed sheets

ed sheets @import nesting up to 4 levels deep.

Why in this day and age do we have such a limit, you ask? Eric explains:

The root of the limitations is that Internet Explorer uses a 32bit integer to identify, sort, and apply the cascading rules. The integer’s 32bits are split into five fields: four sheetIDs of 5 bits each, and one 12bit ruleID. The 5 bit sheetIDs results in the 31 @import limit, and the 12 bit ruleID results in the 4095 rules-per-sheet limitation.

Chris Wilson comments that the feature was implemented in IE3 (released 1996), when keeping the memory requirement per stylesheet rule was a little bit more important.

Dealing with the problem

The specific issue is that Bindle has more than just 4095 selectors. The solution is to split the stylesheet up (for IE only), and to serve up chunks of it selectively to IE. So you’ll see in our <head> tag, something like:

<link href="/assets/screen.css?body=1" media="screen" rel="stylesheet" type="text/css" /> <!--[if IE]><link href="/split_assets/screen-2.css" rel="stylesheet" type="text/css" media="screen"><![endif]-->

The standard sprocketed stylesheet and a second, IE only stylesheet (containing the remaining rules from our stylesheet that it ignores).

How to work producing this screen-2.css into our Rails 3.1 / git / heroku workflow? The nicest solution is Bless, a node.js project that splits CSS files via a command line program, even adding @import statements to your first file to ensure you don’t need to change your html code at all. However, this solution isn’t practical when you are deploying to heroku, with it’s read-only file system, and git push style deployment. (If you are deploying to your own server via capistrano, I would advise following this route).

So what is a poor heroku based developer to do? I found a simple little CSS splitter ruby class which I tried to work into the sprockets assets workflow, so that the CSS files would get split on rake assets:precompile ; however (not being a sprockets expert) it seemed that turning one file into multiple wasn’t part of the basic sprockets plan. Another possibility would be to hook somewhere else into the assets:precompile task, however I need my code to run after assets:precompile (and thus the css files generated), and I don’t know how to ensure that [ ].

UPDATE: Zweilove have released this class as a gem, and have a workaround for the multiple-files problem, so it’s now possible to integrate it into the asset workflow.

A solution: rack middleware

Although working harder through one of the solutions outlined above would probably be the ideal way to do it, I secretly didn’t really want to, as I was itching for a chance to implement a rack middleware. I had previously had to dig around in middleware when solving an extremely scary bug that I encountered. I was keen to have a go at one of my own. Here’s the end result:

require 'css_splitter' class SplitAssetEndpoint def initialize ( app, path ) @app = app @path = path end def call ( env ) if env [ 'PATH_INFO' ] =~ Regexp . new ( '^' + @path + '/([^/]*) \- ( \d +).css' ) filename = File . join ( ::Rails. public_path , ::Rails. application . config . assets . prefix , $1 + '.css' ) css = CssSplitter. split_to_part ( filename, $2. to_i ) [ 200 , { 'Content-Type' => 'text/css' , 'Content-Size' => css. bytesize . to_s , 'Cache-Control' => 'public, max-age=31536000' } , [ css ] ] else @app . call ( env ) end end end

Here’s the modifications I made to the css splitter library to allow me to extract a single part of the file. The middleware itself is pretty simple. We simply look at the incoming request, and if it’s path matches ( /split_assets/ in our case), it parses out the part you want, pulls it out of the CSS asset, and serves it back to you. Otherwise it simply passes the request through to the remainder of the stack.

Doesn’t this use a lot of resources parse + split the CSS every time an IE user accesses the site? Well, no, thanks to the magic of HTTP caching. We simply make sure that we place the SplitAssetEndpoint behind the Cache, like so:

config. middleware . insert insert_after Rack::Cache , 'SplitAssetEndpoint' , '/split_assets'

Problem solved!