├── .gemtest ├── .gitignore ├── Gemfile ├── doc ├── created.rid ├── index.html ├── fr_class_index.html ├── files │ ├── lib │ │ ├── asset_hat │ │ │ ├── vcs_rb.html │ │ │ ├── tasks │ │ │ │ ├── css_rb.html │ │ │ │ └── js_rb.html │ │ │ ├── version_rb.html │ │ │ ├── js │ │ │ │ └── vendors_rb.html │ │ │ ├── capistrano_rb.html │ │ │ ├── initializers │ │ │ │ ├── action_view_rb.html │ │ │ │ └── cache_last_commit_ids_rb.html │ │ │ ├── js_rb.html │ │ │ ├── css_rb.html │ │ │ ├── tasks_rb.html │ │ │ ├── railtie_rb.html │ │ │ └── unicorn_rb.html │ │ ├── tasks │ │ │ └── asset_hat_rake.html │ │ ├── asset_hat_helper_rb.html │ │ └── asset_hat_rb.html │ ├── LICENSE.html │ └── HISTORY.html ├── fr_file_index.html ├── classes │ └── AssetHat │ │ ├── CSS │ │ └── Engines.html │ │ ├── JS │ │ ├── Engines.html │ │ └── Vendors.html │ │ ├── JS.html │ │ └── CSS.html └── rdoc-style.css ├── lib ├── tasks │ └── asset_hat.rake ├── asset_hat │ ├── initializers │ │ ├── action_view.rb │ │ └── cache_last_commit_ids.rb │ ├── version.rb │ ├── unicorn.rb │ ├── railtie.rb │ ├── capistrano.rb │ ├── tasks.rb │ ├── js.rb │ ├── vcs.rb │ ├── tasks │ │ ├── js.rb │ │ └── css.rb │ ├── css.rb │ └── js │ │ └── vendors.rb └── asset_hat.rb ├── public ├── javascripts │ ├── js-file-1-1.js │ ├── js-file-1-2.js │ ├── js-file-1-3.js │ ├── js-file-2-1.js │ ├── js-file-2-2.js │ ├── js-file-2-3.js │ └── bundles │ │ ├── js-bundle-1.min.js │ │ └── js-bundle-2.min.js └── stylesheets │ ├── css-file-1-1.css │ ├── css-file-1-2.css │ ├── css-file-1-3.css │ ├── css-file-2-1.css │ ├── css-file-2-2.css │ ├── css-file-2-3.css │ └── bundles │ ├── css-bundle-1.min.css │ ├── css-bundle-2.min.css │ └── ssl │ ├── css-bundle-1.min.css │ ├── css-bundle-2.min.css │ └── css-bundle-3.min.css ├── VERSION.yml ├── rails └── init.rb ├── test ├── test_helper.rb └── asset_hat_test.rb ├── LICENSE ├── Rakefile ├── config └── assets.yml ├── HISTORY ├── asset_hat.gemspec └── README.rdoc /.gemtest: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | pkg/ 3 | tmp/ 4 | Gemfile.lock -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /doc/created.rid: -------------------------------------------------------------------------------- 1 | Mon, 08 Aug 2011 14:30:15 -0400 2 | -------------------------------------------------------------------------------- /lib/tasks/asset_hat.rake: -------------------------------------------------------------------------------- 1 | require 'asset_hat/tasks' 2 | -------------------------------------------------------------------------------- /public/javascripts/js-file-1-1.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/javascripts/js-file-1-2.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/javascripts/js-file-1-3.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/javascripts/js-file-2-1.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/javascripts/js-file-2-2.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/javascripts/js-file-2-3.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/stylesheets/css-file-1-1.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/stylesheets/css-file-1-2.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/stylesheets/css-file-1-3.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/stylesheets/css-file-2-1.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/stylesheets/css-file-2-2.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /public/stylesheets/css-file-2-3.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | -------------------------------------------------------------------------------- /VERSION.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :major: 0 3 | :build: 4 | :minor: 4 5 | :patch: 2 6 | -------------------------------------------------------------------------------- /lib/asset_hat/initializers/action_view.rb: -------------------------------------------------------------------------------- 1 | ActionView::Base.send(:include, ::AssetHatHelper) 2 | -------------------------------------------------------------------------------- /lib/asset_hat/initializers/cache_last_commit_ids.rb: -------------------------------------------------------------------------------- 1 | AssetHat.cache_last_commit_ids unless defined?(::IRB) 2 | -------------------------------------------------------------------------------- /public/stylesheets/bundles/css-bundle-1.min.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | /* [placeholder] */ 3 | /* [placeholder] */ 4 | -------------------------------------------------------------------------------- /public/stylesheets/bundles/css-bundle-2.min.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | /* [placeholder] */ 3 | /* [placeholder] */ 4 | -------------------------------------------------------------------------------- /public/stylesheets/bundles/ssl/css-bundle-1.min.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | /* [placeholder] */ 3 | /* [placeholder] */ 4 | -------------------------------------------------------------------------------- /public/stylesheets/bundles/ssl/css-bundle-2.min.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | /* [placeholder] */ 3 | /* [placeholder] */ 4 | -------------------------------------------------------------------------------- /public/stylesheets/bundles/ssl/css-bundle-3.min.css: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | /* [placeholder] */ 3 | /* [placeholder] */ 4 | -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | require 'asset_hat/initializers/action_view' 2 | require 'asset_hat/initializers/cache_last_commit_ids' 3 | -------------------------------------------------------------------------------- /public/javascripts/bundles/js-bundle-1.min.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | 3 | /* [placeholder] */ 4 | 5 | /* [placeholder] */ 6 | 7 | -------------------------------------------------------------------------------- /public/javascripts/bundles/js-bundle-2.min.js: -------------------------------------------------------------------------------- 1 | /* [placeholder] */ 2 | 3 | /* [placeholder] */ 4 | 5 | /* [placeholder] */ 6 | 7 | -------------------------------------------------------------------------------- /lib/asset_hat/version.rb: -------------------------------------------------------------------------------- 1 | module AssetHat 2 | # Returns this gem's version number. See also VERSION. 3 | def self.version 4 | data_filepath = File.join(File.dirname(__FILE__), %w[.. .. VERSION.yml]) 5 | data = YAML.load(File.open(data_filepath, 'r')) 6 | [:major, :minor, :patch, :build]. 7 | map { |x| data[x] }.reject(&:blank?).join('.') 8 | end 9 | 10 | # This gem's version number. 11 | VERSION = self.version 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/asset_hat/unicorn.rb: -------------------------------------------------------------------------------- 1 | # Add the code below to your app's unicorn.conf.rb or similar so that assets' 2 | # commit IDs are precached only once by the Unicorn master, then propagated 3 | # out to workers. (Otherwise, each worker will precache commit IDs, which 4 | # wastes resources.) More on configuring Unicorn: 5 | # http://unicorn.bogomips.org/Unicorn/Configurator.html 6 | 7 | before_fork do |server, worker| 8 | AssetHat.cache_last_commit_ids if defined?(AssetHat) 9 | end 10 | -------------------------------------------------------------------------------- /lib/asset_hat/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'asset_hat' 2 | require 'asset_hat_helper' 3 | require 'rails' 4 | 5 | module AssetHat 6 | class Railtie < Rails::Railtie #:nodoc: 7 | initializer 'asset_hat.action_view' do |app| 8 | require 'asset_hat/initializers/action_view' 9 | end 10 | 11 | initializer 'asset_hat.cache_last_commit_ids' do |app| 12 | require 'asset_hat/initializers/cache_last_commit_ids' 13 | end 14 | 15 | rake_tasks do 16 | load 'tasks/asset_hat.rake' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/asset_hat/capistrano.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance(:must_exist).load do 2 | after 'deploy:update_code', 'deploy:asset_hat:minify' 3 | 4 | namespace :deploy do 5 | namespace :asset_hat do 6 | desc 'Minify all CSS/JS with AssetHat' 7 | task :minify, :roles => :assets, :except => {:no_release => true} do 8 | rake = fetch(:rake, "rake") 9 | env = fetch(:environment, fetch(:rails_env, "production")) 10 | run "cd #{release_path} ; " + 11 | "#{rake} RAILS_ENV=#{env} FORMAT=short asset_hat:minify" 12 | end 13 | end 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'active_support' 4 | require 'action_controller' 5 | require 'action_view/test_case' 6 | 7 | require 'shoulda' 8 | require 'flexmock/test_unit' 9 | require 'asset_hat' 10 | require 'asset_hat_helper' 11 | 12 | 13 | 14 | ActionController::Base.perform_caching = false 15 | 16 | unless defined?(Rails) 17 | module Rails 18 | class << self 19 | # Enable `Rails.env.test?`, `Rails.env.development?`, etc. 20 | def env; ActiveSupport::StringInquirer.new('test'); end 21 | end 22 | end 23 | end 24 | 25 | class ActionView::TestCase 26 | teardown :clear_html_cache 27 | 28 | def clear_html_cache 29 | AssetHat.clear_html_cache 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |41 | require ‘asset_hat/tasks‘ 42 |
43 |41 | Helpers for use in layouts for global includes, and in views for 42 | view-specific includes. 43 |
44 |41 | Add the code below to your app’s unicorn.conf.rb or similar so that 42 | assets’ commit IDs are precached only once by the Unicorn master, 43 | then propagated out to workers. (Otherwise, each worker will precache 44 | commit IDs, which wastes resources.) More on configuring Unicorn: unicorn.bogomips.org/Unicorn/Configurator.html 46 |
47 |:jsmin; see
26 | # Engines.jsmin.
27 | # Allowed values are in ENGINES.
28 | def self.minify(input_string, options={})
29 | options.reverse_merge!(:engine => :jsmin)
30 |
31 | engine = options[:engine].to_sym
32 | unless ENGINES.include?(engine)
33 | raise %{
34 | Unknown JS minification engine '#{engine}'.
35 | Allowed: #{ENGINES.map{ |e| "'#{e}'" }.join(', ')}
36 | }.strip.gsub(/\s+/, ' ') and return
37 | end
38 |
39 | AssetHat::JS::Engines.send(engine, input_string).strip
40 | end
41 |
42 | # Swappable JavaScript minification engines.
43 | module Engines
44 | # Barebones JavaScript minification engine that:
45 | # - Skips leading/trailing whitespace for each line, excluding line
46 | # breaks; and
47 | # - Removes one-line comments that had no actual code on that line.
48 | def self.weak(input_string)
49 | input = StringIO.new(input_string)
50 | output = StringIO.new
51 |
52 | input.each do |line|
53 | # Remove indentation and trailing whitespace
54 | line.strip!
55 | next if line.blank?
56 |
57 | # Skip single-line comments
58 | next if !(line =~ /^\/\//).nil?
59 | # TODO: Also skip single-line comments that began mid-line, but not
60 | # inside a string or regex
61 |
62 | # TODO: Skip multi-line comments
63 | # - Should not strip from within a string or regex
64 | # - Should not strip comments that begin with `/*!` (e.g., licenses)
65 |
66 | output.write(line + "\n")
67 | end
68 |
69 | output.rewind
70 | output.read
71 | end
72 |
73 | # JavaScript minification engine that simply uses the JSMin gem, a Ruby
74 | # port of Crockford's JSMin.
75 | #
76 | # Sources:
77 | # - http://github.com/rgrove/jsmin
78 | # - http://rubygems.org/gems/jsmin
79 | def self.jsmin(input_string)
80 | JSMin.minify(input_string + "\n")
81 | end
82 | end # module Engines
83 |
84 | end
85 |
86 | end
87 |
--------------------------------------------------------------------------------
/doc/files/LICENSE.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 41 | Copyright © 2011 Ron DeVera, Mint Digital 42 |
43 |44 | Permission is hereby granted, free of charge, to any person obtaining a 45 | copy of this software and associated documentation files (the 46 | “Software”), to deal in the Software without restriction, 47 | including without limitation the rights to use, copy, modify, merge, 48 | publish, distribute, sublicense, and/or sell copies of the Software, and to 49 | permit persons to whom the Software is furnished to do so, subject to the 50 | following conditions: 51 |
52 |53 | The above copyright notice and this permission notice shall be included in 54 | all copies or substantial portions of the Software. 55 |
56 |57 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 58 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 59 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 60 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 61 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 62 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 63 | USE OR OTHER DEALINGS IN THE SOFTWARE. 64 |
65 |:git.
18 | def self.last_commit_id(*args)
19 | # Process arguments
20 | options = args.extract_options!
21 | options = options.symbolize_keys.reverse_merge(:vcs => :git)
22 | filepaths = args.join(' ')
23 |
24 | # Validate options
25 | if options[:vcs] != :git
26 | raise 'Git is currently the only supported VCS.' and return
27 | end
28 |
29 | @last_commit_ids ||= {}
30 | if @last_commit_ids[filepaths].blank?
31 | h = `git log -1 --pretty=format:%h #{filepaths} 2>/dev/null`
32 | # `h` has either the abbreviated Git commit hash or an empty string
33 | @last_commit_ids[filepaths] = h if h.present?
34 | end
35 | @last_commit_ids[filepaths]
36 | end
37 |
38 | # Usage:
39 | #
40 | # AssetHat.last_bundle_commit_id('application', :css)
41 | #
42 | # Returns a string of the latest commit ID for the given bundle, based
43 | # on which of its files were most recently modified in the repository. If
44 | # no ID can be found, `nil` is returned.
45 | def self.last_bundle_commit_id(bundle, type)
46 | # Process arguments
47 | type = type.to_sym
48 | unless TYPES.include?(type)
49 | raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
50 | return
51 | end
52 |
53 | # Default to `{:css => {}, :js => {}}`
54 | @last_bundle_commit_ids ||=
55 | TYPES.inject({}) { |hsh, t| hsh.merge(t => {}) }
56 |
57 | if @last_bundle_commit_ids[type][bundle].blank?
58 | dir = self.assets_dir(type)
59 | filepaths = self.bundle_filepaths(bundle, type)
60 | if filepaths.present?
61 | @last_bundle_commit_ids[type][bundle] =
62 | self.last_commit_id(*filepaths)
63 | end
64 | end
65 |
66 | @last_bundle_commit_ids[type][bundle]
67 | end
68 |
69 | def self.last_commit_ids #:nodoc:
70 | @last_commit_ids
71 | end
72 |
73 | # Precomputes and caches the last commit ID for all bundles. Your web server
74 | # process(es) should run this at boot to avoid overhead during user runtime.
75 | def self.cache_last_commit_ids
76 | AssetHat::TYPES.each do |type|
77 | next if AssetHat.config[type.to_s].blank? ||
78 | AssetHat.config[type.to_s]['bundles'].blank?
79 |
80 | AssetHat.config[type.to_s]['bundles'].keys.each do |bundle|
81 | # Memoize commit ID for this bundle
82 | AssetHat.last_bundle_commit_id(bundle, type) if AssetHat.cache?
83 |
84 | # Memoize commit IDs for each file in this bundle
85 | AssetHat.bundle_filepaths(bundle, type).each do |filepath|
86 | AssetHat.last_commit_id(filepath)
87 | end
88 | end
89 | end
90 | end
91 |
92 | end
93 |
--------------------------------------------------------------------------------
/HISTORY:
--------------------------------------------------------------------------------
1 | = HISTORY
2 |
3 | == Version 0.4.2 (2011-08-08)
4 | * FIX: Changed Capistrano task to minify assets inside latest release path,
5 | not previous release path. (Thanks, sauliusg[https://github.com/sauliusg]!)
6 | * FIX: Fixed deprecation warnings when running Rake tasks. (Thanks,
7 | jsonperl[https://github.com/jsonperl]!)
8 |
9 | == Version 0.4.1 (2011-05-06)
10 | * FIX: Stopped `gem install asset_hat` from foolishly requiring itself.
11 |
12 | == Version 0.4.0 (2011-05-06)
13 | * FEATURE: Added Rails 3 support.
14 | * FEATURE: Added support for loading JavaScript files via LABjs:
15 | `<%= include_js :jquery, :bundles => %[plugins app], :loader => :lab_js %>`.
16 | (Provides only basic LABjs support. To build custom JS logic, use the new
17 | `:only_url` option.)
18 | * FEATURE: Added support for getting asset URLs, e.g.:
19 | `<%= include_css 'foo', :bundle => 'bar', :only_url => true %>`,
20 | `<%= include_js :jquery, 'foo', :bundle => 'bar', :only_url => true %>`.
21 |
22 | == Version 0.3.1 (2011-04-02)
23 | * IMPROVEMENT: Added tolerance for `.css` and `.js` extensions in assets.yml,
24 | even though you don't need them.
25 | * IMPROVEMENT: Added support for symbols as bundle names, e.g.,
26 | `include_js :bundle => :application`. Thanks,
27 | daphonz[https://github.com/daphonz]!
28 | * FIX: Worked around a JSMin bug that causes an error when JS ends with a
29 | comment and no line break.
30 |
31 | == Version 0.3.0 (2010-12-08)
32 | * FEATURE: Added ERb support in `config/assets.yml`.
33 | * FEATURE: Added support for auto-creating SSL versions of every stylesheet.
34 | * FEATURE: Added support for loading remote JS via SSL. Includes loading
35 | popular JS from `https://ajax.googleapis.com` or your own CDN.
36 | * FEATURE: Added support for `FORMAT=long|short|dot` flags for
37 | `rake asset_hat:minify`. Useful for shortening output from deployment
38 | scripts.
39 | * FEATURE: Added support for absolute paths in `config/assets.yml`. Allows
40 | pointing to assets directly inside `public/`.
41 | * FEATURE: Added `asset_hat:minify:css` and `asset_hat:minify:js` Rake task
42 | aliases.
43 | * IMPROVEMENT: Updated default CSS minification engine to remove rules that
44 | have empty declaration blocks.
45 | * IMPROVEMENT: Stopped pre-caching asset commit IDs when launching console.
46 | * IMPROVEMENT: Added support for single/double quotation marks in `url()` CSS
47 | when adding asset hosts and cache busters
48 | * FIX: Fixed adding asset commit IDs to URLs containing "?" in stylesheets.
49 |
50 | == Version 0.2.1 (2010-07-21)
51 | * FEATURE: Added WebFont Loader to supported JS vendors.
52 | * FIX: Stopped adding asset hosts to `url(/htc/...)` URLs because IE 6, by
53 | default, refuses to run .htc files from other domains, including CDN
54 | subdomains.
55 |
56 | == Version 0.2.0 (2010-06-10)
57 | * FEATURE: Added support for loading many more JS vendors from Google's CDN,
58 | including Prototype, MooTools, and SWFObject.
59 | * FEATURE: Cleaned up existing docs, and added complete RDoc documentation.
60 | * FEATURE: Added example integration script for Capistrano deployments.
61 |
62 | == Version 0.1.5 (2010-03-11)
63 | * FIX: Fixed fetching asset commit IDs in some environments. The bug was
64 | possibly caused by older versions of Git, which fail to read logs properly
65 | when given absolute paths, rather than relative paths.
66 | * FIX: Stopped app tests from running twice in some environments.
67 |
68 | == Version 0.1.4 (2010-03-03)
69 | * FIX: Fixed config filepaths in `asset_hat:config` task.
70 |
71 | == Version 0.1.3 (2010-03-03)
72 | * FIX: Allowed adding commit IDs and asset hosts to `url(/htc/...)` URLs
73 | (e.g., `/htc/iepngfix.htc`) in CSS, not just images.
74 | * FIX: Changed `AssetHat.config` to memoize only if `cache?` is true. In
75 | development environments, this means the config file will be reread with
76 | every request.
77 |
78 | == Version 0.1.2 (2010-01-27)
79 | * IMPROVEMENT: Memoized HTML output from `include_css` and `include_js` when
80 | `AssetHat.cache?` is true.
81 |
82 | == Version 0.1.1 (2010-01-20)
83 | * FIX: Rewrote `AssetHat::VERSION` to be based on `VERSION.yml`.
84 | * FIX: Prefixed `AssetHat::CONFIG_FILEPATH` with `RAILS_ROOT`, which fixes
85 | ability to run an app's individual test files.
86 |
87 | == Version 0.1.0 (2010-01-19)
88 | * Initial release.
89 |
--------------------------------------------------------------------------------
/lib/asset_hat/tasks/js.rb:
--------------------------------------------------------------------------------
1 | namespace :asset_hat do
2 | namespace :js do
3 |
4 | desc 'Minifies one JS file'
5 | task :minify_file, [:filepath] => :environment do |t, args|
6 | type = 'js'
7 |
8 | if args.filepath.blank?
9 | raise "Usage: rake asset_hat:#{type}:" +
10 | "minify_file[filepath.#{type}]" and return
11 | end
12 |
13 | verbose = (ENV['VERBOSE'] != 'false') # Defaults to `true`
14 | min_options = {
15 | :engine => AssetHat.config[type]['engine']
16 | }.reject { |k,v| v.blank? }
17 |
18 | if verbose && args.filepath.match(/\.min\.#{type}$/)
19 | raise "#{args.filepath} is already minified." and return
20 | end
21 |
22 | input = File.open(args.filepath, 'r').read
23 | output = AssetHat::JS.minify(input, min_options)
24 |
25 | # Write minified content to file
26 | target_filepath = AssetHat::JS.min_filepath(args.filepath)
27 | File.open(target_filepath, 'w') { |f| f.write output }
28 |
29 | # Print results
30 | puts "- Minified to #{target_filepath}" if verbose
31 | end
32 |
33 | desc 'Minifies one JS bundle'
34 | task :minify_bundle, [:bundle] => :environment do |t, args|
35 | type = 'js'
36 |
37 | if args.bundle.blank?
38 | raise "Usage: rake asset_hat:#{type}:" +
39 | "minify_bundle[application]" and return
40 | end
41 |
42 | config = AssetHat.config[type]
43 | report_format = ([ENV['FORMAT']] & %w[long short dot])[0] || 'long'
44 | $stdout.sync = true if report_format == 'dot' # Output immediately
45 | min_options = {
46 | :engine => config['engine']
47 | }.reject { |k,v| v.blank? }
48 |
49 | # Get bundle filenames
50 | filenames = config['bundles'][args.bundle].select(&:present?)
51 | if filenames.empty?
52 | raise "No #{type.upcase} files are specified for the " +
53 | "#{args.bundle} bundle in #{AssetHat::CONFIG_FILEPATH}." and return
54 | end
55 | filepaths = filenames.map do |filename|
56 | parts = filename.split(File::SEPARATOR)
57 | parts.last << '.' << type unless parts.last =~ /\.#{type}$/
58 | File.join(
59 | (parts.first.present? ?
60 | AssetHat.assets_dir(type) : # Given path was relative
61 | AssetHat::ASSETS_DIR), # Given path was absolute
62 | parts
63 | )
64 | end
65 | bundle_filepath = AssetHat::JS.min_filepath(File.join(
66 | AssetHat.bundles_dir(type), "#{args.bundle}.#{type}"))
67 |
68 | # Concatenate and process output
69 | output = ''
70 | old_bundle_size = 0.0
71 | new_bundle_size = 0.0
72 | filepaths.each do |filepath|
73 | file_output = File.open(filepath, 'r').read
74 | old_bundle_size += file_output.size
75 | unless filepath =~ /\.min\.#{type}$/ # Already minified
76 | file_output = AssetHat::JS.minify(file_output, min_options)
77 | end
78 | new_bundle_size += file_output.size
79 | output << file_output + "\n"
80 | end
81 | FileUtils.makedirs(File.dirname(bundle_filepath))
82 | File.open(bundle_filepath, 'w') { |f| f.write output }
83 |
84 | # Print results
85 | percent_saved =
86 | "#{'%.1f' % ((1 - (new_bundle_size / old_bundle_size)) * 100)}%"
87 | bundle_filepath.sub!(/^#{Rails.root}\//, '')
88 | case report_format
89 | when 'dot'
90 | print '.'
91 | when 'short'
92 | puts "Minified #{percent_saved.rjust(6)}: #{bundle_filepath}"
93 | else # 'long'
94 | puts "\n Wrote #{type.upcase} bundle: #{bundle_filepath}"
95 | filepaths.each do |filepath|
96 | puts " contains: #{filepath.sub(/^#{Rails.root}\//, '')}"
97 | end
98 | if old_bundle_size > 0
99 | puts " MINIFIED: #{percent_saved}" +
100 | (" (empty!)" if new_bundle_size == 0).to_s +
101 | " (Engine: #{min_options[:engine]})"
102 | end
103 | end
104 | end
105 |
106 | desc 'Concatenates and minifies all JS bundles'
107 | task :minify, [:opts] => :environment do |t, args|
108 | args.with_defaults(:opts => {})
109 | opts = args.opts.reverse_merge(:show_intro => true, :show_outro => true)
110 | type = 'js'
111 | report_format = ENV['FORMAT']
112 |
113 | if opts[:show_intro]
114 | print "Minifying #{type.upcase}..."
115 | if report_format == 'dot'
116 | $stdout.sync = true # Output immediately
117 | else
118 | puts
119 | end
120 | end
121 |
122 | # Get input bundles
123 | config = AssetHat.config[type]
124 | if config.blank? || config['bundles'].blank?
125 | raise "You need to set up #{type.upcase} bundles " +
126 | "in #{AssetHat::CONFIG_FILEPATH}." and return
127 | end
128 | bundles = config['bundles'].keys
129 |
130 | # Minify bundles
131 | bundles.each do |bundle|
132 | task = Rake::Task["asset_hat:#{type}:minify_bundle"]
133 | task.reenable
134 | task.invoke(bundle)
135 | end
136 |
137 | if opts[:show_outro]
138 | puts unless report_format == 'short'
139 | puts 'Done.'
140 | end
141 | end
142 |
143 | end # namespace :js
144 | end # namespace :asset_hat
145 |
--------------------------------------------------------------------------------
/asset_hat.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4 | # -*- encoding: utf-8 -*-
5 |
6 | Gem::Specification.new do |s|
7 | s.name = %q{asset_hat}
8 | s.version = "0.4.2"
9 |
10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11 | s.authors = ["Ron DeVera", "Mint Digital"]
12 | s.date = %q{2011-08-08}
13 | s.description = %q{Load CSS and JS faster. Minifies, bundles, and optimizes CSS/JS assets ahead of time (e.g., on deploy), not at runtime. Loads popular third-party JS (like jQuery, YUI, and Dojo) from localhost in development, and auto-switches to Google's CDN in production. Lets you switch on LABjs mode to load more scripts in parallel. Can rewrite stylesheets to use CDN hosts (not just your web server) and cache-busting hashes for updated images.}
14 | s.email = %q{hello@rondevera.com}
15 | s.extra_rdoc_files = [
16 | "LICENSE",
17 | "README.rdoc"
18 | ]
19 | s.files = [
20 | ".gemtest",
21 | "Gemfile",
22 | "HISTORY",
23 | "LICENSE",
24 | "README.rdoc",
25 | "Rakefile",
26 | "VERSION.yml",
27 | "asset_hat.gemspec",
28 | "config/assets.yml",
29 | "doc/classes/AssetHat.html",
30 | "doc/classes/AssetHat/CSS.html",
31 | "doc/classes/AssetHat/CSS/Engines.html",
32 | "doc/classes/AssetHat/JS.html",
33 | "doc/classes/AssetHat/JS/Engines.html",
34 | "doc/classes/AssetHat/JS/Vendors.html",
35 | "doc/classes/AssetHatHelper.html",
36 | "doc/created.rid",
37 | "doc/files/HISTORY.html",
38 | "doc/files/LICENSE.html",
39 | "doc/files/README_rdoc.html",
40 | "doc/files/lib/asset_hat/capistrano_rb.html",
41 | "doc/files/lib/asset_hat/css_rb.html",
42 | "doc/files/lib/asset_hat/initializers/action_view_rb.html",
43 | "doc/files/lib/asset_hat/initializers/cache_last_commit_ids_rb.html",
44 | "doc/files/lib/asset_hat/js/vendors_rb.html",
45 | "doc/files/lib/asset_hat/js_rb.html",
46 | "doc/files/lib/asset_hat/railtie_rb.html",
47 | "doc/files/lib/asset_hat/tasks/css_rb.html",
48 | "doc/files/lib/asset_hat/tasks/js_rb.html",
49 | "doc/files/lib/asset_hat/tasks_rb.html",
50 | "doc/files/lib/asset_hat/unicorn_rb.html",
51 | "doc/files/lib/asset_hat/vcs_rb.html",
52 | "doc/files/lib/asset_hat/version_rb.html",
53 | "doc/files/lib/asset_hat_helper_rb.html",
54 | "doc/files/lib/asset_hat_rb.html",
55 | "doc/files/lib/tasks/asset_hat_rake.html",
56 | "doc/fr_class_index.html",
57 | "doc/fr_file_index.html",
58 | "doc/fr_method_index.html",
59 | "doc/index.html",
60 | "doc/rdoc-style.css",
61 | "lib/asset_hat.rb",
62 | "lib/asset_hat/capistrano.rb",
63 | "lib/asset_hat/css.rb",
64 | "lib/asset_hat/initializers/action_view.rb",
65 | "lib/asset_hat/initializers/cache_last_commit_ids.rb",
66 | "lib/asset_hat/js.rb",
67 | "lib/asset_hat/js/vendors.rb",
68 | "lib/asset_hat/railtie.rb",
69 | "lib/asset_hat/tasks.rb",
70 | "lib/asset_hat/tasks/css.rb",
71 | "lib/asset_hat/tasks/js.rb",
72 | "lib/asset_hat/unicorn.rb",
73 | "lib/asset_hat/vcs.rb",
74 | "lib/asset_hat/version.rb",
75 | "lib/asset_hat_helper.rb",
76 | "lib/tasks/asset_hat.rake",
77 | "public/javascripts/bundles/js-bundle-1.min.js",
78 | "public/javascripts/bundles/js-bundle-2.min.js",
79 | "public/javascripts/js-file-1-1.js",
80 | "public/javascripts/js-file-1-2.js",
81 | "public/javascripts/js-file-1-3.js",
82 | "public/javascripts/js-file-2-1.js",
83 | "public/javascripts/js-file-2-2.js",
84 | "public/javascripts/js-file-2-3.js",
85 | "public/stylesheets/bundles/css-bundle-1.min.css",
86 | "public/stylesheets/bundles/css-bundle-2.min.css",
87 | "public/stylesheets/bundles/ssl/css-bundle-1.min.css",
88 | "public/stylesheets/bundles/ssl/css-bundle-2.min.css",
89 | "public/stylesheets/bundles/ssl/css-bundle-3.min.css",
90 | "public/stylesheets/css-file-1-1.css",
91 | "public/stylesheets/css-file-1-2.css",
92 | "public/stylesheets/css-file-1-3.css",
93 | "public/stylesheets/css-file-2-1.css",
94 | "public/stylesheets/css-file-2-2.css",
95 | "public/stylesheets/css-file-2-3.css",
96 | "rails/init.rb",
97 | "test/asset_hat_helper_test.rb",
98 | "test/asset_hat_test.rb",
99 | "test/test_helper.rb"
100 | ]
101 | s.homepage = %q{http://mintdigital.github.com/asset_hat}
102 | s.require_paths = ["lib"]
103 | s.rubygems_version = %q{1.4.2}
104 | s.summary = %q{Your assets are covered.}
105 |
106 | if s.respond_to? :specification_version then
107 | s.specification_version = 3
108 |
109 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
110 | s.add_development_dependency(%q:cssmin; see
21 | # Engines.cssmin.
22 | # Allowed values are in ENGINES.
23 | def self.minify(input_string, options={})
24 | options.reverse_merge!(:engine => :cssmin)
25 |
26 | engine = options[:engine].to_sym
27 | unless ENGINES.include?(engine)
28 | raise %{
29 | Unknown CSS minification engine '#{engine}'.
30 | Allowed: #{ENGINES.map{ |e| "'#{e}'" }.join(', ')}
31 | }.strip.gsub(/\s+/, ' ') and return
32 | end
33 |
34 | AssetHat::CSS::Engines.send(engine, input_string).strip
35 | end
36 |
37 | # Given a string containing CSS, appends each referenced asset's last
38 | # commit ID to its URL, e.g.,
39 | # background: url(/images/foo.png?ab12cd3). This enables
40 | # cache busting: If the user's browser has cached a copy of foo.png from a
41 | # previous deployment, this new URL forces the browser to ignore that
42 | # cache and request the latest version.
43 | def self.add_asset_commit_ids(css)
44 | update_css_urls(css, %w[images htc]) do |src, quote|
45 | # Get absolute path
46 | filepath = File.join(ASSETS_DIR, src)
47 |
48 | # Convert to relative path
49 | filepath.sub!(/^#{FileUtils.pwd}#{File::SEPARATOR}/, '')
50 |
51 | commit_id = AssetHat.last_commit_id(filepath)
52 | if commit_id.present?
53 | "url(#{quote}#{src}#{src =~ /\?/ ? '&' : '?'}#{commit_id}#{quote})"
54 | else
55 | "url(#{quote}#{src}#{quote})"
56 | end
57 | end
58 | end
59 |
60 | # Arguments:
61 | #
62 | # - A string containing CSS;
63 | # - A string containing the app's asset host, e.g.,
64 | # 'http\://cdn%d.example.com'. This value is typically taken from
65 | # config.action_controller.asset_host in
66 | # the app's config/environments/production.rb.
67 | #
68 | # An asset host is added to every image URL in the CSS, e.g.,
69 | # background: url(http\://cdn2.example.com/images/foo.png);
70 | # if %d in the asset host, it is replaced with an arbitrary
71 | # number in 0-3, inclusive.
72 | #
73 | # Options:
74 | #
75 | # [ssl] Set to true to simulate a request via SSL. Defaults
76 | # to false.
77 | def self.add_asset_hosts(css, asset_host, options={})
78 | return css if asset_host.blank?
79 |
80 | options.reverse_merge!(:ssl => false)
81 |
82 | update_css_urls(css, %w[images]) do |src, quote|
83 | computed_asset_host = AssetHat.compute_asset_host(
84 | asset_host, src, options.slice(:ssl))
85 | "url(#{quote}#{computed_asset_host}#{src}#{quote})"
86 | end
87 | end
88 |
89 | # Swappable CSS minification engines. Each accepts and returns a string.
90 | module Engines
91 | # Barebones CSS minification engine that only strips whitespace from the
92 | # start and end of every line, including linebreaks. For safety, doesn't
93 | # attempt to parse the CSS itself.
94 | def self.weak(input_string)
95 | input = StringIO.new(input_string)
96 | output = StringIO.new
97 |
98 | input.each do |line|
99 | # Remove indentation and trailing whitespace (including line breaks)
100 | line.strip!
101 | next if line.blank?
102 |
103 | output.write line
104 | end
105 |
106 | output.rewind
107 | output.read
108 | end
109 |
110 | # CSS minification engine that mostly uses the CSSMin gem, a Ruby port
111 | # of Lecomte's YUI Compressor and Schlueter's PHP cssmin.
112 | #
113 | # Sources:
114 | # - http://github.com/rgrove/cssmin
115 | # - http://rubygems.org/gems/cssmin
116 | def self.cssmin(input_string)
117 | output = CSSMin.minify(input_string)
118 |
119 | # Remove rules that have empty declaration blocks
120 | output.gsub!(/\}([^\}]+\{;\}){1,}/, '}')
121 |
122 | output
123 | end
124 | end
125 |
126 |
127 |
128 | private
129 |
130 | # Strips any balanced quotation marks from `src`. Returns `src` and an
131 | # instance of the quotation mark as two separate strings.
132 | def self.separate_src_and_quotes(src)
133 | quote = src[0, 1]
134 |
135 | if %w[' "].include?(quote) && quote == src[-1, 1]
136 | src = src[1, src.length - 2] # Strip quotes
137 | else
138 | quote = nil # No quotes in original CSS
139 | end
140 |
141 | [src, quote]
142 | end
143 |
144 | def self.update_css_urls(css, dirs, &css_block)
145 | new_css = css.dup
146 | dirs = dirs.join('|')
147 |
148 | gsub_block = lambda do |match|
149 | src, quote = separate_src_and_quotes($1)
150 | css_block.call(src, quote)
151 | end
152 |
153 | # Match without quotes
154 | new_css.gsub!(/url[\s]*\((\/(#{dirs})\/[^)'"]+)\)/, &gsub_block)
155 |
156 | # Match with single/double quotes
157 | new_css.gsub!(/url[\s]*\(((['"])\/(#{dirs})\/[^)]+\2)\)/, &gsub_block)
158 |
159 | new_css
160 | end
161 |
162 | end
163 |
164 | end
165 |
--------------------------------------------------------------------------------
/lib/asset_hat/tasks/css.rb:
--------------------------------------------------------------------------------
1 | namespace :asset_hat do
2 | namespace :css do
3 |
4 | desc 'Adds commit IDs to asset URLs in CSS for cache busting'
5 | task :add_asset_commit_ids, [:filename] => :environment do |t, args|
6 | if args.filename.blank?
7 | raise 'Usage: rake asset_hat:css:' +
8 | 'add_asset_commit_ids[filename.css]' and return
9 | end
10 |
11 | verbose = (ENV['VERBOSE'] != 'false') # Defaults to `true`
12 |
13 | css = File.open(args.filename, 'r') { |f| f.read }
14 | css = AssetHat::CSS.add_asset_commit_ids(css)
15 | File.open(args.filename, 'w') { |f| f.write css }
16 |
17 | puts "- Added asset commit IDs to #{args.filename}" if verbose
18 | end
19 |
20 | desc 'Adds hosts to asset URLs in CSS'
21 | task :add_asset_hosts, [:filename] => :environment do |t, args|
22 | if args.filename.blank?
23 | raise 'Usage: rake asset_hat:css:' +
24 | 'add_asset_hosts[filename.css]' and return
25 | end
26 |
27 | verbose = (ENV['VERBOSE'] != 'false') # Defaults to `true`
28 |
29 | asset_host = ActionController::Base.asset_host
30 | if asset_host.blank?
31 | raise "This environment (#{ENV['RAILS_ENV']}) " +
32 | "doesn't have an `asset_host` configured." and return
33 | end
34 |
35 | css = File.open(args.filename, 'r') { |f| f.read }
36 | css = AssetHat::CSS.add_asset_hosts(css, asset_host)
37 | File.open(args.filename, 'w') { |f| f.write css }
38 |
39 | puts "- Added asset hosts to #{args.filename}" if verbose
40 | end
41 |
42 | desc 'Minifies one CSS file'
43 | task :minify_file, [:filepath] => :environment do |t, args|
44 | type = 'css'
45 |
46 | if args.filepath.blank?
47 | raise "Usage: rake asset_hat:#{type}:" +
48 | "minify_file[path/to/filename.#{type}]" and return
49 | end
50 |
51 | verbose = (ENV['VERBOSE'] != 'false') # Defaults to `true`
52 | min_options = {
53 | :engine => AssetHat.config[type]['engine']
54 | }.reject { |k,v| v.blank? }
55 |
56 | input = File.open(args.filepath, 'r').read
57 | output = AssetHat::CSS.minify(input, min_options)
58 |
59 | # Write minified content to file
60 | target_filepath = AssetHat::CSS.min_filepath(args.filepath)
61 | File.open(target_filepath, 'w') { |f| f.write output }
62 |
63 | # Print results
64 | puts "- Minified to #{target_filepath}" if verbose
65 | end
66 |
67 | desc 'Minifies one CSS bundle'
68 | task :minify_bundle, [:bundle] => :environment do |t, args|
69 | type = 'css'
70 |
71 | if args.bundle.blank?
72 | raise "Usage: rake asset_hat:#{type}:" +
73 | "minify_bundle[application]" and return
74 | end
75 |
76 | config = AssetHat.config[type]
77 | report_format = ([ENV['FORMAT']] & %w[long short dot])[0] || 'long'
78 | $stdout.sync = (report_format == 'dot')
79 | min_options = {
80 | :engine => config['engine']
81 | }.reject { |k,v| v.blank? }
82 |
83 | # Get bundle filenames
84 | filenames = config['bundles'][args.bundle].select(&:present?)
85 | if filenames.empty?
86 | raise "No #{type.upcase} files are specified for the " +
87 | "#{args.bundle} bundle in #{AssetHat::CONFIG_FILEPATH}." and return
88 | end
89 | filepaths = filenames.map do |filename|
90 | parts = filename.split(File::SEPARATOR)
91 | parts.last << '.' << type unless parts.last =~ /\.#{type}$/
92 | File.join(
93 | (parts.first.present? ?
94 | AssetHat.assets_dir(type) : # Given path was relative
95 | AssetHat::ASSETS_DIR), # Given path was absolute
96 | parts
97 | )
98 | end
99 |
100 | # Check whether app has special SSL asset host
101 | asset_host = ActionController::Base.asset_host
102 | output_options_array = [{:ssl => false}]
103 | if AssetHat.ssl_asset_host_differs?
104 | # The bundle needs a second version, which uses the asset host via SSL
105 | output_options_array << {:ssl => true}
106 | end
107 |
108 | output_options_array.each do |output_options|
109 |
110 | # Concatenate and process output
111 | bundle_filepath = AssetHat::CSS.min_filepath(File.join(
112 | AssetHat.bundles_dir(type, output_options.slice(:ssl)),
113 | "#{args.bundle}.#{type}"))
114 | old_bundle_size = 0.0
115 | new_bundle_size = 0.0
116 | output = ''
117 | filepaths.each do |filepath|
118 | file_output = File.open(filepath, 'r').read
119 | old_bundle_size += file_output.size
120 |
121 | file_output = AssetHat::CSS.minify(file_output, min_options)
122 | file_output = AssetHat::CSS.add_asset_commit_ids(file_output)
123 | if asset_host.present?
124 | file_output = AssetHat::CSS.add_asset_hosts(
125 | file_output, asset_host, output_options.slice(:ssl))
126 | end
127 |
128 | new_bundle_size += file_output.size
129 | output << file_output + "\n"
130 | end
131 | FileUtils.makedirs(File.dirname(bundle_filepath))
132 | File.open(bundle_filepath, 'w') { |f| f.write output }
133 |
134 | # Print results
135 | percent_saved =
136 | "#{'%.1f' % ((1 - (new_bundle_size / old_bundle_size)) * 100)}%"
137 | bundle_filepath.sub!(/^#{Rails.root}\//, '')
138 | case report_format
139 | when 'dot'
140 | print '.'
141 | when 'short'
142 | puts "Minified #{percent_saved.rjust(6)}: #{bundle_filepath}"
143 | else # 'long'
144 | puts "\nWrote #{type.upcase} bundle: #{bundle_filepath}"
145 | filepaths.each do |filepath|
146 | puts " contains: #{filepath.sub(/^#{Rails.root}\//, '')}"
147 | end
148 | if old_bundle_size > 0
149 | puts " MINIFIED: #{percent_saved}" +
150 | (" (empty!)" if new_bundle_size == 0).to_s +
151 | " (Engine: #{min_options[:engine]})"
152 | end
153 | end
154 | end
155 | end
156 |
157 | desc 'Concatenates and minifies all CSS bundles'
158 | task :minify, [:opts] => :environment do |t, args|
159 | args.with_defaults(:opts => {})
160 | opts = args.opts.reverse_merge(:show_intro => true, :show_outro => true)
161 | type = 'css'
162 | report_format = ENV['FORMAT']
163 |
164 | if opts[:show_intro]
165 | print "Minifying #{type.upcase}..."
166 | if report_format == 'dot'
167 | $stdout.sync = true # Output immediately
168 | else
169 | puts
170 | end
171 | end
172 |
173 | # Get input bundles
174 | config = AssetHat.config[type]
175 | if config.blank? || config['bundles'].blank?
176 | raise "You need to set up #{type.upcase} bundles " +
177 | "in #{AssetHat::CONFIG_FILEPATH}." and return
178 | end
179 | bundles = config['bundles'].keys
180 |
181 | # Minify bundles
182 | bundles.each do |bundle|
183 | task = Rake::Task["asset_hat:#{type}:minify_bundle"]
184 | task.reenable
185 | task.invoke(bundle)
186 | end
187 |
188 | if opts[:show_outro]
189 | puts unless report_format == 'short'
190 | puts 'Done.'
191 | end
192 | end
193 |
194 | end # namespace :css
195 | end # namespace :asset_hat
196 |
--------------------------------------------------------------------------------
/doc/classes/AssetHat/CSS/Engines.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 42 | Swappable CSS minification engines. Each accepts 43 | and returns a string. 44 |
45 |65 | CSS minification engine that mostly uses the 66 | CSSMin gem, a Ruby port of Lecomte’s YUI Compressor and 67 | Schlueter’s PHP cssmin. 68 |
69 |70 | Sources: 71 |
72 |# File lib/asset_hat/css.rb, line 116 116: def self.cssmin(input_string) 117: output = CSSMin.minify(input_string) 118: 119: # Remove rules that have empty declaration blocks 120: output.gsub!(/\}([^\}]+\{;\}){1,}/, '}') 121: 122: output 123: end86 |
96 | Barebones CSS minification engine that only 97 | strips whitespace from the start and end of every line, including 98 | linebreaks. For safety, doesn’t attempt to parse the CSS itself. 100 |
101 |# File lib/asset_hat/css.rb, line 94 94: def self.weak(input_string) 95: input = StringIO.new(input_string) 96: output = StringIO.new 97: 98: input.each do |line| 99: # Remove indentation and trailing whitespace (including line breaks) 100: line.strip! 101: next if line.blank? 102: 103: output.write line 104: end 105: 106: output.rewind 107: output.read 108: end107 |
42 | Swappable JavaScript minification engines. 43 |
44 |64 | JavaScript minification engine that simply uses the JSMin gem, a Ruby port 65 | of Crockford’s JSMin. 66 |
67 |68 | Sources: 69 |
70 |# File lib/asset_hat/js.rb, line 79 79: def self.jsmin(input_string) 80: JSMin.minify(input_string + "\n") 81: end84 |
94 | Barebones JavaScript minification engine that: 95 |
96 |# File lib/asset_hat/js.rb, line 48 48: def self.weak(input_string) 49: input = StringIO.new(input_string) 50: output = StringIO.new 51: 52: input.each do |line| 53: # Remove indentation and trailing whitespace 54: line.strip! 55: next if line.blank? 56: 57: # Skip single-line comments 58: next if !(line =~ /^\/\//).nil? 59: # TODO: Also skip single-line comments that began mid-line, but not 60: # inside a string or regex 61: 62: # TODO: Skip multi-line comments 63: # - Should not strip from within a string or regex 64: # - Should not strip comments that begin with `/*!` (e.g., licenses) 65: 66: output.write(line + "\n") 67: end 68: 69: output.rewind 70: output.read 71: end110 |
48 | Methods for minifying JavaScript. 49 |
50 || ENGINES | 71 |= | 72 |[:weak, :jsmin] | 73 |74 | | 75 | 76 | A list of supported minification engine names. 77 | | 78 |
| VENDORS | 81 |= | 82 |Vendors::VENDORS | 83 |84 | | 85 | 86 | A list of supported 3rd-party JavaScript 87 | plugin/vendor names. 88 | | 89 |
103 | Returns the expected path for the minified version of a JS asset: 105 |
106 |AssetHat::JS.min_filepath('public/javascripts/bundles/application.js')
# => 'public/javascripts/bundles/application.min.js'
107 | # File lib/asset_hat/js.rb, line 19 19: def self.min_filepath(filepath) 20: AssetHat.min_filepath(filepath, 'js') 21: end113 |
123 | Accepts a string of JS, and returns that JS minified. Options: 125 |
126 |# File lib/asset_hat/js.rb, line 28 28: def self.minify(input_string, options={}) 29: options.reverse_merge!(:engine => :jsmin) 30: 31: engine = options[:engine].to_sym 32: unless ENGINES.include?(engine) 33: raise %{ 34: Unknown JS minification engine '#{engine}'. 35: Allowed: #{ENGINES.map{ |e| "'#{e}'" }.join(', ')} 36: }.strip.gsub(/\s+/, ' ') and return 37: end 38: 39: AssetHat::JS::Engines.send(engine, input_string).strip 40: end139 |
config/assets.yml; use this option to override
66 | # the configuration.
67 | def self.source_for(vendor, options={})
68 | vendor_config =
69 | AssetHat.config['js']['vendors'][vendor.to_s] rescue nil
70 | use_local = AssetHat.consider_all_requests_local?
71 | use_ssl = !!options[:ssl]
72 | version = options[:version] || vendor_config['version'] rescue nil
73 |
74 | # Prepare local path and default remote URL
75 | srcs = Vendors.vendor_uris(vendor,
76 | :use_ssl => use_ssl, :version => version)
77 | local_src, remote_src = srcs[:local], srcs[:remote]
78 |
79 | # Using the local URL requires that the vendor file exists locally. If
80 | # the vendor file doesn't exist, use the remote URL as fallback.
81 | use_local &&= AssetHat.asset_exists?(local_src, :js)
82 |
83 | # If no version given, can't determine the remote URL; use the local
84 | # URL as fallback.
85 | use_local ||= version.blank?
86 |
87 | if use_local
88 | src = local_src
89 | else
90 | # To ease setup, if no local copy of the vendor code is found,
91 | # use a remote URL as a fallback.
92 |
93 | # Give precedence to configured remote URLs
94 | src = vendor_config.try(:[], 'remote_ssl_url') if use_ssl
95 | src ||= vendor_config.try(:[], 'remote_url')
96 |
97 | # Use default remote URL as fallback
98 | src ||= remote_src
99 |
100 | # Use local URL as final resort, even though the file doesn't
101 | # exist, in hopes that the app maintainer finds the 404 (or the
102 | # warning below) in the logs. This needs to be fixed in the app,
103 | # rather than relying on a CDN to dynamically provide the latest
104 | # stable vendor version.
105 | if src.blank?
106 | src = local_src
107 | Rails.logger.warn "\n\nAssetHat WARNING (#{Time.now}):\n" + %{
108 | Tried to reference the vendor JS `:#{vendor}`, but
109 | #{AssetHat.assets_dir(:js)}/#{local_src} couldn't be found, and
110 | couldn't use a remote fallback because no vendor version was
111 | given in #{AssetHat::RELATIVE_CONFIG_FILEPATH}.
112 | }.squish!
113 | # TODO: Create `AssetHat::Logger.warn`, etc. methods
114 | end
115 | end
116 |
117 | src
118 | end
119 |
120 |
121 |
122 | private
123 |
124 | def self.vendor_uris(vendor, options={})
125 | # Returns a hash with keys `:local` and `:remote`.
126 |
127 | uris = {}
128 | use_ssl = options[:use_ssl]
129 | version = options[:version]
130 |
131 | case vendor
132 | when :jquery
133 | uris[:local ] = "#{['jquery', version].compact.join('-')}.min.js"
134 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/jquery/#{version}/jquery.min.js"
135 | when :jquery_ui
136 | uris[:local ] = "#{['jquery-ui', version].compact.join('-')}.min.js"
137 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/jqueryui/#{version}/jquery-ui.min.js"
138 | when :prototype
139 | # Prototype currently doesn't provide an official minified version.
140 | uris[:local ] = "#{['prototype', version].compact.join('-')}.js"
141 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/prototype/#{version}/prototype.js"
142 | when :scriptaculous
143 | # script.aculo.us currently doesn't provide an official minified version.
144 | uris[:local ] = "#{['scriptaculous', version].compact.join('-')}.js"
145 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/scriptaculous/#{version}/scriptaculous.js"
146 | when :mootools
147 | uris[:local ] = "#{['mootools', version].compact.join('-')}.min.js"
148 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/mootools/#{version}/mootools-yui-compressed.js"
149 | when :dojo
150 | uris[:local ] = "#{['dojo', version].compact.join('-')}.min.js"
151 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/dojo/#{version}/dojo/dojo.xd.js"
152 | when :swfobject
153 | uris[:local ] = "#{['swfobject', version].compact.join('-')}.min.js"
154 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/swfobject/#{version}/swfobject.js"
155 | when :yui
156 | uris[:local ] = "#{['yui', version].compact.join('-')}.min.js"
157 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/yui/#{version}/build/yuiloader/yuiloader-min.js"
158 | when :ext_core
159 | uris[:local ] = "#{['ext-core', version].compact.join('-')}.min.js"
160 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/ext-core/#{version}/ext-core.js"
161 | when :webfont
162 | uris[:local ] = "#{['webfont', version].compact.join('-')}.min.js"
163 | uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/webfont/#{version}/webfont.js"
164 | when :lab_js
165 | uris[:local ] = "#{['LAB', version].compact.join('-')}.min.js"
166 |
167 | remote_host =
168 | if use_ssl
169 | 'https://d3eee1nukb5wg.cloudfront.net/'
170 | # This must match the value in the cdnjs repo:
171 | # https://github.com/cdnjs/cdnjs/raw/master/https_location
172 | #
173 | # Amazon CloudFront doesn't support SSL, as discussed here:
174 | # - http://www.cdnjs.com/#IDComment130405257
175 | # - https://forums.aws.amazon.com/message.jspa?messageID=141951
176 | # As a result, the SSL certificate at [:css, :js].
16 | TYPES = [:css, :js]
17 |
18 | # Base directory in which all assets are kept, e.g., 'public/'.
19 | ASSETS_DIR = defined?(Rails.public_path) && Rails.public_path.present? ?
20 | Rails.public_path : 'public'
21 |
22 | # Root URL path for all stylesheets. For public-facing use.
23 | STYLESHEETS_PATH = '/stylesheets'
24 |
25 | # Root URL path for all JavaScripts. For public-facing use.
26 | JAVASCRIPTS_PATH = '/javascripts'
27 |
28 | # Directory in which all stylesheets are kept, e.g., 'public/stylesheets'.
29 | # For internal filesystem use.
30 | STYLESHEETS_DIR = File.join(ASSETS_DIR, 'stylesheets')
31 |
32 | # Directory in which all JavaScripts are kept, e.g., 'public/javascripts'.
33 | # For internal filesystem use.
34 | JAVASCRIPTS_DIR = File.join(ASSETS_DIR, 'javascripts')
35 |
36 | # Relative path for the config file.
37 | RELATIVE_CONFIG_FILEPATH = File.join('config', 'assets.yml')
38 |
39 | # Absolute path for the config file.
40 | CONFIG_FILEPATH = File.join(RAILS_ROOT, RELATIVE_CONFIG_FILEPATH)
41 |
42 | class << self
43 | attr_accessor :config, :asset_exists, :html_cache #:nodoc:
44 | end
45 |
46 | # Nested-hash version of config/assets.yml.
47 | def self.config
48 | unless File.exists?(CONFIG_FILEPATH)
49 | raise '`config/assets.yml` is missing! ' +
50 | 'Run `rake asset_hat:config` to generate it.' and return
51 | end
52 |
53 | if !cache? || @config.blank?
54 | @config = YAML.load(ERB.new(File.read(CONFIG_FILEPATH)).result)
55 | end
56 | @config
57 | end
58 |
59 | # Returns the relative path to the directory where the original CSS or JS
60 | # files are stored. For internal filesystem use.
61 | #
62 | # type argument: :css or :js
63 | def self.assets_dir(type)
64 | case type.to_sym
65 | when :css ; STYLESHEETS_DIR
66 | when :js ; JAVASCRIPTS_DIR
67 | else
68 | raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
69 | nil
70 | end
71 | end
72 |
73 | # Returns the root URL path where the original CSS or JS files are stored.
74 | # For external URL-building use.
75 | #
76 | # type argument: :css or :js
77 | def self.assets_path(type)
78 | case type.to_sym
79 | when :css ; STYLESHEETS_PATH
80 | when :js ; JAVASCRIPTS_PATH
81 | else
82 | raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
83 | nil
84 | end
85 | end
86 |
87 | # Returns the relative path to the directory where CSS or JS bundles are
88 | # stored. For internal filesystem use.
89 | #
90 | # Usage:
91 | #
92 | # AssetHat.bundles_dir
93 | # # => 'bundles'
94 | # AssetHat.bundles_dir(:ssl => true)
95 | # # => 'bundles/ssl'
96 | # AssetHat.bundles_dir(:css)
97 | # # => 'public/stylesheets/bundles'
98 | # AssetHat.bundles_dir(:js, :ssl => true)
99 | # # => 'public/javascripts/bundles/ssl
100 | #
101 | # Options:
102 | #
103 | # [ssl] Set this to true if the stylesheet references images
104 | # via SSL. Defaults to false.
105 | def self.bundles_dir(*args)
106 | options = args.extract_options!
107 | options.symbolize_keys!.reverse_merge!(:ssl => false)
108 | type = args.first
109 |
110 | dir = type.present? ? File.join(assets_dir(type), 'bundles') : 'bundles'
111 | dir = File.join(dir, 'ssl') if options[:ssl]
112 | dir
113 | end
114 |
115 | # Returns the root URL path where CSS or JS bundles are stored. For external
116 | # URL-building use.
117 | #
118 | # Usage:
119 | #
120 | # AssetHat.bundles_path(:css)
121 | # # => 'public/stylesheets/bundles'
122 | # AssetHat.bundles_path(:js, :ssl => true)
123 | # # => 'public/javascripts/bundles/ssl
124 | #
125 | # Options:
126 | #
127 | # [ssl] Set this to true if the stylesheet references images
128 | # via SSL. Defaults to false.
129 | def self.bundles_path(type, options={})
130 | type = type.to_sym
131 | unless TYPES.include?(type)
132 | raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
133 | return
134 | end
135 |
136 | path = case type
137 | when :css ; STYLESHEETS_PATH
138 | when :js ; JAVASCRIPTS_PATH
139 | else nil
140 | end
141 | path += '/bundles'
142 | path += '/ssl' if options[:ssl]
143 | path
144 | end
145 |
146 | # Returns true if the specified asset exists in the file system:
147 | #
148 | # AssetHat.asset_exists?('application.css', :css)
149 | # # => true if public/stylesheets/application.css exists
150 | # AssetHat.asset_exists?('some-plugin.js', :js)
151 | # # => true if public/javascripts/some-plugin.js exists
152 | #
153 | # See also AssetHat.assets_dir.
154 | def self.asset_exists?(filename, type)
155 | # Process arguments
156 | type = type.to_sym
157 | unless TYPES.include?(type)
158 | raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
159 | return
160 | end
161 |
162 | # Default to `{:css => {}, :js => {}}`
163 | @asset_exists ||= TYPES.inject({}) { |hsh, t| hsh.merge(t => {}) }
164 |
165 | if @asset_exists[type][filename].nil?
166 | @asset_exists[type][filename] =
167 | File.exist?(File.join(self.assets_dir(type), filename))
168 | end
169 | @asset_exists[type][filename]
170 | end
171 |
172 | # Returns true if bundles should be included as single minified
173 | # files (e.g., in production), or false if bundles should be
174 | # included as separate, unminified files (e.g., in development). To modify
175 | # this value, set
176 | # config.action_controller.perform_caching (boolean)
177 | # in your environment.
178 | def self.cache? ; ActionController::Base.perform_caching ; end
179 |
180 | # Returns the value of
181 | # Rails.application.config.consider_all_requests_local or its
182 | # equivalent in older versions of Rails. To modify this value, set
183 | # config.consider_all_requests_local (boolean) in your
184 | # environment.
185 | def self.consider_all_requests_local?
186 | if defined?(Rails) && Rails.respond_to?(:application)
187 | Rails.application.config.consider_all_requests_local
188 | else # Rails 2.x
189 | ActionController::Base.consider_all_requests_local
190 | end
191 | end
192 |
193 | # Returns the expected path for the minified version of an asset:
194 | #
195 | # AssetHat.min_filepath('public/stylesheets/bundles/application.css', 'css')
196 | # # => 'public/stylesheets/bundles/application.min.css'
197 | #
198 | # See also AssetHat::CSS.min_filepath and
199 | # AssetHat::JS.min_filepath.
200 | def self.min_filepath(filepath, extension)
201 | filepath.sub(/([^\.]*).#{extension}$/, "\\1.min.#{extension}")
202 | end
203 |
204 | # Returns the extension-less names of files in the given bundle:
205 | #
206 | # AssetHat.bundle_filenames('application', :css)
207 | # # => ['reset', 'application']
208 | # AssetHat.bundle_filenames('non-existent-file', :css)
209 | # # => nil
210 | def self.bundle_filenames(bundle, type)
211 | # Process arguments
212 | unless TYPES.include?(type.to_sym)
213 | raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
214 | return
215 | end
216 |
217 | self.config[type.to_s]['bundles'][bundle.to_s] rescue nil
218 | end
219 |
220 | # Returns the full paths of files in the given bundle:
221 | #
222 | # AssetHat.bundle_filenames('application', :css)
223 | # # => ['/path/to/app/public/stylesheets/reset.css',
224 | # '/path/to/app/public/stylesheets/application.css']
225 | # AssetHat.bundle_filenames('non-existent-file', :css)
226 | # # => nil
227 | def self.bundle_filepaths(bundle, type)
228 | # Process arguments
229 | unless TYPES.include?(type.to_sym)
230 | raise %{Unknown type "#{type}"; should be one of: #{TYPES.join(', ')}.}
231 | return
232 | end
233 |
234 | dir = self.assets_dir(type)
235 | filenames = self.bundle_filenames(bundle, type)
236 | filepaths = filenames.present? ?
237 | filenames.map { |fn| File.join(dir, "#{fn}.#{type}") } : nil
238 | end
239 |
240 | # Reads ActionController::Base.asset_host, which can be a
241 | # String or Proc, and returns a String. Should behave just like Rails
242 | # 2.3.x's private `compute_asset_host` method, but with a simulated request.
243 | #
244 | # Example environment config for CDN support via SSL:
245 | #
246 | # # In config/environments/production.rb:
247 | # config.action_controller.asset_host = Proc.new do |source, request|
248 | # "#{request.protocol}cdn#{source.hash % 4}.example.com"
249 | # # => 'http://cdn0.example.com', 'https://cdn1.example.com', etc.
250 | # end
251 | #
252 | # If your CDN doesn't have SSL support, you can instead revert SSL pages to
253 | # serving assets from your web server:
254 | #
255 | # config.action_controller.asset_host = Proc.new do |source, request|
256 | # request.ssl? ? nil : "http://cdn#{source.hash % 4}.example.com"
257 | # end
258 | #
259 | # Options:
260 | #
261 | # [ssl] Set to true to simulate a request via SSL. Defaults to
262 | # false.
263 | def self.compute_asset_host(asset_host, source, options={})
264 | host = asset_host
265 | if host.is_a?(Proc) || host.respond_to?(:call)
266 | case host.is_a?(Proc) ?
267 | host.arity : host.method(:call).arity
268 | when 2
269 | if defined?(ActionDispatch)
270 | request_class = ActionDispatch::Request
271 | else # Rails 2.x
272 | request_class = ActionController::Request
273 | end
274 | request = request_class.new(
275 | 'HTTPS' => options[:ssl] ? 'on' : 'off')
276 | host = host.call(source, request)
277 | else
278 | host = host.call(source)
279 | end
280 | else
281 | host %= (source.hash % 4) if host =~ /%d/
282 | end
283 | host
284 | end
285 |
286 | # Returns true if the asset host differs between SSL and
287 | # non-SSL pages, or false if the asset host doesn't change.
288 | def self.ssl_asset_host_differs?
289 | asset_host = ActionController::Base.asset_host
290 | AssetHat.compute_asset_host(asset_host, 'x.png') !=
291 | AssetHat.compute_asset_host(asset_host, 'x.png', :ssl => true)
292 | end
293 |
294 | def self.clear_html_cache
295 | html_cache = {}
296 | end
297 |
298 | end
299 |
--------------------------------------------------------------------------------
/doc/classes/AssetHat/CSS.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 42 | Methods for minifying and optimizing CSS. 43 |
44 || ENGINES | 66 |= | 67 |[:weak, :cssmin] | 68 |69 | | 70 | 71 | A list of supported minification engine names. 72 | | 73 |
87 | Given a string containing CSS, appends each 88 | referenced asset’s last commit ID to its URL, e.g., background: 89 | url(/images/foo.png?ab12cd3). This enables cache busting: If the 90 | user’s browser has cached a copy of foo.png from a previous 91 | deployment, this new URL forces the browser to ignore that cache and 92 | request the latest version. 93 |
94 |# File lib/asset_hat/css.rb, line 43 43: def self.add_asset_commit_ids(css) 44: update_css_urls(css, %w[images htc]) do |src, quote| 45: # Get absolute path 46: filepath = File.join(ASSETS_DIR, src) 47: 48: # Convert to relative path 49: filepath.sub!(/^#{FileUtils.pwd}#{File::SEPARATOR}/, '') 50: 51: commit_id = AssetHat.last_commit_id(filepath) 52: if commit_id.present? 53: "url(#{quote}#{src}#{src =~ /\?/ ? '&' : '?'}#{commit_id}#{quote})" 54: else 55: "url(#{quote}#{src}#{quote})" 56: end 57: end 58: end100 |
110 | Arguments: 111 |
112 |124 | An asset host is added to every image URL in the CSS, e.g., background: 126 | url(http://cdn2.example.com/images/foo.png); if %d in the 127 | asset host, it is replaced with an arbitrary number in 0-3, inclusive. 128 |
129 |130 | Options: 131 |
132 |# File lib/asset_hat/css.rb, line 77 77: def self.add_asset_hosts(css, asset_host, options={}) 78: return css if asset_host.blank? 79: 80: options.reverse_merge!(:ssl => false) 81: 82: update_css_urls(css, %w[images]) do |src, quote| 83: computed_asset_host = AssetHat.compute_asset_host( 84: asset_host, src, options.slice(:ssl)) 85: "url(#{quote}#{computed_asset_host}#{src}#{quote})" 86: end 87: end144 |
154 | Returns the expected path for the minified version of a CSS asset: 156 |
157 |AssetHat::CSS.min_filepath('public/stylesheets/bundles/application.css')
# => 'public/stylesheets/bundles/application.min.css'
158 | # File lib/asset_hat/css.rb, line 14 14: def self.min_filepath(filepath) 15: AssetHat.min_filepath(filepath, 'css') 16: end164 |
174 | Accepts a string of CSS, and returns that CSS minified. Options: 176 |
177 |# File lib/asset_hat/css.rb, line 23 23: def self.minify(input_string, options={}) 24: options.reverse_merge!(:engine => :cssmin) 25: 26: engine = options[:engine].to_sym 27: unless ENGINES.include?(engine) 28: raise %{ 29: Unknown CSS minification engine '#{engine}'. 30: Allowed: #{ENGINES.map{ |e| "'#{e}'" }.join(', ')} 31: }.strip.gsub(/\s+/, ' ') and return 32: end 33: 34: AssetHat::CSS::Engines.send(engine, input_string).strip 35: end191 |
<%= include_js :jquery %> to load straight from Google.
15 | * Load plenty of JS files in parallel in {LABjs}[http://labjs.com/]
16 | mode. When calling include_js, just add :loader =>
17 | :lab_js.
18 | * Force image URLs in your CSS to use CDN subdomains
19 | (including SSL support), not just the current host.
20 | * Add an image's last Git[http://git-scm.com/] commit ID to its CSS URLs to
21 | bust browser caches (e.g.,
22 | /images/foo.png?ab12cd3).
23 |
24 | After setup, you can use this in your layouts and views:
25 |
26 | <%= include_css :bundle => 'application' %>
27 | <%= include_js :jquery, :bundles => ['plugins', 'common'] %>
28 |
29 | Which expands into:
30 |
31 |
33 |
35 |
36 |
38 |
40 |
41 | Don't have your own copy of jQuery? AssetHat detects this and knows how to
42 | load jQuery from Google's CDN instead, whether you're in development or
43 | production.
44 |
45 | Add this to your deploy script, and your CSS/JS will be optimized
46 | automatically:
47 |
48 | rake asset_hat:minify
49 |
50 | Tested with Rails 3 and Rails 2.3.x (with Bundler). For a quick overview, see
51 | {the AssetHat website}[http://mintdigital.github.com/asset_hat/]. To see how
52 | AssetHat performs in production,
53 | {check some stats}[http://logicalfriday.com/2011/05/06/assethat-0-4-load-css-and-js-faster-your-assets-are-covered/].
54 | For the gritty details, check the
55 | {complete docs}[http://mintdigital.github.com/asset_hat/doc/] and
56 | {change history}[http://mintdigital.github.com/asset_hat/doc/files/HISTORY.html].
57 |
58 |
59 |
60 | == Installation
61 |
62 | === Rails 3.x
63 |
64 | 1. Add to your app's Gemfile: gem 'asset_hat', '0.x.x'
65 | 2. Command-line: bundle install
66 |
67 | === Rails 2.3.x
68 |
69 | 1. Add the gem:
70 |
71 | * If you're using {Bundler 0.9+}[http://github.com/carlhuda/bundler]:
72 |
73 | 1. Add to your app's Gemfile: gem 'asset_hat', '0.x.x'
74 | 2. Command-line: bundle install
75 |
76 | * If you're using Rails 2.3.x's config.gem:
77 |
78 | 1. Add to your app's config/environment.rb:
79 | config.gem 'asset_hat', :version => '0.x.x'
80 | 2. Command-line: gem install asset_hat
81 |
82 | 2. Add to your app's Rakefile: require 'asset_hat/tasks'
83 |
84 |
85 |
86 | == Quick start & configuration
87 |
88 | 1. In all of your layouts and views, change stylesheet_link_tag
89 | to include_css, and change
90 | javascript_include_tag to include_js. (Don't
91 | worry, these helpers use the same arguments as Rails' helpers. Nothing
92 | will break.)
93 |
94 | 2. Create the default config file:
95 |
96 | rake asset_hat:config
97 |
98 | 3. In your app, open the new file at config/assets.yml, and set
99 | up your CSS/JS bundles according to that file's example.
100 |
101 | 4. In your layouts and views, switch to the new bundles. For example, if you
102 | originally had this:
103 |
104 | <%# app/views/layouts/application.html.erb: %>
105 | <%= stylesheet_include_tag 'reset', 'application' %>
106 |
107 | Then you'll now have:
108 |
109 | # config/assets.yml:
110 | css:
111 | bundles:
112 | application: ['reset', 'application']
113 |
114 | <%# app/views/layouts/application.html.erb: %>
115 | <%= include_css :bundle => 'application' %>
116 |
117 | 5. Add this to your deployment script:
118 |
119 | rake asset_hat:minify
120 |
121 | This minifies all of the CSS/JS files listed in
122 | config/assets.yml, concatenates the minified code into bundle
123 | files, and adds CDN asset hosts and cache-busting commit IDs to image URLs
124 | in your CSS.
125 |
126 | Any previously minified bundles are overwritten; your original
127 | CSS/JS files remain untouched. Bundles are created as new files in
128 | public/stylesheets/bundles/ and
129 | public/javascripts/bundles/.
130 |
131 | If you're using a CSS/JS layer like SASS or CoffeeScript, be sure to
132 | compile/transpile to regular CSS/JS before running
133 | rake asset_hat:minify. (When AssetHat is ready for Rails 3.1,
134 | rake asset_hat:minify will automatically start with
135 | rake assets:precompile.)
136 |
137 | === Advanced configuration
138 |
139 | If you manage deployments with Capistrano[http://www.capify.org/], see the
140 | gem's packaged example at
141 | lib/asset_hat/capistrano.rb[https://github.com/mintdigital/asset_hat/blob/master/lib/asset_hat/capistrano.rb].
142 |
143 | If your stack uses Unicorn[http://unicorn.bogomips.org/], you'll want to
144 | configure it so that assets' commit IDs are precached only once. See the gem's
145 | packaged example at
146 | lib/asset_hat/unicorn.rb[https://github.com/mintdigital/asset_hat/blob/master/lib/asset_hat/unicorn.rb].
147 |
148 | If you want shorter output during deployments, you can use
149 | `rake asset_hat:minify FORMAT=short` (one output line per bundle) or
150 | `FORMAT=dot` (one output line total) in your deploy script.
151 |
152 | Additional settings are supported in config/assets.yml:
153 |
154 | * engine: Indicates how CSS and JS are minified; omit this
155 | setting to use the defaults. By default, CSS is minified with
156 | rgrove/cssmin[http://github.com/rgrove/cssmin] (a Ruby port of Lecomte's
157 | YUI Compressor and Schlueter's PHP cssmin), and JS is minified with
158 | rgrove/jsmin[http://github.com/rgrove/jsmin] (a Ruby port of Crockford's
159 | JSMin).
160 |
161 | If you'd prefer to have slightly more readable code for debugging purposes,
162 | you can switch both the CSS and JS engines to weak. However,
163 | the weak engines don't save as many bytes.
164 |
165 | * vendors: Configures third-party JS that's loaded from a CDN or
166 | other external source. The following example configures jQuery and jQuery UI
167 | for use throughout the app:
168 |
169 | js:
170 | vendors:
171 | jquery:
172 | version: 1.5.2
173 | jquery_ui:
174 | version: 1.8.12
175 | remote_url: http://cdn.example.com/js/jquery-ui-1.8.12.min.js
176 | remote_ssl_url: https://cdn-ssl.example.com/js/jquery-ui-1.8.12.min.js
177 |
178 | Configuration keys per vendor:
179 |
180 | * version: Sets the default version across the app. In the
181 | example above, <%= include_js :jquery %> uses version 1.5.2
182 | by default. You can override this for special layouts/views with
183 | <%= include_js :jquery, :version => '1.3.2' %>.
184 | * remote_url, remote_ssl_url: By default, vendor
185 | JS will load from {Google's CDN}[http://code.google.com/apis/ajaxlibs/]
186 | in production (or any environment where
187 | config.action_controller.consider_all_requests_local is set
188 | to false). If the original request to your app used SSL, the
189 | vendor JS will also load from Google's CDN via SSL. If you prefer to use a
190 | different CDN, specify its SSL/non-SSL URLs, and the appropriate URL will
191 | be used per request.
192 |
193 | A full list of supported vendors is in the
194 | AssetHat::JS::Vendors module.
195 |
196 | === SSL configuration
197 |
198 | When you request a page via SSL, some browsers (euphemism for "IE") show
199 | errors if any assets -- stylesheets, JS files, images -- are served _without_
200 | SSL.
201 |
202 | AssetHat plays well with SSL pages that load assets from a CDN. Put this in
203 | config/environments/production.rb or similar:
204 |
205 | config.action_controller.asset_host = Proc.new do |source, request|
206 | "#{request.protocol}cdn#{source.hash % 4}.example.com"
207 | # => 'http://cdn0.example.com', 'https://cdn1.example.com', etc.
208 | end
209 |
210 | This switches to a different CDN URL if the request used SSL. When you run
211 | rake asset_hat:minify at deploy time, AssetHat detects this
212 | configuration, and generates special SSL versions of each stylesheet that also
213 | load images from CDN via SSL. (Non-SSL pages still get non-SSL stylesheets.)
214 |
215 |
216 |
217 | == Usage
218 |
219 | In your layouts and views, instead of these:
220 |
221 | <%= stylesheet_link_tag 'reset', 'application', 'clearfix',
222 | :media => 'screen,projection',
223 | :cache => 'bundles/application' %>
224 | <%= javascript_include_tag 'plugin-1', 'plugin-2', 'plugin-3',
225 | :cache => 'bundles/application' %>
226 |
227 | Use these:
228 |
229 | <%= include_css :bundle => 'application' %>
230 | <%= include_js :bundle => 'application' %>
231 |
232 | These turn into:
233 |
234 |
236 |
238 |
239 | Have an enormous app? You can integrate gradually, using AssetHat alongside
240 | Rails' default asset caching.
241 |
242 | If your environment has config.action_controller.perform_caching
243 | set to true (e.g., in production), the layout/view will include
244 | minified bundle files. Otherwise, the separate, unminified files will be
245 | included, based on the bundle contents you define in
246 | config/assets.yml.
247 |
248 | If your environment has config.action_controller.asset_host
249 | pointing to a CDN, your CSS/JS files will load from there. If your
250 | configuration supports using the CDN via SSL (see the section "SSL
251 | configuration"), SSL requests will also load CSS/JS files via SSL.
252 |
253 | === Advanced usage
254 |
255 | You can also include single files as expected:
256 |
257 | <%= include_css 'reset', 'application' %>
258 | <%= include_js 'plugin.min', 'application' %>
259 |
260 | Or include multiple bundles at once:
261 |
262 | <%= include_js :bundles => %w[plugins common] %>
263 |
264 | When including multiple bundles at once, this yields one
265 | or element per bundle.
266 |
267 | === LABjs mode
268 |
269 | Say you're loading several JS bundles. However, because there are so many, you
270 | decide to try a popular script loader like {LABjs}[http://labjs.com/] to see
271 | how much it improves performance. Here's how it's done the old Rails way:
272 |
273 | <%= javascript_include_tag 'LAB-1.2.0.min.js' %>
274 |
281 |
282 | What a hassle. With AssetHat, just set up a bundle in
283 | config/assets.yml:
284 |
285 | js:
286 | bundles:
287 | app:
288 | - common
289 | - search
290 | - app
291 |
292 | Ready to go. Here's how to load jQuery and your bundle normally:
293 |
294 | <%= include_js :jquery, :bundle => 'app' %>
295 |
296 | And here's how to switch on LABjs mode:
297 |
298 | <%= include_js :jquery, :bundle => 'app',
299 | :loader => :lab_js %>
300 |
301 | Add your preferred jQuery and LABjs versions to the config file if you haven't
302 | already, and that's it. If you don't have a copy of LABjs locally, AssetHat
303 | knows how to instead load it from {cdnjs}[http://cdnjs.com/], which uses
304 | high-speed Amazon Cloudfront servers.
305 |
306 | This is just the most common LABjs use case. If you want to fine-tune it even
307 | further, you can have the best of both worlds:
308 |
309 | <%= include_js :lab_js %>
310 |
317 |
318 | In this example, common is not a dependency for
319 | search, so allow either to execute as soon as possible --
320 | whichever happens to load first -- rather than always forcing
321 | common to execute first.
322 |
323 | === Bundle tips
324 |
325 | Don't go overboard with huge bundles:
326 |
327 | * Mobile browsers may not cache CSS/JS files that are too large, regardless of
328 | gzipping. Check the latest specs for each mobile browser you support.
329 | * You might want to put plugins (rarely changed) in one bundle, and
330 | application code (frequently changed) in another bundle. This way, when the
331 | app code changes, the browser re-downloads only the new app code, and uses
332 | the cached plugin code.
333 | * Regardless of code-change frequency, it's sometimes faster to split a bundle
334 | in half, and load each half in parallel (i.e., two HTTP requests instead of
335 | one). LABjs mode can help with loading several smaller bundles in parallel.
336 | Your own tests will tell what's optimal for your situation.
337 |
338 |
339 |
340 | == More info
341 |
342 | * {Official website}[http://mintdigital.github.com/asset_hat/]
343 | * {Full documentation}[http://mintdigital.github.com/asset_hat/doc/]
344 | * {History/changelog}[http://mintdigital.github.com/asset_hat/doc/files/HISTORY.html]
345 | * Released under the
346 | {MIT license}[https://github.com/mintdigital/asset_hat/blob/master/LICENSE].
347 |
348 | === Contributing
349 |
350 | Have an idea, problem, or bug report?
351 | {Send a pull request}[http://help.github.com/send-pull-requests/]! Please base
352 | pull requests on the `development` branch, not the `master` branch.
353 |
354 | Contributors:
355 |
356 | * {rondevera}[https://github.com/rondevera]
357 | (maintainer; Twitter: {@ronalddevera}[https://twitter.com/#!/ronalddevera])
358 | * {philnash}[https://github.com/philnash]
359 | * {dstrelau}[https://github.com/dstrelau]
360 | * {daphonz}[https://github.com/daphonz]
361 | * {sauliusg}[https://github.com/sauliusg]
362 | * {jsonperl}[https://github.com/jsonperl]
363 |
364 |
365 |
366 | == {What is best in AssetHat?}[http://www.youtube.com/watch?v=V30tyaXv6EI]
367 |
368 | * To crush your assets;
369 | * See them bundled before you; and
370 | * Hear no more lamentation about slow page loads.
371 |
--------------------------------------------------------------------------------
/doc/classes/AssetHat/JS/Vendors.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 42 | For working with supported 3rd-party JavaScript plugin/framework/library 43 | vendors. 44 |
45 || VENDORS_ON_GOOGLE_CDN | 60 |= | 61 |[ :dojo, :ext_core, :jquery, :jquery_ui, :mootools, :prototype, :scriptaculous, :swfobject, :webfont, :yui ] | 62 |63 | |
64 |
65 | A list of supported 3rd-party JavaScript plugin/vendor names. Homepages:
66 |
67 |
|
115 |
| VENDORS_ON_CDNJS | 118 |= | 119 |[ :lab_js ] | 120 |||
| VENDORS | 123 |= | 124 |VENDORS_ON_GOOGLE_CDN + VENDORS_ON_CDNJS | 125 |
139 | Accepts an item from `VENDORS`, and returns the URL at which that vendor 140 | asset can be found. The URL is either local (relative) or remote, depending 141 | on the environment configuration: 142 |
143 |182 | Options: 183 |
184 |# File lib/asset_hat/js/vendors.rb, line 67 67: def self.source_for(vendor, options={}) 68: vendor_config = 69: AssetHat.config['js']['vendors'][vendor.to_s] rescue nil 70: use_local = AssetHat.consider_all_requests_local? 71: use_ssl = !!options[:ssl] 72: version = options[:version] || vendor_config['version'] rescue nil 73: 74: # Prepare local path and default remote URL 75: srcs = Vendors.vendor_uris(vendor, 76: :use_ssl => use_ssl, :version => version) 77: local_src, remote_src = srcs[:local], srcs[:remote] 78: 79: # Using the local URL requires that the vendor file exists locally. If 80: # the vendor file doesn't exist, use the remote URL as fallback. 81: use_local &&= AssetHat.asset_exists?(local_src, :js) 82: 83: # If no version given, can't determine the remote URL; use the local 84: # URL as fallback. 85: use_local ||= version.blank? 86: 87: if use_local 88: src = local_src 89: else 90: # To ease setup, if no local copy of the vendor code is found, 91: # use a remote URL as a fallback. 92: 93: # Give precedence to configured remote URLs 94: src = vendor_config.try(:[], 'remote_ssl_url') if use_ssl 95: src ||= vendor_config.try(:[], 'remote_url') 96: 97: # Use default remote URL as fallback 98: src ||= remote_src 99: 100: # Use local URL as final resort, even though the file doesn't 101: # exist, in hopes that the app maintainer finds the 404 (or the 102: # warning below) in the logs. This needs to be fixed in the app, 103: # rather than relying on a CDN to dynamically provide the latest 104: # stable vendor version. 105: if src.blank? 106: src = local_src 107: Rails.logger.warn "\n\nAssetHat WARNING (#{Time.now}):\n" + %{ 108: Tried to reference the vendor JS `:#{vendor}`, but 109: #{AssetHat.assets_dir(:js)}/#{local_src} couldn't be found, and 110: couldn't use a remote fallback because no vendor version was 111: given in #{AssetHat::RELATIVE_CONFIG_FILEPATH}. 112: }.squish! 113: # TODO: Create `AssetHat::Logger.warn`, etc. methods 114: end 115: end 116: 117: src 118: end201 |