sinatra-bundles 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -3,7 +3,6 @@ sinatra-bundles
3
3
 
4
4
  An easy way to bundle CSS and Javascript assets in your sinatra application.
5
5
 
6
- * Tests: [http://runcoderun.com/darkhelmet/sinatra-bundles](http://runcoderun.com/darkhelmet/sinatra-bundles)
7
6
  * Documentation: [http://yardoc.org/docs/darkhelmet-sinatra-bundles](http://yardoc.org/docs/darkhelmet-sinatra-bundles)
8
7
 
9
8
  Usage
@@ -36,24 +35,35 @@ And include it in your app:
36
35
  'sinatra-bundles rocks!'
37
36
  end
38
37
 
39
- That sinatra is version 0.10.1, so you'll have to grab it from source and install it that way, since it's not out yet.
40
-
41
38
  Then in your view, you can use the view helpers to insert the proper script tags:
42
39
 
43
40
  = javascript_bundle_include_tag(:all)
44
41
  = stylesheet_bundle_link_tag(:all)
45
42
 
46
- All 6 of those files will be served up in 2 files, and they'll be compressed and have headers set for caching.
43
+ All 6 of those files will be served up in 2 files, and they'll be compressed and have headers and etags set for caching.
44
+
45
+ You can also use wildcard splats.
46
+
47
+ = javascript_bundle(:test, %w(test/*))
48
+
49
+ That will grab all files in the test directory.
50
+
51
+ = javascript_bundle(:test, %w(test/**/*))
52
+
53
+ That will grab all files under the test directory recursively. If you don't specify any files, it defaults to 'all files' recursively.
54
+
55
+ = javascript_bundle(:all)
47
56
 
48
57
  Configuration
49
58
  -------------
50
59
 
51
60
  The defaults are pretty good. In development/test mode:
52
61
 
53
- bundle_cache_time # => 60 * 60 * 24 * 365, or 1 year
54
- compress_bundles # => false
55
- cache_bundles # => false
56
- stamp_bundles # => true
62
+ bundle_cache_time # => 60 * 60 * 24 * 365, or 1 year (length of time a bundle will be cached for)
63
+ compress_bundles # => false (compress CSS and Javascript using packr and rainpress)
64
+ cache_bundles # => false (set caching headers)
65
+ stamp_bundles # => true (append a timestamp to the URL as a query param)
66
+ warm_bundle_cache # => false (generate bundle when it is defined)
57
67
 
58
68
  And in production mode, compression and caching are enabled
59
69
 
@@ -85,12 +95,10 @@ Check out the code for my blog for a real example: [darkblog on github](http://g
85
95
  What you Need
86
96
  -------------
87
97
 
88
- sinatra >= 0.10.1 (edge)
98
+ sinatra >= 1.0
89
99
  packr
90
100
  rainpress
91
101
 
92
- packr and rainpress are dependencies, but sinatra isn't, since version 0.10.1 isn't out yet, and you have to download the source manually.
93
-
94
102
  Note on Patches/Pull Requests
95
103
  -----------------------------
96
104
 
@@ -102,6 +110,13 @@ Note on Patches/Pull Requests
102
110
  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
103
111
  * Send me a pull request. Bonus points for topic branches.
104
112
 
113
+ Thanks!
114
+ -------
115
+
116
+ * [Patrick Hogan](http://github.com/pbhogan)
117
+ ** Etag support (with specs!)
118
+ ** Wildcard globbing
119
+
105
120
  Copyright
106
121
  ---------
107
122
 
data/Rakefile CHANGED
@@ -13,7 +13,7 @@ begin
13
13
  gem.add_dependency 'rainpress', '>= 0'
14
14
  gem.add_dependency 'packr', '>= 0'
15
15
  gem.add_dependency 'rack', '>= 1.0'
16
- gem.add_dependency 'sinatra', '>= 1.0.a'
16
+ gem.add_dependency 'sinatra', '>= 1.0'
17
17
  gem.add_development_dependency 'rspec', '>= 1.2.9'
18
18
  gem.add_development_dependency 'rack-test', '>= 0.5.3'
19
19
  gem.add_development_dependency 'yard', '>= 0'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.2.0
@@ -11,7 +11,7 @@ module Sinatra
11
11
  # @param [Symbol,String] key The bundle name
12
12
  # @param [Array(String)] files The list of filenames, without extension,
13
13
  # assumed to be in the public directory, under 'javascripts'
14
- def javascript_bundle(key, files)
14
+ def javascript_bundle(key, files = nil)
15
15
  javascript_bundles[key] = JavascriptBundle.new(self, files)
16
16
  end
17
17
 
@@ -20,7 +20,7 @@ module Sinatra
20
20
  # @param [Symbol,String] key The bundle name
21
21
  # @param [Array(String)] files The list of filenames, without extension,
22
22
  # assumed to be in the public directory, under 'stylesheets'
23
- def stylesheet_bundle(key, files)
23
+ def stylesheet_bundle(key, files = nil)
24
24
  stylesheet_bundles[key] = StylesheetBundle.new(self, files)
25
25
  end
26
26
 
@@ -34,6 +34,7 @@ module Sinatra
34
34
  app.disable(:compress_bundles)
35
35
  app.disable(:cache_bundles)
36
36
  app.enable(:stamp_bundles)
37
+ app.enable(:warm_bundle_cache)
37
38
 
38
39
  # Production defaults
39
40
  app.configure :production do
@@ -46,15 +47,21 @@ module Sinatra
46
47
  app.get('/stylesheets/bundles/:bundle.css') do |bundle|
47
48
  content_type('text/css')
48
49
  headers['Vary'] = 'Accept-Encoding'
49
- expires(options.bundle_cache_time, :public, :must_revalidate) if options.cache_bundles
50
- options.stylesheet_bundles[bundle.intern]
50
+ if options.cache_bundles
51
+ expires(options.bundle_cache_time, :public, :must_revalidate)
52
+ etag(options.stylesheet_bundles[bundle.intern].etag)
53
+ end
54
+ options.stylesheet_bundles[bundle.intern].content
51
55
  end
52
56
 
53
57
  app.get('/javascripts/bundles/:bundle.js') do |bundle|
54
58
  content_type('text/javascript; charset=utf-8')
55
59
  headers['Vary'] = 'Accept-Encoding'
56
- expires(options.bundle_cache_time, :public, :must_revalidate) if options.cache_bundles
57
- options.javascript_bundles[bundle.intern]
60
+ if options.cache_bundles
61
+ expires(options.bundle_cache_time, :public, :must_revalidate)
62
+ etag(options.javascript_bundles[bundle.intern].etag)
63
+ end
64
+ options.javascript_bundles[bundle.intern].content
58
65
  end
59
66
  end
60
67
  end
@@ -1,12 +1,34 @@
1
+ require 'digest'
2
+
1
3
  module Sinatra
2
4
  module Bundles
3
5
  # The base class for a bundle of files.
4
6
  # The developer user sinatra-bundles should
5
7
  # never have to deal with this directly
6
8
  class Bundle
7
- def initialize(app, files)
9
+ include Enumerable
10
+
11
+ def initialize(app, files = nil)
8
12
  @app = app
9
- @files = files
13
+ @files = Array.new
14
+ files ||= ['**/*']
15
+ files.each do |f|
16
+ full_path = path(f)
17
+ if File.file?(full_path)
18
+ @files << f
19
+ else
20
+ dir = File.dirname(full_path)
21
+ ext = File.extname(full_path)
22
+ Dir[full_path].each do |file|
23
+ if File.exists?(file)
24
+ file.chomp!(ext).gsub!("#{root}/", '')
25
+ @files << file
26
+ end
27
+ end
28
+ end
29
+ end
30
+ @files.uniq!
31
+ etag if @app.warm_bundle_cache
10
32
  end
11
33
 
12
34
  # Since we pass Bundles back as the body,
@@ -21,8 +43,34 @@ module Sinatra
21
43
  end
22
44
  end
23
45
 
24
- private
46
+ # Returns the bundled content.
47
+ # Cached in a local variable to prevent rebundling on future requests.
48
+ def content
49
+ rebundle if needs_rebundle?
50
+ @content ||= self.to_a.join
51
+ end
52
+
53
+ # Returns an etag for the bundled content.
54
+ # Cached in a local variable to prevent recomputing on future requests.
55
+ def etag
56
+ rebundle if needs_rebundle?
57
+ @etag ||= Digest::MD5.hexdigest(content)
58
+ end
59
+
60
+ # Returns true if the content needs to be rebundled
61
+ def needs_rebundle?
62
+ # Right now compression is the only option that requires rebundling
63
+ @options_hash != @app.compress_bundles
64
+ end
25
65
 
66
+ # Clear local variable caches effectively causing rebundling
67
+ def rebundle
68
+ @content = nil
69
+ @etag = nil
70
+ @options_hash = @app.compress_bundles
71
+ end
72
+
73
+ private
26
74
  # The timestamp of the bundle, which is the newest file in the bundle.
27
75
  #
28
76
  # @return [Integer] The timestamp of the bundle
@@ -14,8 +14,8 @@ module Sinatra
14
14
  #
15
15
  # @param [Symbol,String] bundle The bundle name
16
16
  # @return [String] HTML link tag
17
- def stylesheet_bundle_link_tag(bundle)
18
- options.stylesheet_bundles[bundle].to_html(bundle)
17
+ def stylesheet_bundle_link_tag(bundle, media = nil)
18
+ options.stylesheet_bundles[bundle].to_html(bundle, media)
19
19
  end
20
20
  end
21
21
  end
@@ -15,6 +15,11 @@ module Sinatra
15
15
 
16
16
  protected
17
17
 
18
+ # The root of these bundles, for path purposes
19
+ def root
20
+ File.join(@app.public, 'javascripts')
21
+ end
22
+
18
23
  # Compress Javascript
19
24
  #
20
25
  # @param [String] js The Javascript to compress
@@ -30,7 +35,7 @@ module Sinatra
30
35
  # assumed to be in the public directory, under 'javascripts'
31
36
  # @return [String] The full path to the file
32
37
  def path(filename)
33
- File.join(@app.public, 'javascripts', "#{filename}.js")
38
+ File.join(root, "#{filename}.js")
34
39
  end
35
40
  end
36
41
  end
@@ -9,12 +9,19 @@ module Sinatra
9
9
  #
10
10
  # @param [String] name The name of a bundle
11
11
  # @return [String] The HTML that can be inserted into the doc
12
- def to_html(name)
13
- "<link type='text/css' href='/stylesheets/bundles/#{name}.css#{@app.stamp_bundles ? "?#{stamp}" : ''}' rel='stylesheet' media='screen' />"
12
+ def to_html(name, media = nil)
13
+ media ||= :all
14
+ media = media.join(", ") if media.is_a? Array
15
+ "<link type='text/css' href='/stylesheets/bundles/#{name}.css#{@app.stamp_bundles ? "?#{stamp}" : ''}' rel='stylesheet' media='#{media}' />"
14
16
  end
15
17
 
16
18
  protected
17
19
 
20
+ # The root of these bundles, for path purposes
21
+ def root
22
+ File.join(@app.public, 'stylesheets')
23
+ end
24
+
18
25
  # Compress CSS
19
26
  #
20
27
  # @param [String] css The CSS to compress
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{sinatra-bundles}
8
- s.version = "0.1.3"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Daniel Huckstep"]
12
- s.date = %q{2010-02-28}
12
+ s.date = %q{2010-04-02}
13
13
  s.description = %q{Bundle CSS and Javascript assets to a single file, compress, and cache them for snappier web experiences.}
14
14
  s.email = %q{darkhelmet@darkhelmetlive.com}
15
15
  s.extra_rdoc_files = [
@@ -32,6 +32,7 @@ Gem::Specification.new do |s|
32
32
  "spec/app.rb",
33
33
  "spec/production_app.rb",
34
34
  "spec/public/javascripts/eval.js",
35
+ "spec/public/javascripts/splat/splat.js",
35
36
  "spec/public/javascripts/test1.js",
36
37
  "spec/public/javascripts/test2.js",
37
38
  "spec/public/stylesheets/test1.css",
@@ -60,7 +61,7 @@ Gem::Specification.new do |s|
60
61
  s.add_runtime_dependency(%q<rainpress>, [">= 0"])
61
62
  s.add_runtime_dependency(%q<packr>, [">= 0"])
62
63
  s.add_runtime_dependency(%q<rack>, [">= 1.0"])
63
- s.add_runtime_dependency(%q<sinatra>, [">= 1.0.a"])
64
+ s.add_runtime_dependency(%q<sinatra>, [">= 1.0"])
64
65
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
65
66
  s.add_development_dependency(%q<rack-test>, [">= 0.5.3"])
66
67
  s.add_development_dependency(%q<yard>, [">= 0"])
@@ -68,7 +69,7 @@ Gem::Specification.new do |s|
68
69
  s.add_dependency(%q<rainpress>, [">= 0"])
69
70
  s.add_dependency(%q<packr>, [">= 0"])
70
71
  s.add_dependency(%q<rack>, [">= 1.0"])
71
- s.add_dependency(%q<sinatra>, [">= 1.0.a"])
72
+ s.add_dependency(%q<sinatra>, [">= 1.0"])
72
73
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
73
74
  s.add_dependency(%q<rack-test>, [">= 0.5.3"])
74
75
  s.add_dependency(%q<yard>, [">= 0"])
@@ -77,7 +78,7 @@ Gem::Specification.new do |s|
77
78
  s.add_dependency(%q<rainpress>, [">= 0"])
78
79
  s.add_dependency(%q<packr>, [">= 0"])
79
80
  s.add_dependency(%q<rack>, [">= 1.0"])
80
- s.add_dependency(%q<sinatra>, [">= 1.0.a"])
81
+ s.add_dependency(%q<sinatra>, [">= 1.0"])
81
82
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
82
83
  s.add_dependency(%q<rack-test>, [">= 0.5.3"])
83
84
  s.add_dependency(%q<yard>, [">= 0"])
data/spec/app.rb CHANGED
@@ -7,5 +7,7 @@ class TestApp < Sinatra::Base
7
7
  register Sinatra::Bundles
8
8
 
9
9
  stylesheet_bundle(:test, %w(test1 test2))
10
- javascript_bundle(:test, %w(test1 test2 eval))
10
+ javascript_bundle(:test, %w(eval test1 test2))
11
+ javascript_bundle(:test2, %w(test*))
12
+ javascript_bundle(:all)
11
13
  end
@@ -0,0 +1,5 @@
1
+ function splat() {
2
+ alert('splat');
3
+ }
4
+
5
+ splat();
@@ -29,7 +29,7 @@ describe 'sinatra-bundles' do
29
29
  end
30
30
 
31
31
  before do
32
- @scripts = %w(test1 test2 eval).map do |name|
32
+ @scripts = %w(eval splat/splat test1 test2).map do |name|
33
33
  File.expand_path(File.join(File.dirname(__FILE__), 'public', 'javascripts', "#{name}.js"))
34
34
  end
35
35
 
@@ -76,9 +76,9 @@ describe 'sinatra-bundles' do
76
76
 
77
77
  context 'javascript bundles' do
78
78
  it 'should be able to set bundles' do
79
- app.javascript_bundle(:all, %w(foo bar baz))
79
+ app.javascript_bundle(:foo, %w(foo bar baz))
80
80
  app.javascript_bundles.should_not == {}
81
- app.javascript_bundles[:all].should be_a_kind_of(Sinatra::Bundles::Bundle)
81
+ app.javascript_bundles[:foo].should be_a_kind_of(Sinatra::Bundles::Bundle)
82
82
  end
83
83
 
84
84
  it 'should create a tag without a stamp if stamps are disabled' do
@@ -101,7 +101,7 @@ describe 'sinatra-bundles' do
101
101
 
102
102
  it 'should concat files in order with newlines including one at the end' do
103
103
  get '/javascripts/bundles/test.js'
104
- last_response.body.should == @scripts.map { |path| File.read(path) }.join("\n") + "\n"
104
+ last_response.body.should == @scripts.reject { |s| s.include?('splat') }.map { |path| File.read(path) }.join("\n") + "\n"
105
105
  end
106
106
 
107
107
  it 'should set cache headers' do
@@ -110,6 +110,7 @@ describe 'sinatra-bundles' do
110
110
  last_response.should be_ok
111
111
  last_response.headers['Vary'].should == 'Accept-Encoding'
112
112
  last_response.headers['Cache-Control'].should == 'public, must-revalidate, max-age=31536000'
113
+ last_response.headers['Etag'].should == '"4f647cfd3ab71800da1b0d0b127e2cd4"'
113
114
  end
114
115
 
115
116
  it 'should not shrink vars on javascript files that use eval' do
@@ -120,6 +121,19 @@ describe 'sinatra-bundles' do
120
121
  last_response.body.include?(Packr.pack(js)).should be_true
121
122
  last_response.body.include?(Packr.pack(js, :shrink_vars => true)).should be_false
122
123
  end
124
+
125
+ it 'should handle wildcard splats for bundles' do
126
+ get '/javascripts/bundles/test2.js'
127
+ last_response.should be_ok
128
+ last_response.body.include?('eval').should be_false
129
+ last_response.body.should == @scripts.reject { |s| s.match(/eval|splat/) }.map { |path| File.read(path) }.join("\n") + "\n"
130
+ end
131
+
132
+ it 'should handle the all scripts wildcard' do
133
+ get '/javascripts/bundles/all.js'
134
+ last_response.should be_ok
135
+ last_response.body.should == @scripts.map { |path| File.read(path) }.join("\n") + "\n"
136
+ end
123
137
  end
124
138
 
125
139
  context 'stylesheet bundles' do
@@ -133,13 +147,25 @@ describe 'sinatra-bundles' do
133
147
  app.new.instance_eval do
134
148
  options.disable(:stamp_bundles)
135
149
  stylesheet_bundle_link_tag(:test)
136
- end.should == "<link type='text/css' href='/stylesheets/bundles/test.css' rel='stylesheet' media='screen' />"
150
+ end.should == "<link type='text/css' href='/stylesheets/bundles/test.css' rel='stylesheet' media='all' />"
137
151
  end
138
152
 
139
153
  it 'should stamp bundles with the timestamp of the newest file in the bundle' do
140
154
  app.new.instance_eval do
141
155
  stylesheet_bundle_link_tag(:test)
142
- end.should == "<link type='text/css' href='/stylesheets/bundles/test.css?#{css_stamp(%w(test1 test2))}' rel='stylesheet' media='screen' />"
156
+ end.should == "<link type='text/css' href='/stylesheets/bundles/test.css?#{css_stamp(%w(test1 test2))}' rel='stylesheet' media='all' />"
157
+ end
158
+
159
+ it 'should create a tag with default media attribute set to all' do
160
+ app.new.instance_eval do
161
+ stylesheet_bundle_link_tag(:test)
162
+ end.include?("media='all'").should be_true
163
+ end
164
+
165
+ it 'should create a tag with specified media attributes' do
166
+ app.new.instance_eval do
167
+ stylesheet_bundle_link_tag(:test, [:screen, :print])
168
+ end.include?("media='screen, print'").should be_true
143
169
  end
144
170
 
145
171
  it 'should serve bundles' do
@@ -158,6 +184,7 @@ describe 'sinatra-bundles' do
158
184
  last_response.should be_ok
159
185
  last_response.headers['Vary'].should == 'Accept-Encoding'
160
186
  last_response.headers['Cache-Control'].should == 'public, must-revalidate, max-age=31536000'
187
+ last_response.headers['Etag'].should == '"6ecd0946412b76dcd1eaa341cdfefa8b"'
161
188
  end
162
189
  end
163
190
  end
data/spec/spec_helper.rb CHANGED
@@ -6,7 +6,12 @@ require 'spec'
6
6
  require 'spec/autorun'
7
7
  require 'rack/test'
8
8
 
9
- gem 'sinatra', '>= 1.0.a'
9
+ begin
10
+ require 'ruby-debug'
11
+ rescue LoadError
12
+ end
13
+
14
+ gem 'sinatra', '>= 1.0'
10
15
  require 'sinatra/base'
11
16
  require 'sinatra/bundles'
12
17
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-bundles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Huckstep
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-28 00:00:00 -07:00
12
+ date: 2010-04-02 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 1.0.a
53
+ version: "1.0"
54
54
  version:
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
@@ -107,6 +107,7 @@ files:
107
107
  - spec/app.rb
108
108
  - spec/production_app.rb
109
109
  - spec/public/javascripts/eval.js
110
+ - spec/public/javascripts/splat/splat.js
110
111
  - spec/public/javascripts/test1.js
111
112
  - spec/public/javascripts/test2.js
112
113
  - spec/public/stylesheets/test1.css