sinatra-bundles 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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