We're hiring!

We're actively seeking designers and developers for all three of our locations.

Getting Around Internet Explorer’s 4,096 CSS Rule Limit

Browser incompatibilities are not quite my favorite bugs to find.

I recently got a bit more gray hair when I found out that Internet Explorer can’t handle more than 4,095 (2 ^ 12 – 1) selectors in a single CSS file. This is easy to run into when you’re using a framework like Rails’ asset pipeline or Compass where it’s so easy to make a single large CSS file.

In my application, the root cause of such large CSS files can be the combinatorial effect of selectors. For example, this SCSS will generate an explosion of rules:

.c1 .c2, .c1 .c3 {
  .c4 .c5, .c4 .c6 {
    .c7 .c8, .c7 .c9 {
      font-size: 100%
    }
  }
}

And the compiled CSS:

.c1 .c2 .c4 .c5 .c7 .c8,
 .c1 .c2 .c4 .c5 .c7 .c9,
 .c1 .c2 .c4 .c6 .c7 .c8,
 .c1 .c2 .c4 .c6 .c7 .c9,
 .c1 .c3 .c4 .c5 .c7 .c8,
 .c1 .c3 .c4 .c5 .c7 .c9,
 .c1 .c3 .c4 .c6 .c7 .c8,
 .c1 .c3 .c4 .c6 .c7 .c9 {
    font-size: 100%;
}

We’ve multiplied three straightforward lines into 8 rules. A few of these in your CSS can quite easily put you over the limit. I’ve found more than one solution, but nothing I’ve really felt confident in. I spiked a quick rack-pagespeed filter to try to automatically solve the problem, but a short investigation revealed that this avenue is not going to be a trivial effort.

In the end I did the simple thing: I just broke it up into two files. For a longer-term preventive measure, I added an acceptance spec (written with the steak framework) that will grab the compiled CSS files and alert me if any file exceeds the limit. The CSS parsing and counting is based on the CssSplitter code found here.

Here’s my spec:

feature 'Stylesheets' do
  scenario 'are all smaller than the Internet Explorer maximum' do
    stylesheets = []

    #### This will examine the HTML at the given URL and check the stylesheets
    #### linked therein. Add similar calls as needed to visit pages that expose
    #### all of your site's CSS
    stylesheets += stylesheets_at_url('/')


    stylesheets.each do |stylesheet|
      visit stylesheet
      count = count_selectors(source)

      # puts "#{stylesheet}: #{count}"

      count.should be_<(4096), "#{stylesheet} contains #{count} selectors, which exceeds Internet Explorer maximum of 4096"
    end
  end

  def stylesheets_at_url(url)
    visit url

    page.all('link[rel="stylesheet"][href]').map do |node|
      node['href']
    end
  end

  def count_selectors(css)
    css.lines("}").inject(0) { |memo, rule| memo += count_selectors_of_rule(rule) }
  end

  def count_selectors_of_rule(rule)
    rule.partition(/\{/).first.scan(/,/).count.to_i + 1
  end
end

It isn’t a perfect solution — I’d prefer if Sprockets just handled everything for me (it looks like people have worked on this, but I didn’t find anything polished). Even if it doesn’t fix the problem, it’s good to get a big red flag whenever it rears its ugly head.

Has anyone out there implemented a better solution?
 

Mike Swieton (44 Posts)

I live and work in Grand Rapids (or near enough as to make no difference).

I write software – all kinds of software. I’ve written code for worldwide enterprises and startups. I’ve build for the web, for desktop, mobile, and everything in between. I’ve worked in all sorts of different industries and with all sorts of technologies.

I always try to understand whatever I’m working on deeply no matter the subject, for anything from learning about shoulder anatomy (… what a mess that is) for weight training, to investigating using the ideal gas law when working with chocolate in a whipping siphon, or diving into a vendor’s source code to find out what’s wrong.

This entry was posted in UX/Design Tools and tagged , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

3 Comments

  1. Posted September 26, 2012 at 7:56 am

    nice solution – simple and to the point.

    • Posted January 21, 2013 at 10:12 am

      Here is a javascript snippet that tells you how many selector has each page

      https://gist.github.com/4586719

      Elie

      • Posted January 21, 2013 at 10:17 am

        Hi Elie,

        That’s cool! It’s good to see such a short solution. It’s also nice to see it in JavaScript – it means we could update the capybara test to just run the JS in the browser, which I suspect would be more reliable than using a handful of regular expressions (which always feels brittle to me, even if it hasn’t broken yet).

        Thanks for posting that!