web_resource_bundler 0.0.14 → 0.0.15

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.
Files changed (43) hide show
  1. data/.gitignore +1 -0
  2. data/Rakefile +1 -1
  3. data/Readme.md +141 -0
  4. data/VERSION +1 -1
  5. data/lib/web_resource_bundler/content_management/block_data.rb +3 -3
  6. data/lib/web_resource_bundler/content_management/block_parser.rb +2 -2
  7. data/lib/web_resource_bundler/content_management/resource_file.rb +11 -8
  8. data/lib/web_resource_bundler/exceptions.rb +6 -0
  9. data/lib/web_resource_bundler/filters/bundle_filter/resource_packager.rb +1 -1
  10. data/lib/web_resource_bundler/filters/bundle_filter.rb +3 -1
  11. data/lib/web_resource_bundler/filters/image_encode_filter/css_generator.rb +1 -3
  12. data/lib/web_resource_bundler/filters/image_encode_filter/image_data.rb +3 -3
  13. data/lib/web_resource_bundler/filters/image_encode_filter.rb +3 -1
  14. data/lib/web_resource_bundler/rails_app_helpers.rb +5 -3
  15. data/lib/web_resource_bundler/settings_manager.rb +91 -0
  16. data/lib/web_resource_bundler/web_resource_bundler_init.rb +2 -16
  17. data/lib/web_resource_bundler.rb +36 -52
  18. data/spec/sample_block_helper.rb +6 -6
  19. data/spec/spec_helper.rb +14 -6
  20. data/spec/test_data/config/web_resource_bundler.yml +21 -0
  21. data/spec/{public → test_data/public}/foo.css +0 -0
  22. data/spec/{public → test_data/public}/images/good.jpg +0 -0
  23. data/spec/{public → test_data/public}/images/logo.jpg +0 -0
  24. data/spec/{public → test_data/public}/images/sdfo.jpg +0 -0
  25. data/spec/{public → test_data/public}/images/too_big_image.jpg +0 -0
  26. data/spec/{public → test_data/public}/marketing.js +0 -0
  27. data/spec/{public → test_data/public}/salog20.js +0 -0
  28. data/spec/{public → test_data/public}/sample.css +0 -0
  29. data/spec/{public → test_data/public}/seal.js +0 -0
  30. data/spec/{public → test_data/public}/set_cookies.js +0 -0
  31. data/spec/{public → test_data/public}/styles/boo.css +0 -0
  32. data/spec/{public → test_data/public}/styles/for_import.css +0 -0
  33. data/spec/{public → test_data/public}/temp.css +0 -0
  34. data/spec/{public → test_data/public}/test.css +0 -0
  35. data/spec/web_resource_bundler/content_management/resource_file_spec.rb +11 -3
  36. data/spec/web_resource_bundler/filters/bundle_filter/filter_spec.rb +4 -2
  37. data/spec/web_resource_bundler/filters/image_encode_filter/filter_spec.rb +17 -4
  38. data/spec/web_resource_bundler/settings_manager_spec.rb +88 -0
  39. data/spec/web_resource_bundler/web_resource_bundler_spec.rb +96 -94
  40. data/web_resource_bundler.gemspec +23 -23
  41. metadata +25 -22
  42. data/.bundle/config +0 -2
  43. data/README +0 -143
data/.gitignore CHANGED
@@ -19,3 +19,4 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ .bundle
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gem.summary = %Q{lib for css and js content bundling and managment}
10
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
11
  gem.email = "anotheroneman@yahoo.com"
12
- gem.homepage = "http://github.com/gregolsen/web-bundler"
12
+ gem.homepage = "https://github.com/railsware/web-bundler"
13
13
  gem.authors = ["gregolsen"]
14
14
  gem.add_development_dependency "rspec", "1.3.1"
15
15
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
data/Readme.md ADDED
@@ -0,0 +1,141 @@
1
+ [Web Resource Bundler](http://wrb.railsware.com/) - New word in resource management
2
+ ============================================
3
+ Purpose
4
+ ------------------
5
+ The main purpose of WebResourceBundler gem is to minimize request & response
6
+ round-trips count. This could be done by bundling particular resource (css or js) in
7
+ one file. Encoding images in base64 and putting then in css directly.
8
+
9
+ Functional description
10
+ ----------------------
11
+ WebResourceBundler parse your head html block, finding all css and js resource
12
+ files.
13
+ It can bundle resource of particular type in one single file. Base64 filter
14
+ encodes images in base64 putting them in css directly. Separate files for IE
15
+ and other browsers created. Conditional comments (like `<!--[if IE 6]>`) also
16
+ supported. You can use external image hosts to server images in css:
17
+ cdn filter can rewrite image urls for you. Resulted filename is a md5(filenames.sort)
18
+
19
+ Installation
20
+ ---------------------
21
+
22
+ gem install web_resource_bundler
23
+
24
+ Usage
25
+ -------------------
26
+
27
+ Firstly you should create your settings file in config dir.
28
+ You can set separate settings for each environment
29
+
30
+ config/web_resource_bundler.yml
31
+
32
+ development:
33
+ :base64_filter:
34
+ :use: true
35
+ :max_image_size: 23
36
+ :protocol: http
37
+ :domain: localhost:3000
38
+ :bundle_filter:
39
+ :use: true
40
+ :cdn_filter:
41
+ :use: true
42
+ :http_hosts: ['http://localhost:3000']
43
+ :https_hosts: ['https://localhost:3000']
44
+
45
+ Then you should create initializer file in
46
+ /path/to/your/rails_app/config/initializers/ directory
47
+ Let's say it will be web_resource_bundler_init.rb
48
+ Then you should put content like this in it.
49
+
50
+ config/initializers/web_resource_bundler_init.rb
51
+
52
+ require 'web_resource_bundler'
53
+ require 'yaml'
54
+ root_dir = Rails.root #or RAILS_ROOT if you are using older rails version than 3
55
+ environment = Rails.env #or RAILS_ENV in case rails <= 2.3
56
+ settings = { }
57
+ settings_file_path = File.join(root_dir, 'config', 'web_resource_bundler.yml')
58
+ if File.exist?(settings_file_path)
59
+ settings_file = File.open(settings_file_path)
60
+ all_settings = YAML::load(settings_file)
61
+ if all_settings[environment]
62
+ settings = all_settings[environment]
63
+ settings[:resource_dir] = File.join(root_dir, 'public')
64
+ end
65
+ end
66
+
67
+ WebResourceBundler::Bundler.instance.set_settings(settings)
68
+ ActionView::Base.send(:include, WebResourceBundler::RailsAppHelpers)
69
+
70
+ Now in your view files you can call **`web_resource_bundler_process`** helper like this:
71
+
72
+ <head>
73
+ <% web_resource_bundler_process do %>
74
+
75
+ <%= stylesheet_link_tag :scaffold %>
76
+ <%= javascript_include_tag :defaults %>
77
+ <link type="text/css" rel="stylesheet" href="/stylesheets/somestyle.css"/>
78
+ <%=yield :head %>
79
+ <!--[if lte IE 7]>
80
+ <link type="text/css" rel="stylesheet" href="/stylesheets/ie7fix.css"/>
81
+ <link type="text/css" rel="stylesheet" href="/stylesheets/pngfix.css"/>
82
+ <![endif]-->
83
+
84
+ <% end %>
85
+ </head>
86
+
87
+ Notice:
88
+
89
+ For Rails < 3
90
+ you should use **`<% web_resource_bundler_process do %>`**
91
+
92
+ And For Rails >= 3
93
+ use **`<%= web_resource_bundler_process do %>`**
94
+
95
+
96
+ And as result you'll have
97
+
98
+ <link href="/cache/base64_style_d880a502addaa493b889c0970616430b.css?1290594873" media="screen" rel="stylesheet" type="text/css" />
99
+ <script src="/cache/script_275d311037da40e9c9b8c919a8c08b55.js?1290594873" type="text/javascript"></script>
100
+
101
+ <!--[if lte IE 7]>
102
+ <link href="/cache/base64_ie_style_d880a502addaa493b889c0970616430b.css?1290594873" media="screen" rel="stylesheet" type="text/css" />
103
+ <![endif]-->
104
+
105
+ <!--[if lte IE 7]>
106
+ <link type="text/css" rel="stylesheet" href="/cache/base64_style_ad801w02addaa493b889c0970616430b.css?1290594873"/>
107
+ <![endif]-->
108
+
109
+ !!!
110
+ Don't forget to clean your cache directory after deploy to clean old bundles
111
+
112
+
113
+ To disable bundling and see raw results add no_bundler param
114
+ mysite.com/?no_bundler=1
115
+
116
+ Recommendations
117
+ --------------------
118
+
119
+ 1. Be mindful while organazing and linking your resource files
120
+ WebResourceBundler combines all resource file in one. This resulted file could be huge.
121
+ a. Don't link all resources in layouts!
122
+ Be sure to link resources (css\js) only for pages that using them, in other case your users will be forced
123
+ to download huge css\js files with unused content.
124
+ b. One css for one page.
125
+ Try to slice you css files - separate file for each particular page.
126
+ c. Separate bundle block for crucial resources
127
+ To make crucial resources (basic styles\scripts for user can see basic page layout ASAP) load first - just bundle them in separate web_resource_bundler_process block and put this block at the top of your head block.
128
+
129
+ 2. Don't set max_image_size to big values.
130
+ The main reason of using Base64 filter is to avoid unnecessary server requests and minimize load time,
131
+ but when encoded image is very big, traffic overhead time (encoded image base64 code is bigger for apprx. 30% than source image size) could be bigger than request time. In this case your site could be even slower than without a WebResourceBundler.
132
+ Recommended max_image_size value is 1..20kbytes
133
+
134
+ 3. Be careful with third party scripts.
135
+ Some third party javascript libs can load another script file on the fly, relative path for this file computed
136
+ on the client side. But your scripts are bundled and their relative path changed (cache folder), that's why such script
137
+ won't be able to compute loaded file path correctly. You should avoid bundling such tricky javascripts.
138
+
139
+ 4. Unexistent resources handling
140
+ a. Be sure to link in html only existent resource files, otherwise bundler won't work.
141
+ b. If you have unexistent images in css, bundler will work but you've got info messages in log.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.14
1
+ 0.0.15
@@ -11,13 +11,13 @@ module WebResourceBundler
11
11
 
12
12
  def styles
13
13
  @files.select do |f|
14
- [WebResourceBundler::ResourceFileType::CSS,
15
- WebResourceBundler::ResourceFileType::IE_CSS].include?(f.type)
14
+ !([WebResourceBundler::ResourceFileType::CSS,
15
+ WebResourceBundler::ResourceFileType::IE_CSS] & f.types).empty?
16
16
  end
17
17
  end
18
18
 
19
19
  def scripts
20
- @files.select {|f| f.type == WebResourceBundler::ResourceFileType::JS}
20
+ @files.select {|f| f.types.include?(WebResourceBundler::ResourceFileType::JS)}
21
21
  end
22
22
 
23
23
  def clone
@@ -45,9 +45,9 @@ module WebResourceBundler
45
45
  unless value.include?('://')
46
46
  case property
47
47
  when "src"
48
- then files << WebResourceBundler::ResourceFile.new(WebResourceBundler::ResourceFileType::JS, value) if File.extname(value) == '.js'
48
+ then files << WebResourceBundler::ResourceFile.new_js_file(value) if File.extname(value) == '.js'
49
49
  when "href"
50
- then files << WebResourceBundler::ResourceFile.new(WebResourceBundler::ResourceFileType::CSS, value) if File.extname(value) == '.css'
50
+ then files << WebResourceBundler::ResourceFile.new_style_file(value) if File.extname(value) == '.css'
51
51
  end
52
52
  end
53
53
  end
@@ -8,27 +8,30 @@ module WebResourceBundler
8
8
  end
9
9
 
10
10
  class ResourceFile
11
- attr_reader :type
11
+ attr_accessor :types #array of ResourceFileType's objects
12
12
  attr_accessor :path, :content
13
- def initialize(type, path, content = "")
14
- @type = type
13
+ def initialize(path, content, *types)
14
+ @types = types.flatten
15
15
  @content = content
16
16
  @path = path
17
17
  end
18
18
  def self.new_js_file(path, content = "")
19
- ResourceFile.new(ResourceFileType::JS, path, content)
19
+ ResourceFile.new(path, content, ResourceFileType::JS)
20
20
  end
21
21
  def self.new_css_file(path, content = "")
22
- ResourceFile.new(ResourceFileType::CSS, path, content)
22
+ ResourceFile.new(path, content, ResourceFileType::CSS)
23
23
  end
24
24
  def self.new_ie_css_file(path, content ="")
25
- ResourceFile.new(ResourceFileType::IE_CSS, path, content)
25
+ ResourceFile.new(path, content, ResourceFileType::IE_CSS)
26
+ end
27
+ def self.new_style_file(path, content ="")
28
+ ResourceFile.new(path, content, ResourceFileType::CSS, ResourceFileType::IE_CSS)
26
29
  end
27
30
  def self.new_mhtml_file(path, content = "")
28
- ResourceFile.new(ResourceFileType::MHTML, path, content)
31
+ ResourceFile.new(path, content, ResourceFileType::MHTML)
29
32
  end
30
33
  def clone
31
- ResourceFile.new(self.type.dup, self.path.dup, self.content.dup)
34
+ ResourceFile.new(self.path.dup, self.content.dup, self.types.dup)
32
35
  end
33
36
  end
34
37
 
@@ -23,4 +23,10 @@ module WebResourceBundler::Exceptions
23
23
  end
24
24
  end
25
25
 
26
+ class LogCreationError < WebResourceBundlerError
27
+ def initialize(log_path, original_exception)
28
+ super "Can't create log file, check log path: #{log_path}\n#{original_exception}"
29
+ end
30
+ end
31
+
26
32
  end
@@ -14,7 +14,7 @@ module WebResourceBundler::Filters::BundleFilter
14
14
  path = file.path
15
15
  content = file.content
16
16
  output << "/* --------- #{path} --------- */\n"
17
- if file.type[:ext] == 'css'
17
+ if file.types.first[:ext] == 'css'
18
18
  imported_files = extract_imported_files!(content, path)
19
19
  #getting imported (@import ...) files contents
20
20
  imported_resource_files = []
@@ -15,7 +15,7 @@ module WebResourceBundler::Filters::BundleFilter
15
15
  unless block_data.styles.empty?
16
16
  new_css_filename = css_bundle_filepath(block_data.styles)
17
17
  new_css_content = @packager.bundle_files(block_data.styles)
18
- new_css_file = WebResourceBundler::ResourceFile.new_css_file(new_css_filename, new_css_content)
18
+ new_css_file = WebResourceBundler::ResourceFile.new_style_file(new_css_filename, new_css_content)
19
19
  new_files << new_css_file
20
20
  end
21
21
  unless block_data.scripts.empty?
@@ -30,6 +30,8 @@ module WebResourceBundler::Filters::BundleFilter
30
30
 
31
31
  def get_md5(files)
32
32
  items = [(files.map {|f| f.path }).sort]
33
+ items << @settings[:protocol]
34
+ items << @settings[:domain]
33
35
  items += @settings[:md5_additional_data] if @settings[:md5_additional_data]
34
36
  Digest::MD5.hexdigest(items.flatten.join('|'))
35
37
  end
@@ -5,7 +5,6 @@ module WebResourceBundler
5
5
  TAGS = ['background-image', 'background']
6
6
  SEPARATOR = 'A_SEPARATOR'
7
7
  PATTERN = /((#{TAGS.join('|')})\s*:[^\(]*)url\(\s*['|"]([^\)]*)['|"]\s*\)/
8
-
9
8
 
10
9
  def initialize(settings, file_manager)
11
10
  @settings = settings
@@ -14,20 +13,19 @@ module WebResourceBundler
14
13
 
15
14
  def set_settings(settings)
16
15
  @settings = settings
16
+ @settings[:max_image_size] = 20 unless @settings[:max_image_size]
17
17
  end
18
18
 
19
19
  #construct mhtml head of css file with definition of image data in base64
20
20
  def construct_mhtml_content(images)
21
21
  result = ""
22
22
  unless images.empty?
23
- result << "/* \n"
24
23
  result << 'Content-Type: multipart/related; boundary="' << SEPARATOR << '"' << "\n\n"
25
24
  #each image found in css should be defined in header with base64 encoded content
26
25
  images.each_key do |key|
27
26
  result << images[key].construct_mhtml_image_data('--' + SEPARATOR)
28
27
  end
29
28
  result << "\n" << '--' << SEPARATOR << '--' << "\n"
30
- result << "*/"
31
29
  end
32
30
  result
33
31
  end
@@ -16,13 +16,13 @@ module WebResourceBundler
16
16
  else
17
17
  @exist = false
18
18
  end
19
- if WebResourceBundler::Bundler.logger and !@path.include?('://') and !@exist
20
- WebResourceBundler::Bundler.logger.info("Image not found #{@path}")
19
+ if WebResourceBundler::Bundler.instance.logger and !@path.include?('://') and !@exist
20
+ WebResourceBundler::Bundler.instance.logger.info("Image not found #{@path}")
21
21
  end
22
22
  if @exist
23
23
  @size = File.size(@path)
24
24
  name, @extension = File.basename(@path).split('.')
25
- #id is a filename plus random number - to support uniqueness
25
+ #id is a filename plus random number - to provide uniqueness
26
26
  @id = name + rand(MAX_RAND_FOR_ID).to_s
27
27
  end
28
28
  end
@@ -24,6 +24,8 @@ module WebResourceBundler::Filters::ImageEncodeFilter
24
24
  #creating new mhtml file with images encoded in base64
25
25
  mhtml_file = WebResourceBundler::ResourceFile.new_mhtml_file(mhtml_filepath(file.path), "")
26
26
  file.path = encoded_filepath(file.path)
27
+ #we've created separate file for IE so current file should be marked as CSS only
28
+ file.types = [WebResourceBundler::ResourceFileType::CSS]
27
29
  unless file.content.empty?
28
30
  @generator.encode_images!(file.content)
29
31
  #getting images to construct mhtml file
@@ -49,7 +51,7 @@ module WebResourceBundler::Filters::ImageEncodeFilter
49
51
 
50
52
  #filepath of mhtml file for IE
51
53
  def mhtml_filepath(base_file_path)
52
- File.join(@settings[:cache_dir], MHTML_FILE_PREFIX + File.basename(base_file_path))
54
+ File.join(@settings[:cache_dir], MHTML_FILE_PREFIX + File.basename(base_file_path, ".*") + '.mhtml')
53
55
  end
54
56
 
55
57
  end
@@ -8,7 +8,7 @@ module WebResourceBundler::RailsAppHelpers
8
8
  version = Rails::VERSION::STRING
9
9
  if !params['no_bundler'] and WebResourceBundler::Bundler.instance.settings_correct
10
10
  #we want to keep original string unchanged so we can return same content on error
11
- block_data = WebResourceBundler::Bundler.instance.process(result.dup)
11
+ block_data = WebResourceBundler::Bundler.instance.process(result.dup, request.host_with_port, request.protocol.gsub(/:\/\//,''))
12
12
  #if everything ok with bundling we should construct resulted html content and change result
13
13
  result = construct_block(block_data, WebResourceBundler::Bundler.instance.settings) if block_data
14
14
  end
@@ -24,10 +24,12 @@ module WebResourceBundler::RailsAppHelpers
24
24
  result = ""
25
25
  #we should include only mhtml files if browser IE 7 or 6
26
26
  if mhtml_should_be_added?
27
- styles = block_data.files.select {|f| [WebResourceBundler::ResourceFileType::MHTML, WebResourceBundler::ResourceFileType::IE_CSS].include?(f.type)}
27
+ styles = block_data.files.select do |f|
28
+ !([WebResourceBundler::ResourceFileType::MHTML, WebResourceBundler::ResourceFileType::IE_CSS] & f.types).empty?
29
+ end
28
30
  else
29
31
  #it normal browser - so just including base64 css
30
- styles = block_data.files.select {|f| f.type == WebResourceBundler::ResourceFileType::CSS}
32
+ styles = block_data.files.select {|f| f.types.include?(WebResourceBundler::ResourceFileType::CSS)}
31
33
  end
32
34
  styles.each do |file|
33
35
  url = File.join('/', file.path)
@@ -0,0 +1,91 @@
1
+ require 'yaml'
2
+ class WebResourceBundler::SettingsManager
3
+
4
+ DEFAULT_LOG_PATH = 'log/web_resource_bundler.log'
5
+ DEFAULT_RESOURCE_DIR = 'public'
6
+ DEFAULT_SETTINGS_PATH = 'config/web_resource_bundler.yml'
7
+ DEFAULT_CACHE_DIR = 'cache'
8
+
9
+ class << self
10
+
11
+ #creates settings from config file or from defaults
12
+ #if config file doesn't exists
13
+ def create_settings(rails_root, rails_env)
14
+ settings = {}
15
+ if File.exist?(rails_root)
16
+ #reading settings from file in config dir
17
+ settings = settings_from_file(rails_root, rails_env)
18
+ #building required defaults
19
+ defaults = create_default_settings(rails_root)
20
+ #merging required with read from file settings
21
+ #if there's no file settings will contain just required defaults
22
+ settings = defaults.merge(settings)
23
+ end
24
+ settings
25
+ end
26
+
27
+ #creates defaults settings
28
+ def create_default_settings(rails_root)
29
+ settings = {}
30
+ settings[:resource_dir] = File.join(rails_root, DEFAULT_RESOURCE_DIR)
31
+ settings[:log_path] = File.join(rails_root, DEFAULT_LOG_PATH)
32
+ settings[:cache_dir] = DEFAULT_CACHE_DIR
33
+ settings[:bundle_filter] = {
34
+ :use => true
35
+ }
36
+ settings[:cdn_filter] = {
37
+ :use => false
38
+ }
39
+ settings[:base64_filter] = {
40
+ :use => true,
41
+ :max_image_size => 20
42
+ }
43
+ settings
44
+ end
45
+
46
+ #settings common for all filters
47
+ def common_settings(settings)
48
+ {
49
+ :resource_dir => settings[:resource_dir],
50
+ :cache_dir => settings[:cache_dir],
51
+ }
52
+ end
53
+
54
+ #load settings from yaml file depending on environment
55
+ def settings_from_file(rails_root, rails_env)
56
+ settings = {}
57
+ settings_file_path = File.join(rails_root, DEFAULT_SETTINGS_PATH)
58
+ if File.exist?(settings_file_path)
59
+ settings_file = File.open(settings_file_path)
60
+ all_settings = YAML::load(settings_file)
61
+ if all_settings[rails_env]
62
+ settings = all_settings[rails_env]
63
+ settings[:resource_dir] = File.join(rails_root, DEFAULT_RESOURCE_DIR)
64
+ end
65
+ end
66
+ settings
67
+ end
68
+
69
+ #ensures that settings has obligatory keys present
70
+ def settings_correct?(settings)
71
+ %w{resource_dir log_path cache_dir}.each do |key|
72
+ return false unless settings.has_key?(key.to_sym)
73
+ end
74
+ return true
75
+ end
76
+
77
+ #dynamically created methods for each filter have its own settings method
78
+ %w{base64_filter cdn_filter bundle_filter}.each do |filter_name|
79
+ define_method "#{filter_name}_settings" do |settings|
80
+ self.common_settings(settings).merge(settings[filter_name.to_sym])
81
+ end
82
+ end
83
+
84
+ #setting request specific settings like domain and protocol
85
+ def set_request_specific_settings!(settings, domain, protocol)
86
+ settings[:domain] = domain
87
+ settings[:protocol] = protocol
88
+ settings
89
+ end
90
+ end
91
+ end