web_resource_bundler 0.0.13
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/.bundle/config +2 -0
- data/.gitignore +21 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +10 -0
- data/README +118 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/lib/web_resource_bundler.rb +195 -0
- data/lib/web_resource_bundler/content_management/block_data.rb +57 -0
- data/lib/web_resource_bundler/content_management/block_parser.rb +63 -0
- data/lib/web_resource_bundler/content_management/css_url_rewriter.rb +23 -0
- data/lib/web_resource_bundler/content_management/resource_file.rb +35 -0
- data/lib/web_resource_bundler/exceptions.rb +26 -0
- data/lib/web_resource_bundler/file_manager.rb +30 -0
- data/lib/web_resource_bundler/filters.rb +9 -0
- data/lib/web_resource_bundler/filters/base_filter.rb +28 -0
- data/lib/web_resource_bundler/filters/bundle_filter.rb +58 -0
- data/lib/web_resource_bundler/filters/bundle_filter/resource_packager.rb +49 -0
- data/lib/web_resource_bundler/filters/cdn_filter.rb +48 -0
- data/lib/web_resource_bundler/filters/image_encode_filter.rb +56 -0
- data/lib/web_resource_bundler/filters/image_encode_filter/css_generator.rb +85 -0
- data/lib/web_resource_bundler/filters/image_encode_filter/image_data.rb +51 -0
- data/lib/web_resource_bundler/rails_app_helpers.rb +65 -0
- data/lib/web_resource_bundler/settings.rb +46 -0
- data/lib/web_resource_bundler/web_resource_bundler_init.rb +17 -0
- data/spec/public/foo.css +4 -0
- data/spec/public/images/good.jpg +0 -0
- data/spec/public/images/logo.jpg +0 -0
- data/spec/public/images/sdfo.jpg +0 -0
- data/spec/public/images/too_big_image.jpg +0 -0
- data/spec/public/marketing.js +14 -0
- data/spec/public/salog20.js +6 -0
- data/spec/public/sample.css +6 -0
- data/spec/public/seal.js +10 -0
- data/spec/public/set_cookies.js +8 -0
- data/spec/public/styles/boo.css +4 -0
- data/spec/public/styles/for_import.css +7 -0
- data/spec/public/temp.css +1 -0
- data/spec/public/test.css +2 -0
- data/spec/sample_block_helper.rb +81 -0
- data/spec/spec_helper.rb +82 -0
- data/spec/web_resource_bundler/content_management/block_data_spec.rb +33 -0
- data/spec/web_resource_bundler/content_management/block_parser_spec.rb +100 -0
- data/spec/web_resource_bundler/content_management/css_url_rewriter_spec.rb +27 -0
- data/spec/web_resource_bundler/content_management/resource_file_spec.rb +37 -0
- data/spec/web_resource_bundler/file_manager_spec.rb +60 -0
- data/spec/web_resource_bundler/filters/bundle_filter/filter_spec.rb +40 -0
- data/spec/web_resource_bundler/filters/bundle_filter/resource_packager_spec.rb +41 -0
- data/spec/web_resource_bundler/filters/cdn_filter_spec.rb +76 -0
- data/spec/web_resource_bundler/filters/image_encode_filter/css_generator_spec.rb +104 -0
- data/spec/web_resource_bundler/filters/image_encode_filter/filter_spec.rb +73 -0
- data/spec/web_resource_bundler/filters/image_encode_filter/image_data_spec.rb +53 -0
- data/spec/web_resource_bundler/settings_spec.rb +45 -0
- data/spec/web_resource_bundler/web_resource_bundler_spec.rb +90 -0
- data/web_resource_bundler.gemspec +111 -0
- metadata +146 -0
data/.bundle/config
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
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
|