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