asset_bundler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ public/*
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ AssetBundler
2
+ ============
3
+
4
+ A simple asset bundling solution for Sinatra/Rack apps. Adapted from asset bundling functionality in ActionPack.
5
+
6
+ Allows you to cache multiple javascripts or stylesheets into one file, so that scripts/styles can be downloaded
7
+ with fewer HTTP connections. Bundling will only happen when ENV['RACK_ENV'] is "production" or "staging" (other
8
+ bundling environments can be added via AssetBundler.bundle_environments).
9
+
10
+ Last modified timestamps are added as a param to outputted script/style sources, so that far future expires headers
11
+ can be leveraged. The [asset_timestamps_cache](http://github.com/gbuesing/asset_timestamps_cache) gem is a required dependency.
12
+
13
+
14
+ ERB Examples:
15
+
16
+ <= javascript_include_tag "/scripts/one.js", "/scripts/two.js", :cache => "/scripts/all.js" %>
17
+
18
+ <= stylesheet_link_tag "/styles/one.css", "/styles/two.css", :cache => "/styles/all.css" %>
19
+
20
+
21
+ output the following tags:
22
+
23
+ <script type="text/javascript" src="/scripts/all.js?1267564623"></script>
24
+
25
+ <link rel="stylesheet" href="/styles/all.css?1267564678" type="text/css">
26
+
27
+
28
+ and creates the following files on first invocation (in the project root):
29
+
30
+ "public/scripts/all.js"
31
+
32
+ "public/styles/all.css"
33
+
34
+
35
+ Sinatra setup example:
36
+
37
+ class MyApp < Sinatra::Base
38
+ helper AssetBundler::ViewHelper
39
+
40
+ # etc...
41
+ end
42
+
43
+
44
+ Adding additional bundling environments:
45
+
46
+ AssetBundler.bundle_environments << "staging2"
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['test/test*.rb']
6
+ t.verbose = true
7
+ end
8
+ task :default => :test
9
+
10
+ begin
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |gemspec|
13
+ gemspec.name = "asset_bundler"
14
+ gemspec.summary = "A simple asset bundling solution."
15
+ gemspec.description = "Adapted from asset bundling functionality in ActionPack."
16
+ gemspec.email = "gbuesing@gmail.com"
17
+ gemspec.homepage = "http://github.com/gbuesing/asset_bundler"
18
+ gemspec.authors = ["Geoff Buesing"]
19
+ gemspec.add_dependency('asset_timestamps_cache', '>= 0.1.1')
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: gem install jeweler"
23
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,73 @@
1
+ require 'asset_timestamps_cache'
2
+ require 'fileutils'
3
+
4
+ module AssetBundler
5
+
6
+ # Mix this in to view context for convenient access
7
+ module ViewHelper
8
+ def javascript_include_tag(*args)
9
+ AssetBundler.javascript_include_tag(*args)
10
+ end
11
+
12
+ def stylesheet_link_tag(*args)
13
+ AssetBundler.stylesheet_link_tag(*args)
14
+ end
15
+ end
16
+
17
+ @@bundle_environments = ['production', 'staging']
18
+
19
+ # Expose @@bundle_environments via class method so that you can add other bundle enviroments:
20
+ #
21
+ # AssetBundler.bundle_environments << "staging2"
22
+ def self.bundle_environments
23
+ @@bundle_environments
24
+ end
25
+
26
+ def self.javascript_include_tag(*args)
27
+ bundled_asset_include_tag(*args) do |cache|
28
+ %{<script type="text/javascript" src="#{AssetTimestampsCache.timestamped_asset_path(cache)}"></script>}
29
+ end
30
+ end
31
+
32
+ def self.stylesheet_link_tag(*args)
33
+ bundled_asset_include_tag(*args) do |cache|
34
+ %{<link rel="stylesheet" href="#{AssetTimestampsCache.timestamped_asset_path(cache)}" type="text/css">}
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def self.bundled_asset_include_tag(*args)
41
+ opts = args.last.is_a?(Hash) ? args.pop : {}
42
+ cache = opts.delete(:cache)
43
+
44
+ if cache && @@bundle_environments.include?(ENV['RACK_ENV'])
45
+ file_path = asset_file_path(cache)
46
+ unless File.exist?(file_path)
47
+ write_asset_file_contents(file_path, args)
48
+ end
49
+ yield cache
50
+ else
51
+ args.map {|f| yield f}.join("\n")
52
+ end
53
+ end
54
+
55
+ def self.join_asset_file_contents(paths)
56
+ paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
57
+ end
58
+
59
+ def self.write_asset_file_contents(joined_asset_path, asset_paths)
60
+ FileUtils.mkdir_p(File.dirname(joined_asset_path))
61
+ File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
62
+
63
+ # Set mtime to the latest of the combined files to allow for
64
+ # consistent ETag without a shared filesystem.
65
+ mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
66
+ File.utime(mt, mt, joined_asset_path)
67
+ end
68
+
69
+ def self.asset_file_path(path)
70
+ File.join('public', path.to_s)
71
+ end
72
+
73
+ end
Binary file
@@ -0,0 +1,2 @@
1
+ // script one
2
+ function one() {};
@@ -0,0 +1,2 @@
1
+ // script three
2
+ function three() {};
@@ -0,0 +1,2 @@
1
+ // script two
2
+ function two() {};
@@ -0,0 +1,2 @@
1
+ /* style one */
2
+ #one {}
@@ -0,0 +1,2 @@
1
+ /* style three */
2
+ #three {}
@@ -0,0 +1,2 @@
1
+ /* style two */
2
+ #two {}
@@ -0,0 +1,111 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'asset_bundler'
4
+
5
+ FileUtils.mkdir_p 'public'
6
+ FileUtils.cp_r 'test/fixtures/public/scripts', 'public'
7
+ FileUtils.cp_r 'test/fixtures/public/styles', 'public'
8
+
9
+ class TestAssetBundler < Test::Unit::TestCase
10
+
11
+ class Foo; include(AssetBundler::ViewHelper); end
12
+
13
+ def setup
14
+ @old_rack_env, ENV['RACK_ENV'] = ENV['RACK_ENV'], 'production'
15
+ AssetTimestampsCache.clear
16
+ FileUtils.mkdir_p 'public'
17
+ @scope = Foo.new
18
+ end
19
+
20
+ def teardown
21
+ ENV['RACK_ENV'] = @old_rack_env
22
+ end
23
+
24
+ def test_javascript_include_tag_html_output_without_cache
25
+ html = @scope.javascript_include_tag('/scripts/one.js', '/scripts/two.js', '/scripts/three.js')
26
+ expected = ['one', 'two', 'three'].map do |name|
27
+ mtime = File.mtime("public/scripts/#{name}.js").to_i
28
+ %(<script type="text/javascript" src="/scripts/#{name}.js?#{mtime}"></script>)
29
+ end.join("\n")
30
+ assert_equal expected, html
31
+ end
32
+
33
+ def test_stylesheet_link_tag_html_output_without_cache
34
+ html = @scope.stylesheet_link_tag('/styles/one.css', '/styles/two.css', '/styles/three.css')
35
+ expected = ['one', 'two', 'three'].map do |name|
36
+ mtime = File.mtime("public/styles/#{name}.css").to_i
37
+ %{<link rel="stylesheet" href="/styles/#{name}.css?#{mtime}" type="text/css">}
38
+ end.join("\n")
39
+ assert_equal expected, html
40
+ end
41
+
42
+ def test_javascript_include_tag_html_output_with_cache
43
+ html = @scope.javascript_include_tag('/scripts/one.js', '/scripts/two.js', '/scripts/three.js', :cache => '/scripts/all.js')
44
+ mtime = File.mtime("public/scripts/all.js").to_i
45
+ expected = %(<script type="text/javascript" src="/scripts/all.js?#{mtime}"></script>)
46
+ assert_equal expected, html
47
+ ensure
48
+ File.delete("public/scripts/all.js")
49
+ end
50
+
51
+ def test_stylesheet_link_tag_html_output_with_cache
52
+ html = @scope.stylesheet_link_tag('/styles/one.css', '/styles/two.css', '/styles/three.css', :cache => '/styles/all.css')
53
+ mtime = File.mtime("public/styles/all.css").to_i
54
+ expected = %{<link rel="stylesheet" href="/styles/all.css?#{mtime}" type="text/css">}
55
+ assert_equal expected, html
56
+ ensure
57
+ File.delete("public/styles/all.css")
58
+ end
59
+
60
+ def test_javascript_include_tag_html_output_with_cache_does_not_bundle_in_development
61
+ with_rack_env 'development' do
62
+ html = @scope.javascript_include_tag('/scripts/one.js', '/scripts/two.js', '/scripts/three.js', :cache => '/scripts/all.js')
63
+ expected = ['one', 'two', 'three'].map do |name|
64
+ mtime = File.mtime("public/scripts/#{name}.js").to_i
65
+ %(<script type="text/javascript" src="/scripts/#{name}.js?#{mtime}"></script>)
66
+ end.join("\n")
67
+ assert_equal expected, html
68
+ end
69
+ end
70
+
71
+ def test_stylesheet_link_tag_html_output_with_cache_does_not_bundle_in_development
72
+ with_rack_env 'development' do
73
+ html = @scope.stylesheet_link_tag('/styles/one.css', '/styles/two.css', '/styles/three.css', :cache => '/styles/all.css')
74
+ expected = ['one', 'two', 'three'].map do |name|
75
+ mtime = File.mtime("public/styles/#{name}.css").to_i
76
+ %{<link rel="stylesheet" href="/styles/#{name}.css?#{mtime}" type="text/css">}
77
+ end.join("\n")
78
+ assert_equal expected, html
79
+ end
80
+ end
81
+
82
+ def test_javascript_include_tag_html_output_with_cache_uses_latest_mtime
83
+ sleep 1
84
+ FileUtils.touch 'public/scripts/two.js'
85
+ @scope.javascript_include_tag('/scripts/one.js', '/scripts/two.js', '/scripts/three.js', :cache => '/scripts/all.js')
86
+ assert_equal File.mtime('public/scripts/two.js'), File.mtime('public/scripts/all.js')
87
+ assert File.mtime('public/scripts/one.js') < File.mtime('public/scripts/all.js')
88
+ assert File.mtime('public/scripts/three.js') < File.mtime('public/scripts/all.js')
89
+ ensure
90
+ File.delete("public/scripts/all.js")
91
+ end
92
+
93
+ def test_cached_javascript
94
+ @scope.javascript_include_tag('/scripts/one.js', '/scripts/two.js', '/scripts/three.js', :cache => '/scripts/all.js')
95
+ actual = File.read("public/scripts/all.js")
96
+ expected = ['one', 'two', 'three'].map do |name|
97
+ "// script #{name}\nfunction #{name}() {};"
98
+ end.join("\n\n")
99
+ assert_equal expected, actual
100
+ ensure
101
+ File.delete("public/scripts/all.js")
102
+ end
103
+
104
+ private
105
+ def with_rack_env(val)
106
+ old_env, ENV['RACK_ENV'] = ENV['RACK_ENV'], val
107
+ yield
108
+ ensure
109
+ ENV['RACK_ENV'] = old_env
110
+ end
111
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asset_bundler
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Geoff Buesing
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-05 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: asset_timestamps_cache
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 1
30
+ - 1
31
+ version: 0.1.1
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: Adapted from asset bundling functionality in ActionPack.
35
+ email: gbuesing@gmail.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - README.md
42
+ files:
43
+ - .gitignore
44
+ - README.md
45
+ - Rakefile
46
+ - VERSION
47
+ - lib/asset_bundler.rb
48
+ - test/fixtures/.DS_Store
49
+ - test/fixtures/public/scripts/one.js
50
+ - test/fixtures/public/scripts/three.js
51
+ - test/fixtures/public/scripts/two.js
52
+ - test/fixtures/public/styles/one.css
53
+ - test/fixtures/public/styles/three.css
54
+ - test/fixtures/public/styles/two.css
55
+ - test/test_asset_bundler.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/gbuesing/asset_bundler
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.3.6
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: A simple asset bundling solution.
86
+ test_files:
87
+ - test/test_asset_bundler.rb