diff --git a/.gitignore b/.gitignore index 1dfe31e..a9ecfb8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .bundle/ +Gemfile.lock log/*.log pkg/ test/dummy/db/*.sqlite3 diff --git a/.travis.yml b/.travis.yml index 33fb9e8..f09ded4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,9 @@ +sudo: false language: ruby bundler_args: --without development rvm: - - "1.9.3" \ No newline at end of file + - "1.9.3" + - "2.1" + - "2.2" +gemfile: + - "Gemfile" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a9235..cd411e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +# 0.4.6 + +* [fix] Implement new engine interface for future sprockets versions #70 +* [bugfix] Fix issue where helper is not a defined method on controller (e.g. ActionController::API:Class) #65 + +# 0.4.5 + +* [bugfix] fix sprockets engine registering for older sprocket-rails versions #68 + +# 0.4.4 + +* [bugfix] fix compatibility with with sprockts-rails version 3.x, see #62 + +# 0.4.3 + +* [bugfix] for media queries with whitespace in front of them #57 + +# 0.4.2 + +* [bugfix] correctly split stylesheets even if @keyframes are directly on the rule limit #55 by [@rubenswieringa](https://github.com/rubenswieringa) + +# 0.4.1 + +* [Improvement] All `*_splitN.css` files default to `debug: false` in development to prevent empty file bug. + +# 0.4.0 + +* **Breaking changes!** +* The `CssSplitter::SprocketsEngine` is now registered as a bundle_processor to avoid issues with sprockets directives #29 + * `.split2` extension is no longer necessary/supported, now we rely on `_splitN` at the end of the filename + * Now you need to use the `require` rather than the `include` directive in the split stylesheet + * Prohibition against using `require_tree .` and `require_self` directives no longer applies + * Better tests + * Thanks a lot to [@Umofomia](https://github.com/Umofomia) +* loosen dependency on `rails` (depend on `sprockets` instead), to make gem compatible to other frameworks like `middleman` + +# 0.2.0 + +* loosen dependency to make it compatible with rails 4 + # 0.1.1 * Added license info ("MIT") to gemspec @@ -17,4 +57,4 @@ # 0.0.1 -Initial commit \ No newline at end of file +Initial commit diff --git a/Gemfile b/Gemfile index 4042fd6..74fab5c 100644 --- a/Gemfile +++ b/Gemfile @@ -6,16 +6,10 @@ source "http://rubygems.org" gemspec group :development do - gem "pry-debugger" + gem 'pry-byebug' end + +gem "rails" gem "sass-rails" gem "jquery-rails" gem "uglifier" - -# Declare any dependencies that are still in development here instead of in -# your gemspec. These might include edge Rails or gems from your path or -# Git. Remember to move these dependencies to your gemspec before releasing -# your gem to rubygems.org. - -# To use debugger -# gem 'debugger' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 90fb3a8..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,126 +0,0 @@ -PATH - remote: . - specs: - css_splitter (0.1.1) - rails (~> 3.1) - -GEM - remote: http://rubygems.org/ - specs: - actionmailer (3.2.13) - actionpack (= 3.2.13) - mail (~> 2.5.3) - actionpack (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) - activerecord (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - activesupport (3.2.13) - i18n (= 0.6.1) - multi_json (~> 1.0) - arel (3.0.2) - builder (3.0.4) - coderay (1.0.9) - columnize (0.3.6) - debugger (1.5.0) - columnize (>= 0.3.1) - debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.2.0) - debugger-linecache (1.2.0) - debugger-ruby_core_source (1.2.0) - erubis (2.7.0) - execjs (1.4.0) - multi_json (~> 1.0) - hike (1.2.1) - i18n (0.6.1) - journey (1.0.4) - jquery-rails (2.2.1) - railties (>= 3.0, < 5.0) - thor (>= 0.14, < 2.0) - json (1.7.7) - mail (2.5.3) - i18n (>= 0.4.0) - mime-types (~> 1.16) - treetop (~> 1.4.8) - method_source (0.8.1) - mime-types (1.21) - multi_json (1.7.1) - polyglot (0.3.3) - pry (0.9.12) - coderay (~> 1.0.5) - method_source (~> 0.8) - slop (~> 3.4) - pry-debugger (0.2.2) - debugger (~> 1.3) - pry (~> 0.9.10) - rack (1.4.5) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.3) - rack - rack-test (0.6.2) - rack (>= 1.0) - rails (3.2.13) - actionmailer (= 3.2.13) - actionpack (= 3.2.13) - activerecord (= 3.2.13) - activeresource (= 3.2.13) - activesupport (= 3.2.13) - bundler (~> 1.0) - railties (= 3.2.13) - railties (3.2.13) - actionpack (= 3.2.13) - activesupport (= 3.2.13) - rack-ssl (~> 1.3.2) - rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.0.3) - rdoc (3.12.2) - json (~> 1.4) - sass (3.2.6) - sass-rails (3.2.6) - railties (~> 3.2.0) - sass (>= 3.1.10) - tilt (~> 1.3) - slop (3.4.4) - sprockets (2.2.2) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - thor (0.17.0) - tilt (1.3.6) - treetop (1.4.12) - polyglot - polyglot (>= 0.3.1) - tzinfo (0.3.37) - uglifier (1.3.0) - execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) - -PLATFORMS - ruby - -DEPENDENCIES - css_splitter! - jquery-rails - pry-debugger - sass-rails - uglifier diff --git a/README.md b/README.md index a52bd97..d1b3805 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -# CssSplitter [![Build Status](https://travis-ci.org/zweilove/css_splitter.png?branch=master)](https://travis-ci.org/zweilove/css_splitter) [![Dependency Status](https://gemnasium.com/zweilove/css_splitter.png)](https://gemnasium.com/zweilove/css_splitter) +# CssSplitter [![Build Status](https://travis-ci.org/zweilove/css_splitter.png?branch=master)](https://travis-ci.org/zweilove/css_splitter) Gem for splitting up stylesheets that go beyond the IE limit of 4096 selectors, for Rails 3.1+ apps using the Asset Pipeline. You can read this [blogpost](http://railslove.com/blog/2013/03/08/overcoming-ies-4096-selector-limit-using-the-css-splitter-gem) for an explanation of this gem's background story. +### Development status + +Fortunately, the problem of too large CSS files is long gone. This repo is unmaintained. +It remains as an artefact of dark times in the history of web browsers. ## Installation @@ -16,22 +20,26 @@ CssSplitter integrates with the Rails 3.1+ Asset Pipeline to generate additional ## Dependencies -* Rails 3.1+ -* Asset Pipeline +* Sprockets 2.0+ +* e.g. Rails 3.1+ with the asset pipeline ## Documentation ### 1. Splitting your stylesheets -The first step is indentifying the stylesheets that have more than 4095 selectors and therefore need to be split for IE. +The first step is identifying the stylesheets that have more than 4095 selectors and therefore need to be split for IE. + +Once you know which stylesheets need to be split, you need to create a second "container file" for those stylesheets with the `_split2` suffix appended to the base filename that will contain the styles beyond the 4095 selector limit. The extension of this file should be just `.css` without any additional preprocessor extensions. -Once you know which stylesheets need to be split, you need to create a second "container file" for those stylesheets with the file extension `.split2`, that will contain the styles beyond the 4095 selector limit. +For example, if you want to split `too_big_stylesheet.css.scss`, you need to create a new file `too_big_stylesheet_split2.css` in the same directory. The only content of that container, will contain a `require` directive to the name of the original asset, e.g.: -For example, if you want to split `too_big_stylesheet.css`, you need to create a new file `too_big_stylesheet_split2.css.split2` in the same directory. The only content of that container, will be an include of the original file, e.g.: + # app/assets/stylesheets/too_big_stylesheet_split2.css - # app/assets/stylesheets/too_big_stylesheet_split2.css.split2 + /* + *= require 'too_big_stylesheet' + */ - //= include 'too_big_stylesheet.css' +If your stylesheet is big enough to need splitting into more than two more files, simply create additional `_split3`, `_split4`, etc. files, the contents of which should be identical to the `_split2` file. You also need to remember to add those new files to the asset pipeline, so they will be compiled. For example: @@ -43,12 +51,10 @@ You also need to remember to add those new files to the asset pipeline, so they Here is a checklist of requirements for your split stylesheet: -1. It needs to have different filename than orginal, e.g. `original_stylesheet_split2` or `application_split2` -2. It needs to have `.split2` as the terminal file extension, e.g. `.css.split2` or `.css.sass.split2` -3. It needs to include the content of the orginal stylesheet, e.g. through `//= include 'application'` -4. It needs to be added to list of precompiled assets - - +1. It needs to have the `_splitN` suffix appended to the original asset name, e.g. `original_stylesheet_split2` or `application_split2` +2. It needs to have `.css` as a file extension. +3. It needs to require the orginal stylesheet. +4. It needs to be added to list of precompiled assets. ### 2. Including your split stylesheets @@ -60,62 +66,46 @@ You can just use our `split_stylesheet_link_tag` helper, which would look someth <%= split_stylesheet_link_tag "too_big_stylesheet", :media => "all" %> # output - + +If your stylesheet is split into more than two files, add the `split_count` option to specify the total number of files. + + <%= split_stylesheet_link_tag "too_big_stylesheet", :split_count => 3 %> + Or you can just create similar HTML as in the above example yourself. If you want to use the `split_stylesheet_link_tag` helper you need to make sure the gem is loaded in production, so you can't put it in the `:assets` group in your Gemfile. ## How it works -Basically, CssSplitter is registering a new `Sprockets::Engine` for the `.split2` file extension, that will fill those files with all the selectors beyond the 4095th. Unfortunately, those `.split2` files need to be created manually, because we haven't figured out a way for a `Sprockets::Engine` to output multiple files. They need to present before the compile step. +Basically, CssSplitter is registering a new Sprockets bundle processor that looks for CSS assets named with the `_splitN` suffix and will fill those files with all the selectors beyond the 4095th. Unfortunately, those `_splitN` files need to be created manually, because we haven't figured out a way for a `Sprockets::Engine` to output multiple files. They need to present before the compile step. If you have more questions about how it works, look at the code or contact us. ## Gotchas -#### Having a JS asset with the same name as the the split stylesheet - -If you want to split a style (e.g. `assets/stylesheets/application.*`) and have a JS asset with the same name (`assets/javascripts/application.*`) in your asset load_path (as is the default in Rails), you need to include the stylesheet along with the file extension `// = include 'application.css'` because otherwise it will try to include the JS asset of the same name instead. Sprocket's `= include` directive doesn't seem to differentiate between different types/folders and just takes the first asset it can find for any given name (see #10). - -#### Don't use Sprocket's `= require_tree .` or `= require_self` for stylesheets -It's recommended that you **always use Sass's `@import`** for all your stylesheets in favor of Sprocket's `= require` directives, just as the official `sass-rails` gem says: https://github.com/rails/sass-rails#important-note +#### Differences from previous versions -If you have a `.split2` stylesheet in your tree that in turn includes the base stylesheet like shown below, you will end up with a nasty `Sprockets::CircularDependencyError`! +Note that if you used versions below `0.4.0` of this gem, the naming and contents of the split files have changed. Split files no longer need to have the `.split2` extension and now use the `require` directive rather than the `include` directive. The previous prohibition against using `require_tree .` and `require_self` directives also no longer applies. For more details see the [CHANGELOG.md](CHANGELOG.md#040) - /* assets/stylesheets/application.css */ - /* = require_tree . - - /* assets/stylesheets/application_split2.css.split2 */ - /* = include 'application.css' */ +#### Empty *_split2.css file -If you have `require_self` in the stylesheet that you're splitting, as shown below, the `.split2` will end up having **both** the original stylesheet and the split contents. You'll end up with an even bigger stylesheet. +Since 0.4.1 in development split stylesheets have `debug: false` option by default. This prevents the empty `*_split2.css` file issue. You can always explicitly go one way or the other setting `debug` option directly in the `split_stylesheet_link_tag` like this: - /* assets/stylesheets/application.css */ - /* = require_self - - /* assets/stylesheets/application_split2.css.split2 */ - /* = include 'application.css' */ +``` +<%= split_stylesheet_link_tag "application", debug: false %> +``` +## Credits & License -## Limitations & Known Issues - -**More than 8190 selectors** - -Currently the gem only supports stylesheets that need to be split into 2 files. It could theoretically create more splits (e.g. if you should have more than 8190 selectors), but in that case you should probably refactor your stylesheets anyway. Contact us, if you have this requirement. - -**@media queries** - -The selector counting algorithm is currently not counting `@media` queries correctly. For each `@media` query it is adding one additional selector to the count (which is actually not a problem in most cases). - -If you have a `@media` query spawning right over the 4096 selector barrier, it will probably get ripped apart into the two splits and ultimately produce broken CSS. You can either try to move the `@media` queries (e.g. before the 4096 selector barrier) or help us fix this issue. - +This is a joint project by the two German Rails shops [Zweitag](https://zweitag.de) and [Railslove](https://railslove.com), therefore the GitHub name "Zweilove". -## Credits & License +The original code was written by Christian Peters and Thomas Hollstegge (see this [Gist](https://gist.github.com/2398394)) and turned into a gem by Jakob Hilden. -This is a joint project by the two German Rails shops [Zweitag](http://zweitag.de) and [Railslove](http://railslove.com), therefore the GitHub name "Zweilove". +**Major Contributors** -The original code was written by [Christian Peters](mailto:christian.peters@zweitag.de) and [Thomas Hollstegge](mailto:thomas.hollstegge@zweitag.de) (see this [Gist](https://gist.github.com/2398394)) and turned into a gem by [Jakob Hilden](mailto:jakobhilden@gmail.com). +* [@Umofomia](https://github.com/Umofomia) +* [@kruszczynski](https://github.com/kruszczynski) -This project rocks and uses MIT-LICENSE. +This project uses MIT-LICENSE. diff --git a/app/helpers/css_splitter/application_helper.rb b/app/helpers/css_splitter/application_helper.rb index 644a071..ecfd2ef 100644 --- a/app/helpers/css_splitter/application_helper.rb +++ b/app/helpers/css_splitter/application_helper.rb @@ -1,18 +1,24 @@ module CssSplitter module ApplicationHelper def split_stylesheet_link_tag(*sources) - original_sources = sources.dup + options = sources.extract_options! + split_count = options.delete(:split_count) || 2 - options = sources.extract_options! - sources.collect!{ |source| "#{source}_split2" } - sources << options + sources.map do |source| + split_sources = (2..split_count).map { |index| "#{source}_split#{index}" } + split_options = options.dup + if Rails.env == 'development' && !split_options.key?(:debug) + split_options[:debug] = false + end + split_sources << split_options - [ - stylesheet_link_tag(*original_sources), - "" - ].join("\n").html_safe + [ + stylesheet_link_tag(source, options), + "" + ] + end.flatten.join("\n").html_safe end end -end \ No newline at end of file +end diff --git a/css_splitter.gemspec b/css_splitter.gemspec index 84286e8..4a19196 100644 --- a/css_splitter.gemspec +++ b/css_splitter.gemspec @@ -17,5 +17,7 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"] s.test_files = Dir["test/**/*"] - s.add_dependency "rails", "~> 3.1" + s.add_dependency "sprockets", ">= 2.0.0" + + s.add_development_dependency "rails", ">= 3.1" end diff --git a/lib/css_splitter.rb b/lib/css_splitter.rb index 7a19c44..a5b2457 100644 --- a/lib/css_splitter.rb +++ b/lib/css_splitter.rb @@ -1,4 +1,4 @@ -require "css_splitter/engine" +require "css_splitter/engine" if defined?(Rails) require "css_splitter/sprockets_engine" require "css_splitter/splitter" diff --git a/lib/css_splitter/engine.rb b/lib/css_splitter/engine.rb index 4352159..05a1fae 100644 --- a/lib/css_splitter/engine.rb +++ b/lib/css_splitter/engine.rb @@ -1,14 +1,19 @@ module CssSplitter class Engine < ::Rails::Engine - isolate_namespace CssSplitter - initializer 'css_splitter.sprockets_engine', after: 'sprockets.environment', group: :all do |app| - app.assets.register_engine '.split2', CssSplitter::SprocketsEngine + if app.config.assets.public_methods.include? :configure + app.config.assets.configure do |assets| + assets.register_bundle_processor 'text/css', CssSplitter::SprocketsEngine + end + else + app.assets.register_bundle_processor 'text/css', CssSplitter::SprocketsEngine + end end initializer 'css_splitter.action_controller' do |app| ActiveSupport.on_load :action_controller do - helper CssSplitter::ApplicationHelper + # Not all controllers use helpers (such as API based controllers) + helper CssSplitter::ApplicationHelper if respond_to?(:helper) end end end diff --git a/lib/css_splitter/splitter.rb b/lib/css_splitter/splitter.rb index c979faf..f5f6a25 100644 --- a/lib/css_splitter/splitter.rb +++ b/lib/css_splitter/splitter.rb @@ -12,7 +12,28 @@ def self.split_string(css_string, split = 1, max_selectors = MAX_SELECTORS_DEFAU # splits string into array of rules (also strips comments) def self.split_string_into_rules(css_string) - strip_comments(css_string).chomp.scan /[^}]*}/ + partial_rules = strip_comments(css_string).chomp.scan /[^}]*}/ + whole_rules = [] + bracket_balance = 0 + in_media_query = false + + partial_rules.each do |rule| + if rule =~ /^\s*@media/ + in_media_query = true + elsif bracket_balance == 0 + in_media_query = false + end + + if bracket_balance == 0 || in_media_query + whole_rules << rule + else + whole_rules.last << rule + end + + bracket_balance += get_rule_bracket_balance rule + end + + whole_rules end # extracts the specified part of an overlong CSS string @@ -26,23 +47,50 @@ def self.extract_part(rules, part = 1, max_selectors = MAX_SELECTORS_DEFAULT) selectors_count = 0 selector_range = max_selectors * (part - 1) + 1 .. max_selectors * part # e.g (4096..8190) + current_media = nil + selectors_in_media = 0 + first_hit = true rules.each do |rule| + media_part = extract_media(rule) + if media_part + current_media = media_part + selectors_in_media = 0 + end + rule_selectors_count = count_selectors_of_rule rule selectors_count += rule_selectors_count + if rule =~ /\A\s*}\z$/ + current_media = nil + # skip the line if the close bracket is the first rule for the new file + next if first_hit + end + if selector_range.cover? selectors_count # add rule to current output if within selector_range + if media_part + output << media_part + elsif first_hit && current_media + output << current_media + end + selectors_in_media += rule_selectors_count if current_media.present? output << rule + first_hit = false elsif selectors_count > selector_range.end # stop writing to output break end end + if current_media.present? and selectors_in_media > 0 + output << '}' + end + output end # count selectors of one individual CSS rule def self.count_selectors_of_rule(rule) - strip_comments(rule).partition(/\{/).first.scan(/,/).count.to_i + 1 + parts = strip_comments(rule).partition(/\{/) + parts.second.empty? ? 0 : parts.first.scan(/,/).count.to_i + 1 end @@ -61,6 +109,12 @@ def self.count_selectors(css_file) private + def self.extract_media(rule) + if rule.sub!(/^\s*(@media[^{]*{)([^{}]*{[^}]*})$/) { $2 } + $1 + end + end + # extracts potential charset declaration from the first rule def self.extract_charset(rule) if rule.include?('charset') @@ -74,6 +128,10 @@ def self.strip_comments(s) s.gsub(/\/\*.*?\*\//m, "") end + def self.get_rule_bracket_balance ( rule ) + rule.scan( /}/ ).size - rule.scan( /{/ ).size + end + end end diff --git a/lib/css_splitter/sprockets_engine.rb b/lib/css_splitter/sprockets_engine.rb index 9f40c03..551af8c 100644 --- a/lib/css_splitter/sprockets_engine.rb +++ b/lib/css_splitter/sprockets_engine.rb @@ -10,10 +10,30 @@ def self.engine_initialized? def prepare end + def self.call(input) + data_in = input[:data] + + # Instantiate Sprockets::Context to pass along helper methods for Tilt + # processors + context = input[:environment].context_class.new(input) + + # Pass the asset file contents as a block to the template engine, + # then get the results of the engine rendering + engine = self.new { data_in } + rendered_data = engine.render(context, {}) + + # Return the data and any metadata (ie file dependencies, etc) + context.metadata.merge(data: rendered_data.to_str) + end + def evaluate(scope, locals, &block) - split = scope.pathname.extname =~ /(\d+)$/ && $1 || 0 # determine which is the current split (e.g. split2, split3) - CssSplitter::Splitter.split_string data, split.to_i + # Evaluate the split if the asset is named with a trailing _split2, _split3, etc. + if scope.logical_path =~ /_split(\d+)$/ + CssSplitter::Splitter.split_string(data, $1.to_i) + else + data + end end end -end \ No newline at end of file +end diff --git a/lib/css_splitter/version.rb b/lib/css_splitter/version.rb index d866b50..57859d1 100644 --- a/lib/css_splitter/version.rb +++ b/lib/css_splitter/version.rb @@ -1,3 +1,3 @@ module CssSplitter - VERSION = "0.1.1" + VERSION = "0.4.6" end diff --git a/test/css_splitter_test.rb b/test/css_splitter_test.rb index 7fd8634..012cf89 100644 --- a/test/css_splitter_test.rb +++ b/test/css_splitter_test.rb @@ -1,7 +1,40 @@ require 'test_helper' class CssSplitterTest < ActiveSupport::TestCase + + setup :clear_assets_cache + test "truth" do assert_kind_of Module, CssSplitter end + + test "asset pipeline stylesheet splitting" do + part1 = "#test{background-color:red}" * CssSplitter::Splitter::MAX_SELECTORS_DEFAULT + part2 = "#test{background-color:green}" * CssSplitter::Splitter::MAX_SELECTORS_DEFAULT + part3 = "#test{background-color:blue}" + + assert_equal "#{part1}#{part2}#{part3}", assets["erb_stylesheet"].to_s.gsub(/\s/, '') + assert_equal "#{part2}", assets["erb_stylesheet_split2"].to_s.gsub(/\s/, '') + assert_equal "#{part3}", assets["erb_stylesheet_split3"].to_s.gsub(/\s/, '') + end + + test "asset pipeline stylesheet splitting on stylesheet combined using requires" do + red = "#test{background-color:red}" * 100 + green = "#test{background-color:green}" * CssSplitter::Splitter::MAX_SELECTORS_DEFAULT + blue = "#test{background-color:blue}" + assert_equal "#{red}#{green}#{blue}", assets["combined"].to_s.gsub(/\s/, '') + assert_equal "#{"#test{background-color:green}" * 100}#{blue}", assets["combined_split2"].to_s.gsub(/\s/, '') + end + + private + + def clear_assets_cache + assets_cache = Rails.root.join("tmp/cache/assets") + assets_cache.rmtree if assets_cache.exist? + end + + def assets + Rails.application.assets + end + end diff --git a/test/dummy/app/assets/stylesheets/combined.css.scss b/test/dummy/app/assets/stylesheets/combined.css.scss new file mode 100644 index 0000000..5fd8ab4 --- /dev/null +++ b/test/dummy/app/assets/stylesheets/combined.css.scss @@ -0,0 +1,4 @@ +//= require 'red_100' +//= require 'green_max' + +#test { background-color: blue; } diff --git a/test/dummy/app/assets/stylesheets/combined_split2.css b/test/dummy/app/assets/stylesheets/combined_split2.css new file mode 100644 index 0000000..cc3963c --- /dev/null +++ b/test/dummy/app/assets/stylesheets/combined_split2.css @@ -0,0 +1,3 @@ +/* + *= require 'combined' + */ diff --git a/test/dummy/app/assets/stylesheets/erb_stylesheet.css.scss.erb b/test/dummy/app/assets/stylesheets/erb_stylesheet.css.scss.erb new file mode 100644 index 0000000..de64edc --- /dev/null +++ b/test/dummy/app/assets/stylesheets/erb_stylesheet.css.scss.erb @@ -0,0 +1,7 @@ +<% CssSplitter::Splitter::MAX_SELECTORS_DEFAULT.times do %> +#test { background-color: red; } +<% end %> +<% CssSplitter::Splitter::MAX_SELECTORS_DEFAULT.times do %> +#test { background-color: green; } +<% end %> +#test { background-color: blue; } diff --git a/test/dummy/app/assets/stylesheets/erb_stylesheet_split2.css b/test/dummy/app/assets/stylesheets/erb_stylesheet_split2.css new file mode 100644 index 0000000..2c6b6e1 --- /dev/null +++ b/test/dummy/app/assets/stylesheets/erb_stylesheet_split2.css @@ -0,0 +1,3 @@ +/* + *= require 'erb_stylesheet.css' + */ diff --git a/test/dummy/app/assets/stylesheets/erb_stylesheet_split3.css b/test/dummy/app/assets/stylesheets/erb_stylesheet_split3.css new file mode 100644 index 0000000..2c6b6e1 --- /dev/null +++ b/test/dummy/app/assets/stylesheets/erb_stylesheet_split3.css @@ -0,0 +1,3 @@ +/* + *= require 'erb_stylesheet.css' + */ diff --git a/test/dummy/app/assets/stylesheets/green_max.css.scss.erb b/test/dummy/app/assets/stylesheets/green_max.css.scss.erb new file mode 100644 index 0000000..df75a7f --- /dev/null +++ b/test/dummy/app/assets/stylesheets/green_max.css.scss.erb @@ -0,0 +1,3 @@ +<% CssSplitter::Splitter::MAX_SELECTORS_DEFAULT.times do %> +#test { background-color: green; } +<% end %> diff --git a/test/dummy/app/assets/stylesheets/red_100.css.scss.erb b/test/dummy/app/assets/stylesheets/red_100.css.scss.erb new file mode 100644 index 0000000..ae6effd --- /dev/null +++ b/test/dummy/app/assets/stylesheets/red_100.css.scss.erb @@ -0,0 +1,3 @@ +<% 100.times do %> +#test { background-color: red; } +<% end %> diff --git a/test/dummy/app/assets/stylesheets/test_stylesheet_with_media_queries_split2.css b/test/dummy/app/assets/stylesheets/test_stylesheet_with_media_queries_split2.css new file mode 100644 index 0000000..23b2f80 --- /dev/null +++ b/test/dummy/app/assets/stylesheets/test_stylesheet_with_media_queries_split2.css @@ -0,0 +1,3 @@ +/* + *= require 'test_stylesheet_with_media_queries' + */ diff --git a/test/dummy/app/assets/stylesheets/test_stylesheet_with_media_queries_split2.css.split2 b/test/dummy/app/assets/stylesheets/test_stylesheet_with_media_queries_split2.css.split2 deleted file mode 100644 index b8ab087..0000000 --- a/test/dummy/app/assets/stylesheets/test_stylesheet_with_media_queries_split2.css.split2 +++ /dev/null @@ -1 +0,0 @@ -//= include 'test_stylesheet_with_media_queries' \ No newline at end of file diff --git a/test/dummy/app/assets/stylesheets/too_big_stylesheet_split2.css b/test/dummy/app/assets/stylesheets/too_big_stylesheet_split2.css new file mode 100644 index 0000000..1cf401d --- /dev/null +++ b/test/dummy/app/assets/stylesheets/too_big_stylesheet_split2.css @@ -0,0 +1,3 @@ +/* + *= require 'too_big_stylesheet.css' + */ diff --git a/test/dummy/app/assets/stylesheets/too_big_stylesheet_split2.css.split2 b/test/dummy/app/assets/stylesheets/too_big_stylesheet_split2.css.split2 deleted file mode 100644 index ffa49f3..0000000 --- a/test/dummy/app/assets/stylesheets/too_big_stylesheet_split2.css.split2 +++ /dev/null @@ -1 +0,0 @@ -//= include 'too_big_stylesheet.css' \ No newline at end of file diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index 9fa37cd..f9e8544 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -5,7 +5,7 @@ <%= stylesheet_link_tag "application", :media => "all" %> - <%= split_stylesheet_link_tag "too_big_stylesheet", :media => "all" %> + <%= split_stylesheet_link_tag "too_big_stylesheet", :media => "all", :debug => true %> <%= csrf_meta_tags %> diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb index 9f787b4..7a5b60e 100644 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -3,7 +3,6 @@ # require 'rails/all' require "action_controller/railtie" require "action_mailer/railtie" -require "active_resource/railtie" require "rails/test_unit/railtie" require "sprockets/railtie" @@ -39,7 +38,8 @@ class Application < Rails::Application # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] - + config.sass.line_comments = false + config.assets.compress = true # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true @@ -56,9 +56,6 @@ class Application < Rails::Application # Enable the asset pipeline config.assets.enabled = true - - # Version of your assets, change this if you want to expire all your assets - config.assets.version = '1.0' end end diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb index 50105b3..ef8997c 100644 --- a/test/dummy/config/environments/development.rb +++ b/test/dummy/config/environments/development.rb @@ -34,4 +34,6 @@ # Expands the lines which load the assets config.assets.debug = true + + config.eager_load = false end diff --git a/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb index b178724..d92c150 100644 --- a/test/dummy/config/environments/production.rb +++ b/test/dummy/config/environments/production.rb @@ -9,7 +9,7 @@ config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) - config.serve_static_assets = false + config.serve_static_files = false # Compress JavaScripts and CSS config.assets.compress = true @@ -31,7 +31,7 @@ # config.force_ssl = true # See everything in the log (default is :info) - # config.log_level = :debug + config.log_level = :debug # Prepend all log lines with the following tags # config.log_tags = [ :subdomain, :uuid ] @@ -45,9 +45,6 @@ # Enable serving of images, stylesheets, and JavaScripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" - # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) - config.assets.precompile += %w( too_big_stylesheet.css too_big_stylesheet_split2.css test_stylesheet_with_media_queries.css test_stylesheet_with_media_queries_split2.css ) - # Disable delivery errors, bad email addresses will be ignored # config.action_mailer.raise_delivery_errors = false @@ -64,4 +61,6 @@ # Log the query plan for queries taking more than this (works # with SQLite, MySQL, and PostgreSQL) # config.active_record.auto_explain_threshold_in_seconds = 0.5 + + config.eager_load = true end diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb index 6feb3d2..3171e7c 100644 --- a/test/dummy/config/environments/test.rb +++ b/test/dummy/config/environments/test.rb @@ -8,7 +8,7 @@ config.cache_classes = true # Configure static asset server for tests with Cache-Control for performance - config.serve_static_assets = true + config.serve_static_files = true config.static_cache_control = "public, max-age=3600" # Log error messages when you accidentally call methods on nil @@ -29,9 +29,14 @@ # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test + # run tests in a random order + config.active_support.test_order = :random + # Raise exception on mass assignment protection for Active Record models # config.active_record.mass_assignment_sanitizer = :strict # Print deprecation notices to the stderr config.active_support.deprecation = :stderr + + config.eager_load = false end diff --git a/test/dummy/config/initializers/assets.rb b/test/dummy/config/initializers/assets.rb new file mode 100644 index 0000000..91073da --- /dev/null +++ b/test/dummy/config/initializers/assets.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +Rails.application.config.assets.precompile += %w( too_big_stylesheet.css too_big_stylesheet_split2.css test_stylesheet_with_media_queries.css test_stylesheet_with_media_queries_split2.css ) \ No newline at end of file diff --git a/test/dummy/config/secrets.yml b/test/dummy/config/secrets.yml new file mode 100644 index 0000000..5dc368e --- /dev/null +++ b/test/dummy/config/secrets.yml @@ -0,0 +1,4 @@ +test: + secret_key_base: c2a98602cc1537b2e38d4f279f20d24db66ff86fe523ab5197529f2ea907b6d70e2dca2fa3da50dcc9cad7bb1b861f0c45b6920fc576cc3e170d8c1ded61887f +development: + secret_key_base: c2a98602cc1537b2e38d4f279f20d24db66ff86fe523ab5197529f2ea907b6d70e2dca2fa3da50dcc9cad7bb1b861f0c45b6920fc576cc3e170d8c1ded61887f \ No newline at end of file diff --git a/test/unit/helpers/css_splitter/application_helper_test.rb b/test/unit/helpers/css_splitter/application_helper_test.rb index 1fedb0a..4dc8d39 100644 --- a/test/unit/helpers/css_splitter/application_helper_test.rb +++ b/test/unit/helpers/css_splitter/application_helper_test.rb @@ -5,13 +5,41 @@ class ApplicationHelperTest < ActionView::TestCase test "should work w/out options" do output = split_stylesheet_link_tag("too_big_stylesheet") - assert_equal "\n", output + assert_equal "\n", output end test "should work with options and multiple stylesheets" do output = split_stylesheet_link_tag("too_big_stylesheet", "foo", media: "print") - assert_equal "\n\n", output + assert_equal "\n\n\n", output end + test "should work with split_count option" do + output = split_stylesheet_link_tag("too_big_stylesheet", split_count: 3) + assert_equal "\n", output + end + + class RailsEnvDefault < ActionView::TestCase + setup do + Rails.env = 'development' + end + + teardown do + Rails.env = 'test' + end + + test "should default to false on splits" do + output = split_stylesheet_link_tag("too_big_stylesheet") + assert_equal "\n", output + end + + test "should respect the debug=true option" do + output = split_stylesheet_link_tag("too_big_stylesheet", debug: true) + assert_equal "\n", output + end + test "should respect the debug=false option" do + output = split_stylesheet_link_tag("too_big_stylesheet", debug: false) + assert_equal "\n", output + end + end end end diff --git a/test/unit/splitter_test.rb b/test/unit/splitter_test.rb index 42bb29e..f9713ea 100644 --- a/test/unit/splitter_test.rb +++ b/test/unit/splitter_test.rb @@ -2,12 +2,15 @@ class CssSplitterTest < ActiveSupport::TestCase test "#count_selectors" do - assert_equal 2938, CssSplitter::Splitter.count_selectors('test/unit/too_many_selectors.css') + assert_equal 2937, CssSplitter::Splitter.count_selectors('test/unit/too_many_selectors.css') end test "#count_selectors_of_rule" do assert_equal 1, CssSplitter::Splitter.count_selectors_of_rule('foo { color: baz; }') assert_equal 2, CssSplitter::Splitter.count_selectors_of_rule('foo, bar { color: baz; }') + + # split_string_into_rules splits the closing brace of a media query into its own rule + assert_equal 0, CssSplitter::Splitter.count_selectors_of_rule('}') end # --- split_string_into_rules --- @@ -32,6 +35,21 @@ class CssSplitterTest < ActiveSupport::TestCase assert_equal ["a{foo:url(//assets.server.com);}", "b{bar:url(//assets/server.com);}"], CssSplitter::Splitter.split_string_into_rules(simple) end + test '#split_string_into_rules containing media queries' do + has_media = "a{foo:bar;}@media print{b{baz:qux;}c{quux:corge;}}d{grault:garply;}" + assert_equal ["a{foo:bar;}", "@media print{b{baz:qux;}", "c{quux:corge;}", "}", "d{grault:garply;}"], CssSplitter::Splitter.split_string_into_rules(has_media) + end + + test '#split_string_into_rules containing media queries with leading whitespace' do + has_media = "a{foo:bar;}\n @media print{b{baz:qux;}c{quux:corge;}}d{grault:garply;}" + assert_equal ["a{foo:bar;}", "\n @media print{b{baz:qux;}", "c{quux:corge;}", "}", "d{grault:garply;}"], CssSplitter::Splitter.split_string_into_rules(has_media) + end + + test "#split_string_into_rules containing keyframes" do + has_keyframes = "a{foo:bar;}@keyframes rubes{from{baz:qux;}50%{quux:corge;}}d{grault:garply;}" + assert_equal ["a{foo:bar;}", "@keyframes rubes{from{baz:qux;}50%{quux:corge;}}", "d{grault:garply;}"], CssSplitter::Splitter.split_string_into_rules(has_keyframes) + end + # --- extract_charset --- test '#extract_charset with no charset' do @@ -44,12 +62,134 @@ class CssSplitterTest < ActiveSupport::TestCase assert_equal ['@charset "UTF-8";', ' .foo { color: black; }'], CssSplitter::Splitter.send(:extract_charset, first_rule) end + # --- extract_media --- + + test '#extract_media with no media block' do + first_rule = ".foo { color: black; }" + assert_equal nil, CssSplitter::Splitter.send(:extract_media, first_rule) + end + + test '#extract_media with media block' do + first_rule = <<< ".a#{n} { color: black; }" + end + css_rules << "@media only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 768px), only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 1280px), only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 800px) {" + css_rules << ".first-in-media-after-split { color: black; } }" + last_part = ".first-after-media { color: black; }" + + first_contents = css_rules.join("").gsub(/\s/, '') + last_contents = last_part.gsub(/\s/, '') + css_contents = "#{first_contents}#{last_contents}" + + assert_equal first_contents, CssSplitter::Splitter.split_string(css_contents, 1, max_selectors) + assert_equal last_contents, CssSplitter::Splitter.split_string(css_contents, 2, max_selectors) + end + + test '#split_string where the media part would overlap the split, no rules in media before the split' do + # This tests the following situation: + # Part 1: CssSplitter::Splitter::MAX_SELECTORS_DEFAULT + # Part 2: Opens with media block with 1 rule inside and one after + + # Change this line to any number, for example 4, if it fails to ease debugging + max_selectors = CssSplitter::Splitter::MAX_SELECTORS_DEFAULT + + css_rules = [] + max_selectors.times do |n| + css_rules << ".a#{n} { color: black; }" + end + media_rule = "@media only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 768px), only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 1280px), only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 800px) {" + last_part = "#{media_rule} .first-in-media-after-split { color: black; } } .first-after-media { color: black; }" + + first_contents = css_rules.join("").gsub(/\s/, '') + last_contents = last_part.gsub(/\s/, '') + css_contents = "#{first_contents}#{last_contents}" + + assert_equal first_contents, CssSplitter::Splitter.split_string(css_contents, 1, max_selectors) + assert_equal last_contents, CssSplitter::Splitter.split_string(css_contents, 2, max_selectors) + end + + test '#split_string where the media part would overlap the split, with rules in media before the split' do + # This tests the following situation: + # Part 1: CssSplitter::Splitter::MAX_SELECTORS_DEFAULT - 1 rules + # + Media block and first rule inside the media block + # Part 2: Opens with media block with last rule inside and one after + + # Change this line to any number, for example 4, if it fails to ease debugging + max_selectors = CssSplitter::Splitter::MAX_SELECTORS_DEFAULT + + css_rules = [] + (max_selectors - 1).times do |n| + css_rules << ".a#{n} { color: black; }" + end + css_rules << media_rule = "@media only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 768px), only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 1280px), only screen and (-webkit-min-device-pixel-ratio: 0) and (device-width: 800px) {" + css_rules << ".last-before-split { color: black; }" + + after_split = ".last-after-split { color: black; } } .first-after-media { color: black; }".gsub(/\s/, '') + + first_contents = css_rules.join("").gsub(/\s/, '') + css_contents = "#{first_contents}#{after_split}" + + # The last part should open with the media, followed by the rules defined in after_split + last_contents = "#{media_rule}#{after_split}".gsub(/\s/, '') + + # The first file should be closed neatly, as the media part opened before the last rule + # it should be closed as well. + assert_equal "#{first_contents}}", CssSplitter::Splitter.split_string(css_contents, 1, max_selectors) + + # The second part should open with the media definition, followed by one rule inside + # the media block and one rule after. + assert_equal last_contents, CssSplitter::Splitter.split_string(css_contents, 2, max_selectors) + end + + test '#split_string where the media part comes before the split' do + # This tests the following situation: + # Part 1: Media block with rule inside media block + # + CssSplitter::Splitter::MAX_SELECTORS_DEFAULT - 1 rules outside media block + # Part 2: Outputs the last rule outside media block + + # Change this line to any number, for example 4, if it fails to ease debugging + max_selectors = CssSplitter::Splitter::MAX_SELECTORS_DEFAULT + + css_rules = [] + css_rules << "@media print { .media-rule { color: black; } }" + + (max_selectors - 1).times do |n| + css_rules << ".a#{n} { color: black; }" + end + + first_contents = css_rules.join("").gsub(/\s/, '') + last_contents = ".first-after-split { color: black; }".gsub(/\s/, '') + + css_contents = "#{first_contents}#{last_contents}" + + assert_equal first_contents, CssSplitter::Splitter.split_string(css_contents, 1, max_selectors) + assert_equal last_contents, CssSplitter::Splitter.split_string(css_contents, 2, max_selectors) + end + # --- strip_comments --- test '#strip_comments: strip single line CSS coment' do