Rails asset pipeline solution for IE 4096 selector/stylesheet limit
Microsoft's IE support documentation explains that in Internet Explorer 6-9:
- All style tags after the first 31 style tags are not applied.
- All style rules after the first 4,095 rules are not applied.
- On pages that uses the @import rule to continously import external style sheets that import other style sheets, style sheets that are more than three levels deep are ignored.
We need a way to split up compiled stylesheets generated by Sprockets in the asset pipeline to keep the max selector count below 4096, and link to them in the HTML of a deployed Rails application. How can we pass the compiled output of processed assets (specifically stylesheets) as an argument to a method that can then modify the files?
See the below attempts for a place to start. If someone could help me find a way to make either operational (or an entirely new solution), that would be fantastic!
Existing solution attempts
Bless was created to solve this problem by splitting up stylesheets to keep the max selector count per sheet under the limit. Bless runs on the server in node.js. I haven't seen a Ruby-equivalent yet. Eric Fields tried to serve assets compiled with compass to Bless (running in node), but that solution depends on Compass handling asset compilation, and thus doesn't seem to work with the asset pipeline. Note that instead of linking to multiple stylesheets, Bless adds @include statements to the first sheet, which may be the way to go so as to avoid touching the markup.
When Christian Peters (@crispy) discovered this problem, he implemented a splitter like Bless that also passed Compass output to a custom module, which worked great before Rails 3.1. Later he adapted his splitter with a SprocketsEngine for integration with the Rails Asset pipeline. I've tried implementing the new code, but it doesn't seem to function automatically (though the splitter works fine when called manually in the console).
For more information on the CSS limits in IE 6-9, see these related questions:
We have an automated (albeit somehow awkward) solution working in production for a Rails 3.1 app with asset pipeline in place. Ryan already referenced the solution in his question but I try to come up with a more comprehensive answer.
The asset pipeline pipes an asset through different Sprocket engines.
So you might have e.g. a ie.css.sass.erb that runs through the ERB Sprocket engine and then passed to the Sass Sprocket engine etc. But it is always one file in and one file out.
In this special problem, we would like to have 1 inbound file and n outbound files. We have not found a way to make this possible with sprockets. But we found a workaround:
Provide an ie.css.sass that includes the complete stylesheet and a ie_portion2.css.sass.split2 that just imports the complete ie.css file:
//= include 'ie.css'
For the split2 file extension, we register a Sprockets Engine:
require 'css_splitter' Rails.application.assets.register_engine '.split2', CssSplitter::SprocketsEngine
When evaluating assets with the split2 extension, we pass its content to the CssSplitter and instruct it to extract the part 2 (> 4095 selectors):
require 'tilt' module CssSplitter class SprocketsEngine < Tilt::Template def self.engine_initialized? true end def prepare end def evaluate(scope, locals, &block) part = scope.pathname.extname =~ /(\d+)$/ && $1 || 0 CssSplitter.split_string data, part.to_i end end end
This would also work for further parts (split3, ...).
The CSS Splitter recognizes valid places where stylesheets can be split into parts with less than 4096 selectors and returns the requested part.
The result is a ie_portion2.css which you have to link in the head and precompile separately.
I hope my revised CSS Splitter Gist is complete enough to employ the solution.
The CssSplitter mentionend above has been release as a gem now: https://github.com/zweilove/css_splitter
The solution I'm using in production is very simple, not automated, but works very well. To me this was the obvious thing to do, so maybe you thought about it already and did not like it - either way, here we go:
I'm assuming you're using sass, if not, I think you should :)
First, split your application.css.scss in seperate files, e.g.: application_a.css.scss and application_b.css.scss
Second, in your application.css.scss file, use:
@import "application_a" @import "application_b"
Third, in your layout template, include either the complete file, or both parts:
<!--[if !IE]><!--> # link to application.css.scss <!--<![endif]--> <!--[if IE]> # link to application_a.css.scss # link to application_b.css.scss <![endif]-->
Side note: Do not generate your stylesheet manifest files via the asset pipeline, do it via sass and it's @import statement, everything else will lead to problems.