compressible 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.markdown +125 -0
- data/Rakefile +80 -0
- data/init.rb +1 -0
- data/lib/compressible.rake +27 -0
- data/lib/compressible.rb +225 -0
- data/lib/compressible/view_helpers.rb +17 -0
- data/lib/ext.rb +25 -0
- data/rails/init.rb +1 -0
- data/test/config.yml +20 -0
- data/test/initializer.rb +2 -0
- data/test/result.css +3 -0
- data/test/result.js +3 -0
- data/test/result_test.css +3 -0
- data/test/test-a.css +450 -0
- data/test/test-a.js +205 -0
- data/test/test-b.css +48 -0
- data/test/test-b.js +124 -0
- data/test/test_compressible.rb +125 -0
- data/test/test_helper.rb +8 -0
- metadata +106 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Lance Pollard and David Crockett
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# Compressible
|
2
|
+
|
3
|
+
Ready-to-go Asset Compression for Ruby, using the YUICompressor.
|
4
|
+
|
5
|
+
sudo gem install compressible
|
6
|
+
|
7
|
+
# Usage
|
8
|
+
|
9
|
+
## Configuration
|
10
|
+
|
11
|
+
There are a few ways you can configure this
|
12
|
+
|
13
|
+
### 1. Initializers
|
14
|
+
|
15
|
+
Inside an initializer such as `config/compressible.rb`:
|
16
|
+
|
17
|
+
Compressible.stylesheets(
|
18
|
+
:production_cache => %w(reset background footer list),
|
19
|
+
:typography => %w(forms headers basic_text)
|
20
|
+
)
|
21
|
+
Compressible.javascripts(
|
22
|
+
:production_cache => %w(animations effects dragdrop)
|
23
|
+
)
|
24
|
+
|
25
|
+
# or
|
26
|
+
Compressible.assets(
|
27
|
+
:stylesheets => {
|
28
|
+
:production_cache => %w(reset background footer list),
|
29
|
+
:typography => %w(forms headers basic_text)
|
30
|
+
}
|
31
|
+
:javascripts => {
|
32
|
+
:production_cache => %w(animations effects dragdrop)
|
33
|
+
}
|
34
|
+
)
|
35
|
+
|
36
|
+
# or one at a time
|
37
|
+
Compressible.stylesheet(
|
38
|
+
:production_cache => %w(reset background footer list)
|
39
|
+
)
|
40
|
+
Compressible.stylesheet(
|
41
|
+
:typography => %w(forms headers basic_text)
|
42
|
+
)
|
43
|
+
|
44
|
+
Each of those methods have plenty of aliases to be more descriptive and more concise, respectively:
|
45
|
+
|
46
|
+
- `stylesheets`: `add_stylesheets`
|
47
|
+
- `javascripts`: `add_javascripts`
|
48
|
+
- `stylesheet`: `add_stylesheet`, `css`
|
49
|
+
- `javascript`: `add_javascript`, `js`
|
50
|
+
|
51
|
+
Or you can call them generically:
|
52
|
+
|
53
|
+
Compressible.assets(:stylesheet)
|
54
|
+
Compressible.assets(:javascript)
|
55
|
+
|
56
|
+
### 2. Yaml
|
57
|
+
|
58
|
+
You can specify these same configuration options using Yaml:
|
59
|
+
|
60
|
+
js:
|
61
|
+
paths:
|
62
|
+
- animations
|
63
|
+
- effects
|
64
|
+
- dragdrop
|
65
|
+
to: production_cache
|
66
|
+
munge: true
|
67
|
+
css:
|
68
|
+
-
|
69
|
+
paths:
|
70
|
+
- reset
|
71
|
+
- background
|
72
|
+
- footer
|
73
|
+
- list
|
74
|
+
to: production_cache
|
75
|
+
-
|
76
|
+
paths:
|
77
|
+
- forms
|
78
|
+
- headers
|
79
|
+
- basic_text
|
80
|
+
to: typography
|
81
|
+
|
82
|
+
You can then setup everything with `configure`:
|
83
|
+
|
84
|
+
Compressible.configure("config/compressible.yml") # or pass it the yaml hash
|
85
|
+
|
86
|
+
## Views
|
87
|
+
|
88
|
+
Add this to your views:
|
89
|
+
|
90
|
+
compressible_stylesheet_tag "production_cache", "typography"
|
91
|
+
compressible_javascript_tag "production_cache"
|
92
|
+
|
93
|
+
By default, it will use non-compressed stylesheets when `Rails.env == "development"`, and the compressed stylesheets when `Rails.env == "production"`.
|
94
|
+
|
95
|
+
To tell it you want to use the cached in a different environment, you can specify it like this:
|
96
|
+
|
97
|
+
compressible_stylesheet_tag "production_cache", :environments => ["production", "staging"]
|
98
|
+
|
99
|
+
## Awesome Assets for Heroku
|
100
|
+
|
101
|
+
Because Heroku is a Read-Only file system, you can't use Rails' built in asset cacher, or libraries like `asset_packager`. They rely on the ability to write to the file system in the production environment.
|
102
|
+
|
103
|
+
Some libraries solve this by patching `asset_packager` to redirect css/js urls to the `/tmp` directory using Rack middleware. The `/tmp` directory is about the only place Heroku lets you write to the file system in. The main issue with this approach is that all requests for stylesheets and javascripts must pass through Rack, which potentially _doubles_ response time. Take a look at the [asset loading performance comparison with different Ruby stacks](http://www.ridingtheclutch.com/2009/07/13/the-ultimate-ruby-performance-test-part-1.html).
|
104
|
+
|
105
|
+
As such, Compressible compresses all assets before you push to Heroku, so a) you never write to the Heroku file system, and b) you don't have slow down the request with application or middleware layers. It does this with _git hooks_. Every time you commit, a `pre-commit` hook runs which re-compresses your assets. That means whenever you push, your assets are ready for production.
|
106
|
+
|
107
|
+
This is configurable. It relies on the [`hookify`](http://github.com/viatropos/hookify) gem.
|
108
|
+
|
109
|
+
sudo gem install hookify
|
110
|
+
cd my-rails-app
|
111
|
+
hookify pre-commit
|
112
|
+
|
113
|
+
That creates a ruby script for you were you can define what you want to run when. In our case, we want to run:
|
114
|
+
|
115
|
+
Compressible.assets(
|
116
|
+
:stylesheets => {
|
117
|
+
:production_cache => %w(reset background footer list),
|
118
|
+
:typography => %w(forms headers basic_text)
|
119
|
+
}
|
120
|
+
:javascripts => {
|
121
|
+
:production_cache => %w(animations effects dragdrop)
|
122
|
+
}
|
123
|
+
)
|
124
|
+
|
125
|
+
Very cool.
|
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require "rake/rdoctask"
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
# http://docs.rubygems.org/read/chapter/20
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.name = "compressible"
|
8
|
+
s.version = "0.0.2"
|
9
|
+
s.author = "Lance Pollard"
|
10
|
+
s.summary = "Compressible: Quick asset compression for Ruby"
|
11
|
+
s.homepage = "http://github.com/viatropos/compressible"
|
12
|
+
s.email = "lancejpollard@gmail.com"
|
13
|
+
s.description = "Quick asset compression for Ruby"
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.rubyforge_project = "compressible"
|
16
|
+
s.platform = Gem::Platform::RUBY
|
17
|
+
s.files = %w(README.markdown Rakefile init.rb MIT-LICENSE) + Dir["{lib,rails,test}/**/*"] - Dir["test/tmp"]
|
18
|
+
s.require_path = "lib"
|
19
|
+
s.add_dependency("activesupport", ">= 2.1.2")
|
20
|
+
s.add_dependency("yui-compressor")
|
21
|
+
end
|
22
|
+
|
23
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
24
|
+
pkg.gem_spec = spec
|
25
|
+
pkg.package_dir = "pkg"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Create .gemspec file (useful for github)"
|
29
|
+
task :gemspec do
|
30
|
+
File.open("pkg/#{spec.name}.gemspec", "w") do |f|
|
31
|
+
f.puts spec.to_ruby
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Build the gem into the current directory"
|
36
|
+
task :gem => :gemspec do
|
37
|
+
`gem build pkg/#{spec.name}.gemspec`
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Publish gem to rubygems"
|
41
|
+
task :publish => [:package] do
|
42
|
+
%x[gem push pkg/#{spec.name}-#{spec.version}.gem]
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Print a list of the files to be put into the gem"
|
46
|
+
task :manifest do
|
47
|
+
File.open("Manifest", "w") do |f|
|
48
|
+
spec.files.each do |file|
|
49
|
+
f.puts file
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Install the gem locally"
|
55
|
+
task :install => [:package] do
|
56
|
+
sh %{sudo gem install pkg/#{spec.name}-#{spec.version} --no-ri --no-rdoc}
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Generate the rdoc"
|
60
|
+
Rake::RDocTask.new do |rdoc|
|
61
|
+
files = ["README.markdown", "lib/**/*.rb"]
|
62
|
+
rdoc.rdoc_files.add(files)
|
63
|
+
rdoc.main = "README.markdown"
|
64
|
+
rdoc.title = spec.summary
|
65
|
+
end
|
66
|
+
|
67
|
+
task :yank do
|
68
|
+
`gem yank #{spec.name} -v #{spec.version}`
|
69
|
+
end
|
70
|
+
|
71
|
+
task :lance do
|
72
|
+
puts "LANCE POLARd"
|
73
|
+
end
|
74
|
+
|
75
|
+
namespace :hookify do
|
76
|
+
|
77
|
+
task :pre_commit do
|
78
|
+
end
|
79
|
+
|
80
|
+
end#
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
File.dirname(__FILE__) + "/rails/init.rb"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'yui/compressor'
|
4
|
+
|
5
|
+
namespace :compress do
|
6
|
+
|
7
|
+
desc "Compress JS"
|
8
|
+
task :js do
|
9
|
+
Compressible.js(
|
10
|
+
ENV["JS"].split(/,?\s+/),
|
11
|
+
:to => ENV["TO"],
|
12
|
+
:munge => ENV["MUNGE"].blank? || true : ENV["MUNGE"]
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Compress CSS"
|
17
|
+
task :css do
|
18
|
+
Compressible.css(
|
19
|
+
ENV["CSS"].split(/,?\s+/),
|
20
|
+
:to => ENV["TO"]
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Compress CSS and JS"
|
25
|
+
task :all => [:js, :css]
|
26
|
+
|
27
|
+
end
|
data/lib/compressible.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
begin
|
4
|
+
gem "activesupport", "= 2.3.5"
|
5
|
+
require 'active_support'
|
6
|
+
rescue Gem::LoadError => e
|
7
|
+
puts e.inspect
|
8
|
+
end
|
9
|
+
require 'yui/compressor'
|
10
|
+
this = File.dirname(__FILE__)
|
11
|
+
require File.join(this, "ext.rb")
|
12
|
+
|
13
|
+
class Compressible
|
14
|
+
|
15
|
+
KEYS = {
|
16
|
+
:css => :stylesheet,
|
17
|
+
:stylesheet => :css,
|
18
|
+
:js => :javascript,
|
19
|
+
:javascript => :js
|
20
|
+
}
|
21
|
+
|
22
|
+
class << self
|
23
|
+
|
24
|
+
attr_reader :config
|
25
|
+
|
26
|
+
def configure(value = nil)
|
27
|
+
raise "invalid config" unless (value.is_a?(String) || value.is_a?(Hash))
|
28
|
+
@config = value.is_a?(String) ? YAML.load_file(value) : value
|
29
|
+
@config.recursively_symbolize_keys!
|
30
|
+
|
31
|
+
@config = defaults.merge(@config)
|
32
|
+
|
33
|
+
# normalize everything to an array
|
34
|
+
[:js, :css].each do |type|
|
35
|
+
@config[type] = [@config[type]] unless @config[type].is_a?(Array)
|
36
|
+
end
|
37
|
+
|
38
|
+
@config
|
39
|
+
end
|
40
|
+
|
41
|
+
def defaults
|
42
|
+
{
|
43
|
+
:js => [],
|
44
|
+
:css => [],
|
45
|
+
:stylesheet_path => defined?(Rails) ? "#{Rails.root}/public/stylesheets" : nil,
|
46
|
+
:javascript_path => defined?(Rails) ? "#{Rails.root}/public/javascripts" : nil
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def config
|
51
|
+
@config ||= defaults
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_to_config(type, key, value)
|
55
|
+
item = find_or_create(type, key)
|
56
|
+
item[:paths] = value.collect {|i| asset_name(i)}
|
57
|
+
item
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_or_create(type, key)
|
61
|
+
result = config[type].detect {|i| i[:to].to_s == key.to_s}
|
62
|
+
unless result
|
63
|
+
result = {:to => key.to_s}
|
64
|
+
config[type] << result
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def reset
|
70
|
+
@config = defaults
|
71
|
+
end
|
72
|
+
|
73
|
+
def uncached_stylesheet_paths(*keys)
|
74
|
+
uncached_paths_for(:css, *keys)
|
75
|
+
end
|
76
|
+
|
77
|
+
def uncached_javascript_paths(*keys)
|
78
|
+
uncached_paths_for(:js, *keys)
|
79
|
+
end
|
80
|
+
|
81
|
+
def uncached_paths_for(type, *keys)
|
82
|
+
returning [] do |result|
|
83
|
+
config[type].each do |item|
|
84
|
+
keys.each do |key|
|
85
|
+
result.concat(item[:paths]) if item[:to] == key.to_s
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# called if you gave it a config
|
92
|
+
def compress(value = nil)
|
93
|
+
configure(value) if value
|
94
|
+
raise "set config to yaml file or run 'Compressible.js' or 'Compressible.css' manually" unless @config
|
95
|
+
|
96
|
+
[:js, :css].each do |k|
|
97
|
+
config[k].each do |item|
|
98
|
+
compress_from_hash(k, item)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def compress_from_hash(k, v)
|
104
|
+
args = v.dup.delete(:paths) + [v]
|
105
|
+
self.send(k, *args)
|
106
|
+
end
|
107
|
+
|
108
|
+
def javascripts(hash)
|
109
|
+
hash.each do |to, paths|
|
110
|
+
paths << {:to => to}
|
111
|
+
javascript(*paths)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
alias_method :add_javascripts, :javascripts
|
115
|
+
|
116
|
+
def stylesheets(hash)
|
117
|
+
hash.each do |to, paths|
|
118
|
+
paths << {:to => to}
|
119
|
+
stylesheet(*paths)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias_method :add_stylesheets, :stylesheets
|
123
|
+
|
124
|
+
def javascript(*paths)
|
125
|
+
options = paths.extract_options!
|
126
|
+
to = asset_name(options[:to])
|
127
|
+
raise "Please define a name for the cached javascript using ':to => :my_name'" unless to
|
128
|
+
munge = options.has_key?(:munge) ? options[:munge] : true
|
129
|
+
|
130
|
+
add_to_config(:js, to, paths)
|
131
|
+
|
132
|
+
compressor = YUI::JavaScriptCompressor.new(:munge => munge)
|
133
|
+
|
134
|
+
result = paths.collect do |path|
|
135
|
+
puts "Compressing #{path}..."
|
136
|
+
compressor.compress(read(:javascript, path))
|
137
|
+
end.join("\n\n")
|
138
|
+
|
139
|
+
write(:javascript, to, result) if to
|
140
|
+
|
141
|
+
result
|
142
|
+
end
|
143
|
+
alias_method :add_javascript, :javascript
|
144
|
+
alias_method :js, :javascript
|
145
|
+
|
146
|
+
def stylesheet(*paths)
|
147
|
+
options = paths.extract_options!
|
148
|
+
to = asset_name(options[:to])
|
149
|
+
|
150
|
+
add_to_config(:css, to, paths)
|
151
|
+
|
152
|
+
compressor = YUI::CssCompressor.new
|
153
|
+
|
154
|
+
result = paths.collect do |path|
|
155
|
+
puts "Compressing #{path}..."
|
156
|
+
compressor.compress(read(:stylesheet, path))
|
157
|
+
end.join("\n\n")
|
158
|
+
|
159
|
+
write(:stylesheet, to, result) if to
|
160
|
+
|
161
|
+
result
|
162
|
+
end
|
163
|
+
alias_method :add_stylesheet, :stylesheet
|
164
|
+
alias_method :css, :stylesheet
|
165
|
+
|
166
|
+
def stylesheets_for(*keys)
|
167
|
+
assets_for(:stylesheet, *keys)
|
168
|
+
end
|
169
|
+
|
170
|
+
def javascripts_for(*keys)
|
171
|
+
assets_for(:javascript, *keys)
|
172
|
+
end
|
173
|
+
|
174
|
+
def assets_for(type, *keys)
|
175
|
+
options = keys.extract_options!
|
176
|
+
environment = defined?(Rails) ? Rails.env.to_s : (options[:current] || "production")
|
177
|
+
environment = environment.to_s
|
178
|
+
cache_environments = options[:environments] || "production"
|
179
|
+
cache_environments = [cache_environments] unless cache_environments.is_a?(Array)
|
180
|
+
cache_environments = cache_environments.collect(&:to_s)
|
181
|
+
|
182
|
+
assets = cache_environments.include?(environment) ? keys : send("uncached_#{type.to_s}_paths", *keys)
|
183
|
+
assets
|
184
|
+
end
|
185
|
+
|
186
|
+
def read(type, from)
|
187
|
+
IO.read(path_for(type, from))
|
188
|
+
end
|
189
|
+
|
190
|
+
def write(type, to, result)
|
191
|
+
File.open(path_for(type, to), "w") {|f| f.puts result}
|
192
|
+
end
|
193
|
+
|
194
|
+
def asset_name(path)
|
195
|
+
result = path.to_s.split(".")
|
196
|
+
if result.last =~ /(js|css)/
|
197
|
+
result = result[0..-2].join(".")
|
198
|
+
else
|
199
|
+
result = result.join(".")
|
200
|
+
end
|
201
|
+
result
|
202
|
+
end
|
203
|
+
|
204
|
+
# ultimately should return global path
|
205
|
+
def path_for(type, file)
|
206
|
+
key = "#{type.to_s}_path".to_sym
|
207
|
+
|
208
|
+
if config && config[key]
|
209
|
+
path = File.join(config[key], file.to_s)
|
210
|
+
elsif defined?(Rails)
|
211
|
+
path = File.join(Rails.root.to_s, "public/#{type.to_s.pluralize}", file.to_s)
|
212
|
+
else
|
213
|
+
path = file.to_s
|
214
|
+
end
|
215
|
+
|
216
|
+
path << ".#{KEYS[type].to_s}" unless path.split(".").last == KEYS[type].to_s
|
217
|
+
|
218
|
+
path
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
Dir["#{this}/compressible/*"].each { |c| require c }
|