web_resource_bundler 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.bundle/config +2 -0
  2. data/.gitignore +21 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +10 -0
  5. data/README +118 -0
  6. data/Rakefile +29 -0
  7. data/VERSION +1 -0
  8. data/lib/web_resource_bundler.rb +195 -0
  9. data/lib/web_resource_bundler/content_management/block_data.rb +57 -0
  10. data/lib/web_resource_bundler/content_management/block_parser.rb +63 -0
  11. data/lib/web_resource_bundler/content_management/css_url_rewriter.rb +23 -0
  12. data/lib/web_resource_bundler/content_management/resource_file.rb +35 -0
  13. data/lib/web_resource_bundler/exceptions.rb +26 -0
  14. data/lib/web_resource_bundler/file_manager.rb +30 -0
  15. data/lib/web_resource_bundler/filters.rb +9 -0
  16. data/lib/web_resource_bundler/filters/base_filter.rb +28 -0
  17. data/lib/web_resource_bundler/filters/bundle_filter.rb +58 -0
  18. data/lib/web_resource_bundler/filters/bundle_filter/resource_packager.rb +49 -0
  19. data/lib/web_resource_bundler/filters/cdn_filter.rb +48 -0
  20. data/lib/web_resource_bundler/filters/image_encode_filter.rb +56 -0
  21. data/lib/web_resource_bundler/filters/image_encode_filter/css_generator.rb +85 -0
  22. data/lib/web_resource_bundler/filters/image_encode_filter/image_data.rb +51 -0
  23. data/lib/web_resource_bundler/rails_app_helpers.rb +65 -0
  24. data/lib/web_resource_bundler/settings.rb +46 -0
  25. data/lib/web_resource_bundler/web_resource_bundler_init.rb +17 -0
  26. data/spec/public/foo.css +4 -0
  27. data/spec/public/images/good.jpg +0 -0
  28. data/spec/public/images/logo.jpg +0 -0
  29. data/spec/public/images/sdfo.jpg +0 -0
  30. data/spec/public/images/too_big_image.jpg +0 -0
  31. data/spec/public/marketing.js +14 -0
  32. data/spec/public/salog20.js +6 -0
  33. data/spec/public/sample.css +6 -0
  34. data/spec/public/seal.js +10 -0
  35. data/spec/public/set_cookies.js +8 -0
  36. data/spec/public/styles/boo.css +4 -0
  37. data/spec/public/styles/for_import.css +7 -0
  38. data/spec/public/temp.css +1 -0
  39. data/spec/public/test.css +2 -0
  40. data/spec/sample_block_helper.rb +81 -0
  41. data/spec/spec_helper.rb +82 -0
  42. data/spec/web_resource_bundler/content_management/block_data_spec.rb +33 -0
  43. data/spec/web_resource_bundler/content_management/block_parser_spec.rb +100 -0
  44. data/spec/web_resource_bundler/content_management/css_url_rewriter_spec.rb +27 -0
  45. data/spec/web_resource_bundler/content_management/resource_file_spec.rb +37 -0
  46. data/spec/web_resource_bundler/file_manager_spec.rb +60 -0
  47. data/spec/web_resource_bundler/filters/bundle_filter/filter_spec.rb +40 -0
  48. data/spec/web_resource_bundler/filters/bundle_filter/resource_packager_spec.rb +41 -0
  49. data/spec/web_resource_bundler/filters/cdn_filter_spec.rb +76 -0
  50. data/spec/web_resource_bundler/filters/image_encode_filter/css_generator_spec.rb +104 -0
  51. data/spec/web_resource_bundler/filters/image_encode_filter/filter_spec.rb +73 -0
  52. data/spec/web_resource_bundler/filters/image_encode_filter/image_data_spec.rb +53 -0
  53. data/spec/web_resource_bundler/settings_spec.rb +45 -0
  54. data/spec/web_resource_bundler/web_resource_bundler_spec.rb +90 -0
  55. data/web_resource_bundler.gemspec +111 -0
  56. metadata +146 -0
data/.bundle/config ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_DISABLE_SHARED_GEMS: "1"
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gem "rspec", '1.3.1'
data/Gemfile.lock ADDED
@@ -0,0 +1,10 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ rspec (1.3.1)
5
+
6
+ PLATFORMS
7
+ ruby
8
+
9
+ DEPENDENCIES
10
+ rspec (= 1.3.1)
data/README ADDED
@@ -0,0 +1,118 @@
1
+ =================== WebResourceBundler gem README ============================
2
+
3
+ Content:
4
+ 1. Purpose
5
+ 2. Functional description
6
+ 3. Installation
7
+ 4. Usage
8
+
9
+ =================== 1. Purpose ===============================================
10
+
11
+ The main purpose of WebResourceBundler gem is to minimize request & response
12
+ round-trips count. This could be done by bundling particular resource (css or js) in
13
+ one file. Encoding images in base64 and putting then in css directly.
14
+
15
+ =================== 2. Functional description ================================
16
+
17
+ WebResourceBundler parse your head html block, finding all css and js resource
18
+ files.
19
+ It can bundle resource of particular type in one single file. Base64 filter
20
+ encodes images in base64 putting them in css directly. Separate files for IE
21
+ and other browsers created. Conditional comments (like <!--[if IE 6]>) also
22
+ supported. You can use external image hosts to server images in css:
23
+ cdn filter can rewrite image urls for you. Resulted filename is a md5(filenames.sort)
24
+
25
+ =================== 3. Installation ==========================================
26
+
27
+ gem install web_resource_bundler
28
+
29
+ It's pretty easy, yeah?
30
+
31
+ =================== 4. Usage =================================================
32
+ Firstly you should create your settings file in config dir.
33
+ You can set separate settings for each environment
34
+
35
+ -------------- config/web_resource_bundler.yml --------------
36
+ development:
37
+ :base64_filter:
38
+ :use: true
39
+ :max_image_size: 23
40
+ :protocol: http
41
+ :domain: localhost:3000
42
+ :bundle_filter:
43
+ :use: true
44
+ :cdn_filter:
45
+ :use: true
46
+ :http_hosts: ['http://localhost:3000']
47
+ :https_hosts: ['https://localhost:3000']
48
+ ------------------------------------------------------------
49
+
50
+ Then you should create initializer file in
51
+ /path/to/your/rails_app/config/initializers/ directory
52
+ Let's say it will be web_resource_bundler_init.rb
53
+ Then you should put content like this in it.
54
+
55
+ ------------- config/initializers/web_resource_bundler_init.rb ---------------
56
+ require 'web_resource_bundler'
57
+ require 'yaml'
58
+ root_dir = Rails.root #or RAILS_ROOT if you are using older rails version than 3
59
+ environment = Rails.env #or RAILS_ENV in case rails <= 2.3
60
+ settings = { }
61
+ settings_file_path = File.join(root_dir, 'config', 'web_resource_bundler.yml')
62
+ if File.exist?(settings_file_path)
63
+ settings_file = File.open(settings_file_path)
64
+ all_settings = YAML::load(settings_file)
65
+ if all_settings[environment]
66
+ settings = all_settings[environment]
67
+ settings[:resource_dir] = File.join(root_dir, 'public')
68
+ end
69
+ end
70
+
71
+ WebResourceBundler::Bundler.instance.set_settings(settings)
72
+ ActionView::Base.send(:include, WebResourceBundler::RailsAppHelpers)
73
+ ------------------------------------------------------------------------------
74
+
75
+ Now in your view files you can call web_resource_bundler_process helper like this:
76
+
77
+ <head>
78
+ <% web_resource_bundler_process do %>
79
+ <%= stylesheet_link_tag :scaffold %>
80
+ <%= javascript_include_tag :defaults %>
81
+ <link type="text/css" rel="stylesheet" href="/stylesheets/somestyle.css"/>
82
+ <%=yield :head %>
83
+ <!--[if lte IE 7]>
84
+ <link type="text/css" rel="stylesheet" href="/stylesheets/ie7fix.css"/>
85
+ <link type="text/css" rel="stylesheet" href="/stylesheets/pngfix.css"/>
86
+ <![endif]-->
87
+
88
+ <% end %>
89
+ </head>
90
+ Notice:
91
+
92
+ For Rails < 3
93
+ you should use <% web_resource_bundler_process do %>
94
+
95
+ And For Rails >= 3
96
+ use <%= web_resource_bundler_process do %>
97
+
98
+
99
+ And as result you'll have
100
+
101
+
102
+ <link href="/cache/base64_style_d880a502addaa493b889c0970616430b.css?1290594873" media="screen" rel="stylesheet" type="text/css" />
103
+ <script src="/cache/script_275d311037da40e9c9b8c919a8c08b55.js?1290594873" type="text/javascript"></script>
104
+
105
+ <!--[if lte IE 7]>
106
+ <link href="/cache/base64_ie_style_d880a502addaa493b889c0970616430b.css?1290594873" media="screen" rel="stylesheet" type="text/css" />
107
+ <![endif]-->
108
+
109
+ <!--[if lte IE 7]>
110
+ <link type="text/css" rel="stylesheet" href="/cache/base64_style_ad801w02addaa493b889c0970616430b"/>
111
+ <![endif]-->
112
+
113
+ !!!
114
+ Don't forget to clean your cache directory after deploy to clean old bundles
115
+
116
+
117
+ To disable bundling and see raw results add no_bundler param
118
+ mysite.com/?no_bundler=1
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "web_resource_bundler"
9
+ gem.summary = %Q{lib for css and js content bundling and managment}
10
+ gem.description = %Q{this lib could bundle you css/js files in single file, encode images in base64, rewrite images urls to your cdn hosts}
11
+ gem.email = "anotheroneman@yahoo.com"
12
+ gem.homepage = "http://github.com/gregolsen/web-bundler"
13
+ gem.authors = ["gregolsen"]
14
+ gem.add_development_dependency "rspec", "1.3.1"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ namespace :spec do
23
+
24
+ desc "Run specs with RCov"
25
+ Spec::Rake::SpecTask.new('rcov' ) do |t|
26
+ t.spec_files = FileList['spec/**/*_spec.rb' ]
27
+ t.rcov = true
28
+ end
29
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.13
@@ -0,0 +1,195 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'web_resource_bundler')
2
+ require 'content_management/block_parser'
3
+ require 'content_management/block_data'
4
+ require 'content_management/css_url_rewriter'
5
+ require 'content_management/resource_file'
6
+
7
+ require 'settings'
8
+ require 'file_manager'
9
+ require 'logger'
10
+ require 'filters'
11
+ require 'exceptions'
12
+ require 'rails_app_helpers'
13
+ require 'yaml'
14
+ require 'singleton'
15
+
16
+ module WebResourceBundler
17
+ class Bundler
18
+ include Singleton
19
+
20
+ attr_reader :settings, :settings_correct, :filters
21
+ @@logger = nil
22
+ def self.logger
23
+ @@logger
24
+ end
25
+
26
+ def initialize
27
+ @filters = {}
28
+ @settings = nil
29
+ @file_manager = nil
30
+ @parser = BlockParser.new
31
+ @@logger = nil
32
+ @settings_correct = false
33
+ end
34
+
35
+ #could be used also when settings are different on each request
36
+ def set_settings(settings)
37
+ #all methods user call from rails should not raise any exception
38
+ begin
39
+ @settings = Settings.new settings
40
+ if @settings.resource_dir
41
+ @@logger = create_logger(@settings)
42
+ unless @settings.cache_dir
43
+ @settings.cache_dir = 'cache'
44
+ end
45
+ #if file manager is nil it should be created
46
+ unless @file_manager
47
+ @file_manager = FileManager.new(@settings.resource_dir, @settings.cache_dir)
48
+ else
49
+ #if manager already exist than its settings chaged
50
+ @file_manager.resource_dir, @file_manager.cache_dir = @settings.resource_dir, @settings.cache_dir
51
+ end
52
+ set_filters(@settings, @file_manager)
53
+ #used to determine if bundler in correct state and could be used
54
+ @settings_correct = true
55
+ end
56
+ rescue Exception => e
57
+ @@logger.error("Incorrect settings initialization, #{settings}\n#{e.to_s}") if @@logger
58
+ @settings_correct = false
59
+ end
60
+ end
61
+
62
+ #main method to process html text block
63
+ def process(block)
64
+ begin
65
+ filters = filters_array
66
+ #parsing html text block, creating BlockData instance
67
+ block_data = @parser.parse(block)
68
+ #if filters set and no bundle files exists we should process block data
69
+ unless filters.empty? or bundle_upto_date?(block_data)
70
+ #reading files content and populating block_data
71
+ read_resources!(block_data)
72
+ #applying filters to block_data
73
+ block_data.apply_filters(filters)
74
+ #writing resulted files with filtered content on disk
75
+ write_files_on_disk(block_data)
76
+ @@logger.info("files written on disk")
77
+ return block_data
78
+ end
79
+ #bundle up to date, returning existing block with modified file names
80
+ block_data.apply_filters(filters)
81
+ return block_data
82
+ rescue Exceptions::WebResourceBundlerError => e
83
+ @@logger.error(e.to_s)
84
+ return nil
85
+ rescue Exception => e
86
+ @@logger.error(e.backtrace.join("\n") + "Unknown error occured: " + e.to_s)
87
+ return nil
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ #giving filters array in right sequence (bundle filter should be first)
94
+ def filters_array
95
+ filters = []
96
+ %w{bundle_filter base64_filter cdn_filter}.each do |key|
97
+ filters << @filters[key.to_sym] if @filters[key.to_sym]
98
+ end
99
+ filters
100
+ end
101
+
102
+ #creates filters or change their settings
103
+ def set_filters(settings, file_manager)
104
+ #common settings same for all filters
105
+ common_sets = {
106
+ :resource_dir => settings.resource_dir,
107
+ :cache_dir => settings.cache_dir
108
+ }
109
+ #used to craete filters
110
+ filters_data = {
111
+ :bundle_filter => 'BundleFilter',
112
+ :base64_filter => 'ImageEncodeFilter',
113
+ :cdn_filter => 'CdnFilter'
114
+ }
115
+ filters_data.each_pair do |key, filter_class|
116
+ if settings[key] and settings[key][:use]
117
+ filter_settings = settings[key].merge(common_sets)
118
+ if @filters[key]
119
+ @filters[key].set_settings(filter_settings)
120
+ else
121
+ #creating filter instance with settings
122
+ @filters[key] = eval("Filters::" + filter_class + "::Filter").new(filter_settings, file_manager)
123
+ end
124
+ else
125
+ #this filter turned off in settings so should be deleted
126
+ @filters.delete(key)
127
+ end
128
+ end
129
+ @filters
130
+ end
131
+
132
+ def create_logger(settings)
133
+ begin
134
+ #creating default log file in rails log directory called web_resource_bundler.log
135
+ unless settings.log_path
136
+ log_dir = File.expand_path('../log', settings.resource_dir)
137
+ log_name = 'web_resource_bundler.log'
138
+ settings[:log_path] = File.join(log_dir, log_name)
139
+ Dir.mkdir(log_dir) unless File.exist?(log_dir)
140
+ end
141
+ file = File.open(settings.log_path, File::WRONLY | File::APPEND | File::CREAT)
142
+ logger = Logger.new(file)
143
+ rescue Exception => e
144
+ logger = Logger.new(STDOUT)
145
+ logger.error("Can't create log file, check log path: #{settings.log_path}\n#{e.to_s}")
146
+ end
147
+ logger
148
+ end
149
+
150
+ #checks if resulted files exist for current @filters and block data
151
+ def bundle_upto_date?(block_data)
152
+ #we don't want to change original parsed block data
153
+ #so just making a clone, using overriden clone method in BlockData
154
+ block_data_copy = block_data.clone
155
+ #modifying clone to obtain resulted files
156
+ #apply_filters will just compute resulted file paths
157
+ #because block_data isn't populated with files content yet
158
+ block_data_copy.apply_filters(filters_array)
159
+ #cheking if resulted files exist on disk in cache folder
160
+ block_data_copy.files.each do |file|
161
+ return false unless File.exist?(File.join(@settings.resource_dir, file.path))
162
+ end
163
+ true
164
+ end
165
+
166
+ #reads block data resource files content from disk and populating block_data
167
+ def read_resources!(block_data)
168
+ #iterating through each resource files
169
+ block_data.files.each do |file|
170
+ content = @file_manager.get_content(file.path)
171
+ #rewriting url to absolute if content is css
172
+ WebResourceBundler::CssUrlRewriter.rewrite_content_urls!(file.path, content) if file.type[:ext] == 'css'
173
+ file.content = content
174
+ end
175
+ #making the same for each child blocks, recursively
176
+ block_data.child_blocks.each do |block|
177
+ read_resources!(block)
178
+ end
179
+ end
180
+
181
+ #recursive method to write all resulted files on disk
182
+ def write_files_on_disk(block_data)
183
+ @file_manager.create_cache_dir
184
+ block_data.files.each do |file|
185
+ File.open(File.join(@settings.resource_dir, file.path), "w") do |f|
186
+ f.print(file.content)
187
+ end
188
+ end
189
+ block_data.child_blocks.each do |block|
190
+ write_files_on_disk(block)
191
+ end
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,57 @@
1
+ module WebResourceBundler
2
+ class BlockData
3
+ attr_accessor :files, :inline_block, :condition, :child_blocks
4
+
5
+ def initialize(condition = "")
6
+ @inline_block = ""
7
+ @files = []
8
+ @condition = condition
9
+ @child_blocks = []
10
+ end
11
+
12
+ def styles
13
+ @files.select do |f|
14
+ [WebResourceBundler::ResourceFileType::CSS,
15
+ WebResourceBundler::ResourceFileType::IE_CSS].include?(f.type)
16
+ end
17
+ end
18
+
19
+ def scripts
20
+ @files.select {|f| f.type == WebResourceBundler::ResourceFileType::JS}
21
+ end
22
+
23
+ def clone
24
+ clon = self.dup
25
+ clon.files = self.files.map {|f| f.clone}
26
+ if clon.child_blocks.size > 0
27
+ clon.child_blocks = self.child_blocks.map do |block|
28
+ block.clone
29
+ end
30
+ else
31
+ clon.child_blocks = []
32
+ end
33
+ clon
34
+ end
35
+
36
+ def self.all_childs(block_data)
37
+ result = []
38
+ result << block_data
39
+ block_data.child_blocks.each do |child|
40
+ result += BlockData.all_childs(child)
41
+ end
42
+ return result
43
+ end
44
+
45
+ def apply_filters(filters)
46
+ unless filters.empty?
47
+ filters.each do |filter|
48
+ items = BlockData.all_childs(self)
49
+ items.each do |block_data|
50
+ filter.apply!(block_data)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+ end