-
-
Save ChristianPeters/2398394 to your computer and use it in GitHub Desktop.
#... | |
module MyProject | |
class Application < Rails::Application | |
config.assets.precompile += %w( ie6.css ie6_portion2.css ie7.css ie7_portion2.css ie8.css ie8_portion2.css ie9.css ie9_portion2.css) | |
#... | |
require 'css_splitter' | |
Rails.application.assets.register_engine '.split2', CssSplitter::SprocketsEngine |
//= include 'ie8.css' |
-#... | |
-# Rails Dev Boost compiles all templates that are included here regardless of browser requests | |
-# FIXME: Consider to remove Rails Dev Boost and this switch after updating to Rails 3.2 | |
-# Switch testing_for to e.g. :ie7 when testing for ie7 | |
- testing_for = nil | |
- if !Rails.env.development? || testing_for == :ie6 | |
/[if IE 6] | |
= stylesheet_link_tag :ie6 | |
= stylesheet_link_tag :ie6_portion2 | |
- if !Rails.env.development? || testing_for == :ie7 | |
/[if IE 7] | |
= stylesheet_link_tag :ie7 | |
= stylesheet_link_tag :ie7_portion2 | |
- if !Rails.env.development? || testing_for == :ie8 | |
/[if IE 8] | |
= stylesheet_link_tag :ie8 | |
= stylesheet_link_tag :ie8_portion2 | |
- if !Rails.env.development? || [:ie6, :ie7, :ie8].include?(testing_for) | |
/[if lt IE 9] | |
= javascript_include_tag "//html5shim.googlecode.com/svn/trunk/html5.js" | |
= javascript_include_tag 'ie/selectivizr' | |
- if !Rails.env.development? || testing_for == :ie9 | |
/[if IE 9] | |
= stylesheet_link_tag :ie9 | |
= stylesheet_link_tag :ie9_portion2 |
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 | |
MAX_SELECTORS_DEFAULT = 4095 | |
def self.split(infile, outdir = File.dirname(infile), max_selectors = MAX_SELECTORS_DEFAULT) | |
raise "infile could not be found" unless File.exists? infile | |
rules = IO.readlines(infile, "}") | |
return if rules.first.nil? | |
charset_statement, rules[0] = rules.first.partition(/^\@charset[^;]+;/)[1,2] | |
return if rules.nil? | |
file_id = 1 # The infile remains the first file | |
selectors_count = 0 | |
output = nil | |
rules.each do |rule| | |
rule_selectors_count = count_selectors_of_rule rule | |
selectors_count += rule_selectors_count | |
# Nothing happens until the selectors limit is reached for the first time | |
if selectors_count > max_selectors | |
# Close current file if there is already one | |
output.close if output | |
# Prepare next file | |
file_id += 1 | |
filename = File.join(outdir, File.basename(infile, File.extname(infile)) + "_#{file_id.to_s}" + File.extname(infile)) | |
output = File.new(filename, "w") | |
output.write charset_statement | |
# Reset count with current rule count | |
selectors_count = rule_selectors_count | |
end | |
output.write rule if output | |
end | |
end | |
def self.split_string(css_string, part = 1, max_selectors = MAX_SELECTORS_DEFAULT) | |
rules = split_string_into_rules(css_string) | |
extract_part rules, part, max_selectors | |
end | |
def self.split_string_into_rules(css_string) | |
strip_comments(css_string).chomp.scan /[^}]*}/ | |
end | |
def self.extract_part(rules, part = 1, max_selectors = MAX_SELECTORS_DEFAULT) | |
return if rules.first.nil? | |
charset_statement, rules[0] = rules.first.partition(/^\@charset[^;]+;/)[1,2] | |
return if rules.nil? | |
output = charset_statement | |
selectors_count = 0 | |
selector_range = max_selectors * (part - 1) + 1 .. max_selectors * part | |
rules.each do |rule| | |
rule_selectors_count = count_selectors_of_rule rule | |
selectors_count += rule_selectors_count | |
if selector_range.cover? selectors_count | |
output << rule | |
elsif selectors_count > selector_range.end | |
break | |
end | |
end | |
output | |
end | |
def self.count_selectors(css_file) | |
raise "file could not be found" unless File.exists? css_file | |
rules = IO.readlines(css_file, '}') | |
return if rules.first.nil? | |
charset_statement, rules[0] = rules.first.partition(/^\@charset[^;]+;/)[1,2] | |
return if rules.first.nil? | |
rules.sum {|rule| count_selectors_of_rule(rule)}.tap do |result| | |
puts File.basename(css_file) + " contains #{result} selectors." | |
end | |
end | |
def self.count_selectors_of_rule(rule) | |
strip_comments(rule).partition(/\{/).first.scan(/,/).count.to_i + 1 | |
end | |
private | |
def self.strip_comments(s) | |
s.gsub(/\/\/.*$/, "").gsub(/\/\*.*?\*\//, "") | |
end | |
end |
This is great, thanks! Does this work for the assets:precompile? it doesn't for me so I am wondering what the best way to do this for production? The only way I have thought of so far is to patch the Sprockets::StaticCompiler ... hoping there is a better way.
It does. Maybe I have forgotten to extract something from our app. Please help me finding out what's missing by providing more information on the error.
I get no errors it's just that assets:precompile task doesn't produce the secondary ie file.
hi, Christian,
Could you please provide more details on how to add SprocketsEngine to an application Assets Pipeline? At what point should we include this engine to make sure that digests for all files are generated?
Hi, @ChristianPeters I have the same question with @mkurutin, on https://gist.github.com/2398394#gistcomment-342203
I would appreciate if you could give some more details
Okay, I think I have been missing an important piece.
it's just that assets:precompile task doesn't produce the secondary ie file.
That's right, the asset pipeline cannot produce extra files. It has to be there upfront.
Have a closer look at the CssSplitter::SprocketsEngine#evaluate:
def evaluate(scope, locals, &block)
part = scope.pathname.extname =~ /(\d+)$/ && $1 || 0
CssSplitter.split_string data, part.to_i
end
All it does is evaluating / manipulating an existing asset file. The workaround we came up with is providing the same file multiple times with the same content but based on the file extension different parts are extracted using the CssSplitter.
So we have a ie8_portion2.css.split2
:
//= include 'ie8.css'
See complete asset folder structure
The regex matches the 2
of the file extension split2
and hands it on to the CssSplitter: CssSplitter.split_string(data, part.to_i)
The result is a compiled ie8_portion2.css
- including only the second part > 4095 selectors.
You could add further files if your CSS file is bigger than 8190 selectors.
This mechanism works fine on a Rails 3.1 project with asset pipeline turned on and fully automated asset compilation during deployment. Let me know if this does not work for you.
Hi @ChristianPeters,
pretty nifty workaround with the .splitx
file endings.
Any plans to turn this into a gem?
This is based on https://gist.github.com/1131536, extended by a Sprockets Engine for integration with the Rails Asset Pipeline.