rack-pack 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :gemcutter
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Peter Browne
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.
@@ -0,0 +1,89 @@
1
+ # Rack::Pack
2
+
3
+ Rack::Pack is a piece of Rack Middleware that packages and optionally compresses assets such as javascripts and stylesheets into single files. In a development environment, assets will be packaged on each request if there have been changes to the source files. In a production environment, assets will only be packaged one time, and only if there have been changes.
4
+
5
+ ### Why?
6
+
7
+ I've tried a dozen different asset packaging solutions including AssetPackager, BundleFu, Jammit, Sprockets, etc...none of which were quite what I wanted. I didn't need any helpers, controllers, embedded images, rake tasks, or Yaml config files. I just wanted something to take my assets and package them into one file, and you're looking at it.
8
+
9
+ ## Installation
10
+
11
+ gem install rack-pack
12
+
13
+ ## Basic Usage
14
+
15
+ require 'rack-pack'
16
+ use Rack::Pack
17
+
18
+ or in Rails:
19
+
20
+ # Gemfile
21
+ gem 'rack-pack'
22
+
23
+ # config/application.rb
24
+ config.middleware.use Rack::Pack
25
+
26
+ ### Packages
27
+
28
+ Two files will be packaged out of the box: `javascripts/application.js` & `stylesheets/application.css`. Rack::Pack will look in `vendor/javascripts`, `app/javascripts`, & `./javascripts` for any .js files and `vendor/stylesheets`, `app/stylesheets`, & `./stylesheets` for any .css files. These files will be packaged in the order they're found.
29
+
30
+ To create your own packages, pass in the name of the output file and the source files as options:
31
+
32
+ use Rack::Pack, 'js/main.js' => [
33
+ 'vendor/javascripts/jquery.js',
34
+ 'vendor/javascripts/swfobject.js,
35
+ 'app/javascripts/misc.js',
36
+ 'app/javascripts/main.js'
37
+ ]
38
+ # Creates a 'public/js/main.js' file
39
+
40
+ Notice how the output file is relative to the public dir. By default this is just `'public'`, but this can be changed using the `:public_dir` option:
41
+
42
+ use Rack::Pack, :public_dir => 'html', 'js/main.js' => %w(js/plugins.js js/main.js)
43
+ # Creates a 'html/js/main.js' file
44
+
45
+ You can also pass a glob string for the source files. This string will be used to search for new files on each request. The downside is the source files will be concatenated in the order they're found.
46
+
47
+ use Rack::Pack, 'assets/scripts.js' => 'app/js/**/*.js'
48
+
49
+ In fact, this is how the default packages are declared:
50
+
51
+ use Rack::Pack,
52
+ 'javascripts/application.js' => '{vendor,app,.}/javascripts/*.js',
53
+ 'stylesheets/application.css' => '{vendor,app,.}/stylesheets/*.css'
54
+
55
+ Beautiful, isn't it? I don't think you can get simpler than that. No Yaml config files or rake tasks. You'll set it up once then forget about it completely. Well unless you have to add a new source file and you were explicity setting your source files for a package, but whatever.
56
+
57
+ ### Compression
58
+
59
+ What good would an asset packager be without compression? Rack::Pack determines which javascript compressor you want to use based on which one has been required.
60
+
61
+ require 'packr'
62
+ use Rack::Pack
63
+ # would use Packr
64
+
65
+ or in Rails:
66
+
67
+ # Gemfile
68
+ gem 'jsmin'
69
+
70
+ # config/application.rb
71
+ config.middleware.use Rack::Pack
72
+ # would use JSMin
73
+
74
+ To pass options to the javascript compressor just use the `:js_compressor` option:
75
+
76
+ require 'packr'
77
+ use Rack::Pack, :js_compression => { :shrink_vars => true }
78
+
79
+ By default, packages are only compressed in a production environment. If for some reason you want them to always be compressed, pass the `:always_compress` option:
80
+
81
+ use Rack::Pack, :always_compress => true
82
+
83
+ ## Heroku and other read-only filesystems
84
+
85
+ Because Rack::Pack relies on writing the packaged files, it won't package anything on Heroku. But, you could just check in your packaged files and push them to Heroku. I'll look into other options for future versions.
86
+
87
+ ## Copyright
88
+
89
+ Copyright (c) 2010 [Peter Browne](http://petebrowne.com). See LICENSE for details.
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ Rspec::Core::RakeTask.new
7
+
8
+ desc 'Open an irb session preloaded with this library'
9
+ task :console do
10
+ sh 'irb -rubygems -I lib -r mock_ftp.rb'
11
+ end
12
+
13
+ task :default => :spec
@@ -0,0 +1 @@
1
+ require 'rack/pack'
@@ -0,0 +1,87 @@
1
+ require 'rack'
2
+
3
+ module Rack
4
+ class Pack
5
+ autoload :Javascript, 'rack/pack/javascript'
6
+ autoload :Package, 'rack/pack/package'
7
+ autoload :Stylesheet, 'rack/pack/stylesheet'
8
+ autoload :Version, 'rack/pack/version'
9
+
10
+ DEFAULT_OPTIONS = {
11
+ :public_dir => 'public',
12
+ :always_update => false,
13
+ :always_compress => false,
14
+ :js_compression => {},
15
+ :css_compression => {}
16
+ }.freeze
17
+
18
+ class << self
19
+ attr_accessor :packages, :options, :environment
20
+
21
+ def production?
22
+ self.environment.to_s == 'production'
23
+ end
24
+
25
+ def add_package(output_file, source_files)
26
+ if source_files.nil?
27
+ packages.delete(output_file)
28
+ else
29
+ public_output_file = ::File.join(self.options[:public_dir], output_file.to_s)
30
+ package_class = Package[output_file]
31
+ packages[output_file] = package_class.new(public_output_file, source_files)
32
+ end
33
+ end
34
+ end
35
+
36
+ def initialize(app, options = {})
37
+ @app = app
38
+
39
+ Pack.packages = {}
40
+ Pack.options = DEFAULT_OPTIONS.dup
41
+ Pack.environment = if defined?(RAILS_ENV)
42
+ RAILS_ENV # Rails 2
43
+ elsif defined?(Rails) && defined?(Rails.env)
44
+ Rails.env # Rails 3
45
+ elsif defined?(app.settings) && defined?(app.settings.environment)
46
+ app.settings.environment # Sinatra
47
+ elsif ENV.key?('RACK_ENV')
48
+ ENV['RACK_ENV']
49
+ else
50
+ :development
51
+ end
52
+
53
+ Pack.options.each_key do |key|
54
+ Pack.options[key] = options.delete(key) if options.key?(key)
55
+ end
56
+
57
+ Pack.add_package 'javascripts/application.js', '{vendor,app,.}/javascripts/*.js'
58
+ Pack.add_package 'stylesheets/application.css', '{vendor,app,.}/stylesheets/*.css'
59
+
60
+ options.each do |to_file, from_files|
61
+ Pack.add_package(to_file, from_files)
62
+ end
63
+ end
64
+
65
+ def call(env)
66
+ update_packages unless skip_update?
67
+ @app.call(env)
68
+ end
69
+
70
+ protected
71
+
72
+ def update_packages
73
+ Pack.packages.each_value do |package|
74
+ package.update if package.stale?
75
+ end
76
+ @updated = true
77
+ end
78
+
79
+ def skip_update?
80
+ return false if Pack.options[:always_update]
81
+ Pack.production? && @updated
82
+ end
83
+
84
+ Package.register :js, Javascript
85
+ Package.register :css, Stylesheet
86
+ end
87
+ end
@@ -0,0 +1,28 @@
1
+ module Rack
2
+ class Pack
3
+ class Javascript < Package
4
+ def compress(source)
5
+ if defined?(JSMin)
6
+ JSMin.minify(source)
7
+ elsif defined?(Packr)
8
+ options = compression_options :shrink_vars => true
9
+ Packr.pack(source, options)
10
+ elsif defined?(YUI) and defined?(YUI::JavaScriptCompressor)
11
+ options = compression_options :munge => true
12
+ YUI::JavaScriptCompressor.new(options).compress(source)
13
+ elsif defined?(Closure) and defined?(Closure::Compiler)
14
+ Closure::Compiler.new(compression_options).compile(source)
15
+ else
16
+ source
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ def compression_options(defaults = {})
23
+ return defaults unless Pack.options
24
+ defaults.merge Pack.options[:js_compression]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,82 @@
1
+ require 'pathname'
2
+
3
+ module Rack
4
+ class Pack
5
+ class Package
6
+ class << self
7
+ def mappings
8
+ @package_mappings ||= {}
9
+ end
10
+
11
+ def register(ext, package_class)
12
+ ext = ext.to_s.sub(/^\./, '').downcase
13
+ mappings[ext] = package_class
14
+ end
15
+
16
+ def [](file)
17
+ ext = ::File.extname(file.to_s).sub(/^\./, '').downcase
18
+ mappings[ext] || self
19
+ end
20
+ end
21
+
22
+ attr_reader :file
23
+
24
+ def initialize(output_file, source_files)
25
+ @file = to_pathname(output_file)
26
+ @from = if source_files.is_a?(Array)
27
+ source_files.map { |file| to_pathname(file) }
28
+ else
29
+ source_files.to_s
30
+ end
31
+ end
32
+
33
+ def update
34
+ file.dirname.mkpath
35
+ file.open('w') do |file|
36
+ file << compile
37
+ end
38
+ end
39
+
40
+ def compile
41
+ compiled = source_files.map(&:read).join
42
+ compiled = compress(compiled) if compress?
43
+ compiled.strip
44
+ end
45
+
46
+ def compress(source)
47
+ source
48
+ end
49
+
50
+ def stale?
51
+ @source_files = nil
52
+ source_files? && (file_missing? || source_files_newer?)
53
+ end
54
+
55
+ def source_files
56
+ @source_files ||= @from.is_a?(Array) ? @from : Pathname.glob(@from)
57
+ end
58
+
59
+ def compress?
60
+ Pack.production? || Pack.options && Pack.options[:always_compress]
61
+ end
62
+
63
+ protected
64
+
65
+ def to_pathname(file)
66
+ file.is_a?(Pathname) ? file : Pathname.new(file)
67
+ end
68
+
69
+ def source_files?
70
+ !source_files.empty?
71
+ end
72
+
73
+ def file_missing?
74
+ !file.exist?
75
+ end
76
+
77
+ def source_files_newer?
78
+ source_files.map(&:mtime).max > file.mtime
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,22 @@
1
+ module Rack
2
+ class Pack
3
+ class Stylesheet < Package
4
+ def compress(source)
5
+ if defined?(Rainpress)
6
+ Rainpress.compress(source, compression_options)
7
+ elsif defined?(YUI) and defined?(YUI::CssCompressor)
8
+ YUI::CssCompressor.new(compression_options).compress(source)
9
+ else
10
+ source
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def compression_options(defaults = {})
17
+ return defaults unless Pack.options
18
+ defaults.merge Pack.options[:css_compression]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class Pack
3
+ VERSION = '0.2.1'
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rack/pack/version', __FILE__)
3
+ require 'bundler'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'rack-pack'
7
+ s.version = Rack::Pack::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = 'Pete Browne'
10
+ s.email = 'me@petebrowne.com'
11
+ s.homepage = 'http://rubygems.org/gems/rack-pack'
12
+ s.summary = 'Rack Middleware for packaging assets such as javascripts and stylesheets.'
13
+ s.description = 'Packages assets such as javascripts and stylesheets using a method inspired by Sass::Plugin. In development mode, the assets are packaged on each request. In production mode, the assets are packaged only one time.'
14
+
15
+ s.required_rubygems_version = '>= 1.3.6'
16
+ s.rubyforge_project = 'rack-pack'
17
+
18
+ s.add_dependency 'rack', '~> 1.2.1'
19
+ s.add_development_dependency 'rspec', '~> 2.0.0.beta.20'
20
+ s.add_development_dependency 'activesupport', '~> 3.0.0.rc2'
21
+ s.add_development_dependency 'test-construct', '~> 1.2.0'
22
+ s.add_development_dependency 'jsmin', '~> 1.0.1'
23
+ s.add_development_dependency 'packr', '~> 3.1.0'
24
+ s.add_development_dependency 'yui-compressor', '~> 0.9.1'
25
+ s.add_development_dependency 'closure-compiler', '~> 0.3.2'
26
+ s.add_development_dependency 'rainpress', '~> 1.0.0'
27
+
28
+ s.files = `git ls-files`.split("\n")
29
+ s.executables = `git ls-files`.split("\n").map{ |f| f =~ /^bin\/(.*)/ ? $1 : nil }.compact
30
+ s.require_path = 'lib'
31
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Pack::Javascript do
4
+ describe '#compile' do
5
+ it 'should by default strip the concatenated output' do
6
+ within_construct do |c|
7
+ c.file 'input-1.js', ' 1'
8
+ c.file 'input-2.js', '2 '
9
+
10
+ package = Rack::Pack::Javascript.new('output.js', 'input-*.js')
11
+ package.compile.should == '12'
12
+ end
13
+ end
14
+
15
+ describe 'with compression' do
16
+ before do
17
+ Rack::Pack.stub(:production? => true)
18
+ end
19
+
20
+ context 'when JSMin is required' do
21
+ it 'should compress using JSMin' do
22
+ reveal_const :JSMin do
23
+ within_construct do |c|
24
+ c.file 'input.js', 'function(number) { return number + 2; }'
25
+
26
+ package = Rack::Pack::Javascript.new('output.js', 'input.js')
27
+ package.compile.should == 'function(number){return number+2;}'
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'when packr is required' do
34
+ it 'should compress using Packr' do
35
+ reveal_const :Packr do
36
+ within_construct do |c|
37
+ c.file 'input.js', '1'
38
+
39
+ Packr.should_receive(:pack).with('1', :shrink_vars => true).and_return('1')
40
+ Rack::Pack::Javascript.new('output.js', 'input.js').compile
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ context 'when yui/compressor is required' do
47
+ it 'should compress using YUI::JavaScriptCompressor' do
48
+ reveal_const :YUI do
49
+ within_construct do |c|
50
+ c.file 'input.js', '1'
51
+
52
+ compressor = double(:yui_compressor)
53
+ compressor.should_receive(:compress).with('1').and_return('1')
54
+ YUI::JavaScriptCompressor.should_receive(:new).with(:munge => true).and_return(compressor)
55
+ Rack::Pack::Javascript.new('output.js', 'input.js').compile
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'when closure-compiler is required' do
62
+ it 'should compress using Closure::Compiler' do
63
+ reveal_const :Closure do
64
+ within_construct do |c|
65
+ c.file 'input.js', '1'
66
+
67
+ compressor = double(:closure_compiler)
68
+ compressor.should_receive(:compile).with('1').and_return('1')
69
+ Closure::Compiler.should_receive(:new).with({}).and_return(compressor)
70
+ Rack::Pack::Javascript.new('output.js', 'input.js').compile
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Pack::Package do
4
+ class MockPackage
5
+ attr_accessor :output_file, :source_files
6
+ def initialize(output_file, source_files)
7
+ @output_file = output_file
8
+ @source_files = source_files
9
+ end
10
+ end
11
+
12
+ def file_double(modified_at)
13
+ double(:file, :mtime => modified_at, :exist? => true, :is_a? => true)
14
+ end
15
+
16
+ describe '.register' do
17
+ it 'will store the package class for the given extension' do
18
+ Rack::Pack::Package.register :mock, MockPackage
19
+ Rack::Pack::Package.mappings['mock'].should == MockPackage
20
+ end
21
+ end
22
+
23
+ describe '.mappings' do
24
+ it 'should by default have a Javascript mapping' do
25
+ Rack::Pack::Package.mappings['js'].should == Rack::Pack::Javascript
26
+ end
27
+
28
+ it 'should by default have a Stylesheet mapping' do
29
+ Rack::Pack::Package.mappings['css'].should == Rack::Pack::Stylesheet
30
+ end
31
+ end
32
+
33
+ describe '.[]' do
34
+ it 'should find the correct package class for the given file' do
35
+ Rack::Pack::Package['some/javascript.js'].should == Rack::Pack::Javascript
36
+ end
37
+
38
+ it 'should default to the base Package' do
39
+ Rack::Pack::Package['missing.ext'].should == Rack::Pack::Package
40
+ end
41
+ end
42
+
43
+ describe '#file' do
44
+ context 'when initialized with a string' do
45
+ it 'should convert it to a pathname' do
46
+ package = Rack::Pack::Package.new('hello', [])
47
+ package.file.should == Pathname.new('hello')
48
+ end
49
+ end
50
+
51
+ describe '#source_files'
52
+ context 'when initialized with an array of strings' do
53
+ it 'should convert them to pathnames' do
54
+ package = Rack::Pack::Package.new('', %w(file-1 file-2))
55
+ package.source_files.should =~ [ Pathname.new('file-1'), Pathname.new('file-2') ]
56
+ end
57
+ end
58
+
59
+ context 'when initialized with a string' do
60
+ it 'should treat it as a Dir glob' do
61
+ within_construct do |c|
62
+ c.file 'dir/file-1.js'
63
+ c.file 'dir/file-2.js'
64
+
65
+ package = Rack::Pack::Package.new('', 'dir/*.js')
66
+ package.source_files.should =~ [ Pathname.new('dir/file-1.js'), Pathname.new('dir/file-2.js') ]
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#stale?' do
73
+ context 'if the packed file is current' do
74
+ subject do
75
+ now = Time.now
76
+ package = Rack::Pack::Package.new('', [
77
+ file_double(now),
78
+ file_double(1.week.ago),
79
+ file_double(2.weeks.ago)
80
+ ])
81
+ package.stub(:file => file_double(now))
82
+ package
83
+ end
84
+
85
+ it { should_not be_stale }
86
+ end
87
+
88
+ context 'if the packed file is old' do
89
+ subject do
90
+ package = Rack::Pack::Package.new('', [
91
+ file_double(Time.now),
92
+ file_double(1.week.ago),
93
+ file_double(2.weeks.ago)
94
+ ])
95
+ package.stub(:file => file_double(1.week.ago))
96
+ package
97
+ end
98
+
99
+ it { should be_stale }
100
+ end
101
+
102
+ context "if the packed file doesn't exist" do
103
+ subject do
104
+ Rack::Pack::Package.new(
105
+ 'missing/file',
106
+ [
107
+ file_double(Time.now),
108
+ file_double(1.week.ago),
109
+ file_double(2.weeks.ago)
110
+ ]
111
+ )
112
+ end
113
+
114
+ it { should be_stale }
115
+ end
116
+
117
+ context 'when given a glob string' do
118
+ it 'should check for new files' do
119
+ within_construct do |c|
120
+ c.file 'directory/file-1'
121
+
122
+ package = Rack::Pack::Package.new(
123
+ file_double(2.weeks.ago),
124
+ 'directory/**/*'
125
+ )
126
+ package.stale?
127
+
128
+ c.file 'directory/file-2'
129
+ package.should be_stale
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ describe '#update' do
136
+ it 'should combine the files and write it to the output file' do
137
+ within_construct do |c|
138
+ to_file = c.file('packed-file', 'Stale Content')
139
+ package = Rack::Pack::Package.new(
140
+ to_file,
141
+ [
142
+ c.file('file-1', '1'),
143
+ c.file('file-2', '2'),
144
+ c.file('file-3', '3')
145
+ ]
146
+ )
147
+ package.update
148
+ to_file.read.should == '123'
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#compress?' do
154
+ subject { Rack::Pack::Package.new('', '') }
155
+ it { should_not be_compress }
156
+
157
+ context 'in a production environment' do
158
+ before { Rack::Pack.stub(:production? => true) }
159
+ it { should be_compress }
160
+ end
161
+
162
+ context 'with the :always_compress option' do
163
+ before { Rack::Pack.stub(:options => double(:options, :[] => true)) }
164
+ it { should be_compress }
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Pack::Stylesheet do
4
+ describe '#compile' do
5
+ it 'should by default strip the concatenated output' do
6
+ within_construct do |c|
7
+ c.file 'input-1.css', ' 1'
8
+ c.file 'input-2.css', '2 '
9
+
10
+ package = Rack::Pack::Stylesheet.new('output.css', 'input-*.css')
11
+ package.compile.should == '12'
12
+ end
13
+ end
14
+
15
+ describe 'with compression' do
16
+ before do
17
+ Rack::Pack.stub(:production? => true)
18
+ end
19
+
20
+ context 'when yui/compressor is required' do
21
+ it 'should compress using YUI::JavaScriptCompressor' do
22
+ reveal_const :YUI do
23
+ within_construct do |c|
24
+ c.file 'input.css', '1'
25
+
26
+ compressor = double(:yui_compressor)
27
+ compressor.should_receive(:compress).with('1').and_return('1')
28
+ YUI::CssCompressor.should_receive(:new).with({}).and_return(compressor)
29
+ Rack::Pack::Stylesheet.new('output.css', 'input.css').compile
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'when rainpress is required' do
36
+ it 'should compress using Rainpress' do
37
+ reveal_const :Rainpress do
38
+ within_construct do |c|
39
+ c.file 'input.css', '1'
40
+
41
+ Rainpress.should_receive(:compress).with('1', {}).and_return('')
42
+ Rack::Pack::Stylesheet.new('output.css', 'input.css').compile
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,185 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Pack do
4
+ def build_app(*args)
5
+ Rack::Builder.app do
6
+ use Rack::Lint
7
+ use Rack::Pack, *args
8
+ run lambda { |env| [ 200, { 'Content-Type' => 'text/html' }, [ 'Hello World!' ] ] }
9
+ end
10
+ end
11
+
12
+ def request_for(url = '/')
13
+ Rack::MockRequest.env_for(url)
14
+ end
15
+ alias_method :request, :request_for
16
+
17
+ context 'with default settings' do
18
+ it 'should pack javascripts' do
19
+ within_construct do |c|
20
+ c.file 'vendor/javascripts/file-1.js', '1'
21
+ c.file 'javascripts/file-2.js', '2'
22
+ c.file 'javascripts/file-3.js', '3'
23
+
24
+ @app = build_app
25
+ @app.call(request)
26
+ File.read('public/javascripts/application.js').should == '123'
27
+ end
28
+ end
29
+
30
+ it 'should pack stylesheets' do
31
+ within_construct do |c|
32
+ c.file 'vendor/stylesheets/file-1.css', '1'
33
+ c.file 'stylesheets/file-2.css', '2'
34
+ c.file 'stylesheets/file-3.css', '3'
35
+
36
+ @app = build_app
37
+ @app.call(request)
38
+ File.read('public/stylesheets/application.css').should == '123'
39
+ end
40
+ end
41
+
42
+ it 'should not compress the packages' do
43
+ reveal_const :JSMin do
44
+ within_construct do |c|
45
+ c.file 'app/javascripts/file.js', '1'
46
+
47
+ JSMin.should_not_receive(:minify)
48
+ @app = build_app
49
+ @app.call(request)
50
+ end
51
+ end
52
+ end
53
+
54
+ context 'on next request' do
55
+ context 'with updates' do
56
+ it 'should re-pack the package' do
57
+ within_construct do |c|
58
+ c.file 'app/stylesheets/file-1.css', '1'
59
+ c.file 'app/stylesheets/file-2.css', '2'
60
+
61
+ @app = build_app
62
+ @app.call(request)
63
+ File.read('public/stylesheets/application.css').should == '12'
64
+
65
+ sleep 1
66
+ c.file 'app/stylesheets/file-2.css', '3'
67
+ @app.call(request)
68
+ File.read('public/stylesheets/application.css').should == '13'
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'without updates' do
74
+ it 'should not re-pack the package' do
75
+ within_construct do |c|
76
+ c.file 'app/stylesheets/file-1.css', '1'
77
+ c.file 'app/stylesheets/file-2.css', '2'
78
+
79
+ @app = build_app
80
+ @app.call(request)
81
+ File.read('public/stylesheets/application.css').should == '12'
82
+ original_mtime = File.mtime('public/stylesheets/application.css')
83
+
84
+ sleep 1
85
+ @app.call(request)
86
+ File.mtime('public/stylesheets/application.css').should == original_mtime
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'with some custom packages' do
94
+ it 'should pack the files' do
95
+ within_construct do |c|
96
+ c.file 'vendor/javascripts/file-1.js', '1'
97
+ c.file 'app/javascripts/file-2.js', '2'
98
+
99
+ @app = build_app 'main.js' => %w(vendor/javascripts/file-1.js app/javascripts/file-2.js)
100
+ @app.call(request)
101
+ File.read('public/main.js').should == '12'
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'with :always_compress on' do
107
+ it 'should compress the packages' do
108
+ reveal_const :JSMin do
109
+ within_construct do |c|
110
+ c.file 'app/javascripts/file.js', '1'
111
+
112
+ JSMin.should_receive(:minify).with('1').and_return('1')
113
+ @app = build_app :always_compress => true
114
+ @app.call(request)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'with a default package set to nil' do
121
+ it 'should not pack the files' do
122
+ within_construct do |c|
123
+ c.file 'vendor/javascripts/file-1.js', '1'
124
+
125
+ @app = build_app 'javascripts/application.js' => nil
126
+ @app.call(request)
127
+ File.exist?('public/javascripts/application.js').should_not be_true
128
+ end
129
+ end
130
+ end
131
+
132
+ context 'in a production environment' do
133
+ before do
134
+ Rails = double('rails', :env => double('env', :to_s => 'production'))
135
+ end
136
+
137
+ after do
138
+ Object.send(:remove_const, :Rails)
139
+ end
140
+
141
+ it 'should pack files only one time' do
142
+ within_construct do |c|
143
+ c.file 'app/javascripts/file.js', '1'
144
+
145
+ @app = build_app
146
+ @app.call(request)
147
+
148
+ sleep 1
149
+ c.file 'app/javascripts/file.js', '2'
150
+ @app.call(request)
151
+ File.read('public/javascripts/application.js').should == '1'
152
+ end
153
+ end
154
+
155
+ context 'with always_update option as true' do
156
+ it 'should pack the files on each request' do
157
+ within_construct do |c|
158
+ c.file 'app/javascripts/file.js', '1'
159
+
160
+ @app = build_app :always_update => true
161
+ @app.call(request)
162
+
163
+ sleep 1
164
+ c.file 'app/javascripts/file.js', '2'
165
+ @app.call(request)
166
+ File.read('public/javascripts/application.js').should == '2'
167
+ end
168
+ end
169
+ end
170
+
171
+ context 'with javascript compression options' do
172
+ it 'should pass the options to the javascript compressor' do
173
+ reveal_const :Packr do
174
+ within_construct do |c|
175
+ c.file 'app/javascripts/file.js', '1'
176
+
177
+ Packr.should_receive(:pack).with('1', :shrink_vars => false).and_return('1')
178
+ @app = build_app :js_compression => { :shrink_vars => false }
179
+ @app.call(request)
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,41 @@
1
+ lib = File.expand_path('../../lib', __FILE__)
2
+ $:.unshift(lib) unless $:.include?(lib)
3
+
4
+ require 'rubygems'
5
+ require 'rspec'
6
+ require 'construct'
7
+ # To get 2.weeks.ago syntax...
8
+ require 'active_support/core_ext/time/acts_like'
9
+ require 'active_support/core_ext/time/calculations'
10
+ require 'active_support/core_ext/numeric/time'
11
+ require 'jsmin'
12
+ require 'packr'
13
+ require 'yui/compressor'
14
+ require 'closure-compiler'
15
+ require 'rainpress'
16
+ require 'rack/pack'
17
+
18
+ $hidden_consts = {}
19
+ [ :JSMin, :Packr, :YUI, :Closure, :Rainpress ].each do |const|
20
+ $hidden_consts[const] = Object.const_get(const)
21
+ Object.send :remove_const, const
22
+ end
23
+
24
+ RSpec.configure do |config|
25
+ config.include Construct::Helpers
26
+
27
+ config.after do
28
+ Rack::Pack.packages = nil
29
+ Rack::Pack.options = nil
30
+ Rack::Pack.environment = nil
31
+ end
32
+
33
+ def reveal_const(const)
34
+ begin
35
+ Object.const_set const, $hidden_consts[const]
36
+ yield
37
+ ensure
38
+ Object.send :remove_const, const
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,232 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-pack
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 1
10
+ version: 0.2.1
11
+ platform: ruby
12
+ authors:
13
+ - Pete Browne
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-29 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 29
28
+ segments:
29
+ - 1
30
+ - 2
31
+ - 1
32
+ version: 1.2.1
33
+ requirement: *id001
34
+ name: rack
35
+ prerelease: false
36
+ type: :runtime
37
+ - !ruby/object:Gem::Dependency
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 62196427
44
+ segments:
45
+ - 2
46
+ - 0
47
+ - 0
48
+ - beta
49
+ - 20
50
+ version: 2.0.0.beta.20
51
+ requirement: *id002
52
+ name: rspec
53
+ prerelease: false
54
+ type: :development
55
+ - !ruby/object:Gem::Dependency
56
+ version_requirements: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 977940607
62
+ segments:
63
+ - 3
64
+ - 0
65
+ - 0
66
+ - rc2
67
+ version: 3.0.0.rc2
68
+ requirement: *id003
69
+ name: activesupport
70
+ prerelease: false
71
+ type: :development
72
+ - !ruby/object:Gem::Dependency
73
+ version_requirements: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ hash: 31
79
+ segments:
80
+ - 1
81
+ - 2
82
+ - 0
83
+ version: 1.2.0
84
+ requirement: *id004
85
+ name: test-construct
86
+ prerelease: false
87
+ type: :development
88
+ - !ruby/object:Gem::Dependency
89
+ version_requirements: &id005 !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ~>
93
+ - !ruby/object:Gem::Version
94
+ hash: 21
95
+ segments:
96
+ - 1
97
+ - 0
98
+ - 1
99
+ version: 1.0.1
100
+ requirement: *id005
101
+ name: jsmin
102
+ prerelease: false
103
+ type: :development
104
+ - !ruby/object:Gem::Dependency
105
+ version_requirements: &id006 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 3
113
+ - 1
114
+ - 0
115
+ version: 3.1.0
116
+ requirement: *id006
117
+ name: packr
118
+ prerelease: false
119
+ type: :development
120
+ - !ruby/object:Gem::Dependency
121
+ version_requirements: &id007 !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ~>
125
+ - !ruby/object:Gem::Version
126
+ hash: 57
127
+ segments:
128
+ - 0
129
+ - 9
130
+ - 1
131
+ version: 0.9.1
132
+ requirement: *id007
133
+ name: yui-compressor
134
+ prerelease: false
135
+ type: :development
136
+ - !ruby/object:Gem::Dependency
137
+ version_requirements: &id008 !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ~>
141
+ - !ruby/object:Gem::Version
142
+ hash: 23
143
+ segments:
144
+ - 0
145
+ - 3
146
+ - 2
147
+ version: 0.3.2
148
+ requirement: *id008
149
+ name: closure-compiler
150
+ prerelease: false
151
+ type: :development
152
+ - !ruby/object:Gem::Dependency
153
+ version_requirements: &id009 !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ~>
157
+ - !ruby/object:Gem::Version
158
+ hash: 23
159
+ segments:
160
+ - 1
161
+ - 0
162
+ - 0
163
+ version: 1.0.0
164
+ requirement: *id009
165
+ name: rainpress
166
+ prerelease: false
167
+ type: :development
168
+ description: Packages assets such as javascripts and stylesheets using a method inspired by Sass::Plugin. In development mode, the assets are packaged on each request. In production mode, the assets are packaged only one time.
169
+ email: me@petebrowne.com
170
+ executables: []
171
+
172
+ extensions: []
173
+
174
+ extra_rdoc_files: []
175
+
176
+ files:
177
+ - .gitignore
178
+ - .rspec
179
+ - Gemfile
180
+ - LICENSE
181
+ - README.md
182
+ - Rakefile
183
+ - lib/rack-pack.rb
184
+ - lib/rack/pack.rb
185
+ - lib/rack/pack/javascript.rb
186
+ - lib/rack/pack/package.rb
187
+ - lib/rack/pack/stylesheet.rb
188
+ - lib/rack/pack/version.rb
189
+ - rack-pack.gemspec
190
+ - spec/rack/pack/javascript_spec.rb
191
+ - spec/rack/pack/package_spec.rb
192
+ - spec/rack/pack/stylesheet_spec.rb
193
+ - spec/rack/pack_spec.rb
194
+ - spec/spec_helper.rb
195
+ has_rdoc: true
196
+ homepage: http://rubygems.org/gems/rack-pack
197
+ licenses: []
198
+
199
+ post_install_message:
200
+ rdoc_options: []
201
+
202
+ require_paths:
203
+ - lib
204
+ required_ruby_version: !ruby/object:Gem::Requirement
205
+ none: false
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ hash: 3
210
+ segments:
211
+ - 0
212
+ version: "0"
213
+ required_rubygems_version: !ruby/object:Gem::Requirement
214
+ none: false
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ hash: 23
219
+ segments:
220
+ - 1
221
+ - 3
222
+ - 6
223
+ version: 1.3.6
224
+ requirements: []
225
+
226
+ rubyforge_project: rack-pack
227
+ rubygems_version: 1.3.7
228
+ signing_key:
229
+ specification_version: 3
230
+ summary: Rack Middleware for packaging assets such as javascripts and stylesheets.
231
+ test_files: []
232
+