web_resource_bundler 0.0.14 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
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