slight_assets 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/generators/slight_assets/slight_assets_generator.rb +28 -0
- data/lib/slight_assets/base.rb +7 -0
- data/lib/slight_assets/js_reducer.rb +85 -0
- data/lib/slight_assets/rails.rb +79 -0
- data/lib/slight_assets/rake_tasks.rb +112 -0
- data/lib/slight_assets/settings/default_config.yml +43 -0
- data/lib/slight_assets/settings.rb +57 -0
- data/lib/slight_assets/util.rb +243 -0
- data/lib/slight_assets/version.rb +6 -0
- data/lib/slight_assets.rb +1 -0
- data/templates/install/config/assets.yml +42 -0
- data/templates/install/config/initializers/assets.rb +2 -0
- metadata +154 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
class SlightAssetsGenerator < ::Rails::Generator::Base
|
4
|
+
def manifest
|
5
|
+
record do |m|
|
6
|
+
m.file "config/assets.yml", "config/assets.yml"
|
7
|
+
m.file "config/initializers/assets.rb", "config/initializers/assets.rb"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.gem_root
|
12
|
+
File.expand_path('../../../', __FILE__)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.source_root
|
16
|
+
File.join(gem_root, 'templates', 'install')
|
17
|
+
end
|
18
|
+
|
19
|
+
def source_root
|
20
|
+
self.class.source_root
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def banner
|
26
|
+
"Usage: #{$0} slight_assets"
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module SlightAssets
|
2
|
+
class JsReducer
|
3
|
+
BYTE_RANGE = 32..127
|
4
|
+
BASE_NUMBER = BYTE_RANGE.count
|
5
|
+
FETCH_RANGE = BASE_NUMBER ** 2
|
6
|
+
LOOKUP_CHAR = "`"
|
7
|
+
ESCAPABLE_CHARS = ["\\", "\"", "\r", "\n"].freeze
|
8
|
+
|
9
|
+
def compress(js_content)
|
10
|
+
content = reduce(js_content)
|
11
|
+
return js_content if content == js_content
|
12
|
+
func = 'function(x){var b="",p=0,c,l,L=x.length,m="`";while(p<L)b+=(c=x[p++])!=m?c:(l=x.charCodeAt(p++)-31)>1?b.substr(b.length-x.charCodeAt(p++)*96-x.charCodeAt(p++)+3104-l,l):m;return b}'
|
13
|
+
content = content.gsub(/([\\"])/, '\\\\\\1').gsub(/\r/, "\\r").gsub(/\n/, "\\n")
|
14
|
+
"eval((#{func})(\"#{content}\"))"
|
15
|
+
end
|
16
|
+
|
17
|
+
def reduce(js_content)
|
18
|
+
max_cropsize = BASE_NUMBER
|
19
|
+
content = str_as_utf8(js_content)
|
20
|
+
return js_content if content.nil?
|
21
|
+
output, index, limit = "", -1, content.size
|
22
|
+
while (index+=1) < limit
|
23
|
+
i, chars = -1, str_as_utf8("")
|
24
|
+
cropindex, cropsize, s = nil, 0, 0
|
25
|
+
while (i+=1) < max_cropsize && index + i < limit
|
26
|
+
chars << (c = content[index+i])
|
27
|
+
next if s < 5 && (s += (ESCAPABLE_CHARS.include?(c) ? 2 : c.bytesize) ) < 5
|
28
|
+
max_start = index - chars.size
|
29
|
+
start = [0, max_start - FETCH_RANGE].max
|
30
|
+
if (pos = content.index(chars, start)) && pos <= max_start
|
31
|
+
cropindex, cropsize = pos, chars.size
|
32
|
+
end
|
33
|
+
end
|
34
|
+
if cropindex.nil?
|
35
|
+
output << content[index]
|
36
|
+
output << " " if content[index] == LOOKUP_CHAR
|
37
|
+
else
|
38
|
+
max_start = [0, index - cropsize].max
|
39
|
+
chars = str_as_utf8(content[cropindex, cropsize])
|
40
|
+
cropindex = pos while (pos = content.index(chars, cropindex + 1)) && pos <= max_start
|
41
|
+
output << LOOKUP_CHAR
|
42
|
+
output << BYTE_RANGE.last - max_cropsize + cropsize
|
43
|
+
offset = (index - cropindex) - cropsize
|
44
|
+
output << BYTE_RANGE.first + (offset / BASE_NUMBER)
|
45
|
+
output << BYTE_RANGE.first + (offset % BASE_NUMBER)
|
46
|
+
index += cropsize - 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
output
|
50
|
+
end
|
51
|
+
|
52
|
+
if RUBY_VERSION =~ /\A1\.[0-8]/
|
53
|
+
begin
|
54
|
+
require "utf8"
|
55
|
+
def str_as_utf8(str)
|
56
|
+
str.as_utf8
|
57
|
+
end
|
58
|
+
rescue LoadError
|
59
|
+
def str_as_utf8(str)
|
60
|
+
a = str.is_a?(Array) ? str : str.scan(/./mu)
|
61
|
+
class << a
|
62
|
+
def index(chars, start)
|
63
|
+
return nil if start >= size
|
64
|
+
i, s, cs = start - 1, size, chars.size
|
65
|
+
while (i+=1) < s
|
66
|
+
next if self[i] != chars[0]
|
67
|
+
return i if cs == 1
|
68
|
+
j = 0
|
69
|
+
while (j+=1) < cs && i + j < s
|
70
|
+
break if self[i+j] != chars[j]
|
71
|
+
return i if j + 1 == cs
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
a
|
77
|
+
end
|
78
|
+
end
|
79
|
+
else
|
80
|
+
def str_as_utf8(str)
|
81
|
+
str
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module SlightAssets
|
2
|
+
module Rails
|
3
|
+
def init!
|
4
|
+
ActionView::Helpers::AssetTagHelper.send :include, HelperMethods
|
5
|
+
end
|
6
|
+
module_function :init!
|
7
|
+
|
8
|
+
module HelperMethods
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
alias_method_chain :write_asset_file_contents, :static_compressed_file
|
12
|
+
alias_method_chain :javascript_src_tag, :static_compressed_file
|
13
|
+
alias_method_chain :stylesheet_tag, :static_compressed_file
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def write_asset_file_contents_with_static_compressed_file(joined_asset_path, asset_paths)
|
20
|
+
minify = false
|
21
|
+
asset_paths = asset_paths.map do |asset_path|
|
22
|
+
p = asset_path.to_s
|
23
|
+
if p =~ /\.min\./
|
24
|
+
asset_path
|
25
|
+
else
|
26
|
+
p = p.gsub(/\.(js|css)(\?\w+)?\z/, '.min.\1\2')
|
27
|
+
if ::File.exists?(asset_file_path(p))
|
28
|
+
p
|
29
|
+
else
|
30
|
+
minify = true
|
31
|
+
asset_path
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
minified_joined_asset_path = joined_asset_path.gsub(/(?:\.min)?\.(js|css)\z/, '.min.\1')
|
36
|
+
already_existed = File.exists?(minified_joined_asset_path)
|
37
|
+
r = write_asset_file_contents_without_static_compressed_file(minify ? joined_asset_path : minified_joined_asset_path, asset_paths)
|
38
|
+
if minify && ! already_existed
|
39
|
+
SlightAssets::Util.async_write_static_compressed_file(joined_asset_path)
|
40
|
+
else
|
41
|
+
SlightAssets::Util.write_static_gzipped_file(minified_joined_asset_path)
|
42
|
+
end
|
43
|
+
r
|
44
|
+
end
|
45
|
+
|
46
|
+
def javascript_src_tag_with_static_compressed_file(source, options)
|
47
|
+
unless source =~ /\A(?:\w+:)?\/\//
|
48
|
+
asset_path = path_to_javascript(source).split('?').first
|
49
|
+
file_path = asset_file_path(asset_path)
|
50
|
+
if asset_path.end_with?(".js") && asset_path !~ /\.min\./
|
51
|
+
compressed_path = asset_path.gsub(/\.js\z/, '.min.js')
|
52
|
+
if ::File.exists?(asset_file_path(compressed_path))
|
53
|
+
source = compressed_path unless ::File.exists?("#{file_path}.locked")
|
54
|
+
elsif ::File.exists?(file_path)
|
55
|
+
SlightAssets::Util.async_write_static_compressed_file(file_path)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
javascript_src_tag_without_static_compressed_file(source, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def stylesheet_tag_with_static_compressed_file(source, options)
|
63
|
+
unless source =~ /\A(?:\w+:)?\/\//
|
64
|
+
asset_path = path_to_stylesheet(source).split('?').first
|
65
|
+
file_path = asset_file_path(asset_path)
|
66
|
+
if asset_path.end_with?(".css") && asset_path !~ /\.min\./
|
67
|
+
compressed_path = asset_path.gsub(/\.css\z/, '.min.css')
|
68
|
+
if ::File.exists?(asset_file_path(compressed_path))
|
69
|
+
source = compressed_path unless ::File.exists?("#{file_path}.locked")
|
70
|
+
elsif ::File.exists?(file_path)
|
71
|
+
SlightAssets::Util.async_write_static_compressed_file(file_path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
stylesheet_tag_without_static_compressed_file(source, options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
namespace :asset do
|
2
|
+
namespace :compress do
|
3
|
+
task :pre => :environment do
|
4
|
+
require "slight_assets"
|
5
|
+
class AssetWriter
|
6
|
+
include ::SlightAssets::Util
|
7
|
+
attr_reader :image_report
|
8
|
+
def initialize
|
9
|
+
@image_report = {}
|
10
|
+
end
|
11
|
+
def write_compressed_file(file)
|
12
|
+
write_static_compressed_file(file)
|
13
|
+
end
|
14
|
+
def js_list
|
15
|
+
unless defined?(@js_list)
|
16
|
+
@js_list = []
|
17
|
+
(SlightAssets::Cfg.minify_assets? || []).each do |mask|
|
18
|
+
if mask =~ /\A([+-])?\s*(.*\.js)\z/
|
19
|
+
oper = $1 || "+"
|
20
|
+
files_mask = [::Rails.root, "public", "javascripts", $2]
|
21
|
+
@js_list = @js_list.send(oper, FileList[File.join(*files_mask)])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
files_mask = [::Rails.root, "public", "javascripts", "**", "*.min.js"]
|
25
|
+
@js_list = (@js_list - FileList[File.join(*files_mask)]).uniq.sort
|
26
|
+
end
|
27
|
+
@js_list
|
28
|
+
end
|
29
|
+
def css_list
|
30
|
+
unless defined?(@css_list)
|
31
|
+
@css_list = []
|
32
|
+
(SlightAssets::Cfg.minify_assets? || []).each do |mask|
|
33
|
+
if mask =~ /\A([+-])?\s*(.*\.css)\z/
|
34
|
+
oper = $1 || "+"
|
35
|
+
files_mask = [::Rails.root, "public", "stylesheets", $2]
|
36
|
+
@css_list = @css_list.send(oper, FileList[File.join(*files_mask)])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
files_mask = [::Rails.root, "public", "stylesheets", "**", "*.min.css"]
|
40
|
+
@css_list = (@css_list - FileList[File.join(*files_mask)]).uniq.sort
|
41
|
+
end
|
42
|
+
@css_list
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Compress all JS files from your Rails application"
|
48
|
+
task :js => :pre do
|
49
|
+
puts "=" * 80
|
50
|
+
if SlightAssets::Util.js_compressor.nil?
|
51
|
+
puts "WARN: No JS compressor was found"
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
writer = AssetWriter.new
|
55
|
+
jslist = writer.js_list
|
56
|
+
originalsize, compressedsize = 0, 0
|
57
|
+
start_time = Time.now
|
58
|
+
jslist.each do |jsfile|
|
59
|
+
print "Compressing #{jsfile[(Rails.root.to_s.size+1)..-1]}"
|
60
|
+
time = Time.now
|
61
|
+
minfile = writer.write_compressed_file(jsfile).chomp(".gz")
|
62
|
+
originalsize += jssize = File.size(jsfile)
|
63
|
+
compressedsize += minsize = File.size(minfile)
|
64
|
+
percent = jssize.zero? ? 100 : (100.0 * minsize / jssize).round
|
65
|
+
puts " (#{Time.now - time}s)\n -> #{jssize} bytes to #{minsize} bytes (#{percent}% size)"
|
66
|
+
end
|
67
|
+
if jslist.any?
|
68
|
+
puts "Total #{jslist.size} JS files were compressed"
|
69
|
+
percent = originalsize.zero? ? 100 : (100.0 * compressedsize / originalsize).round
|
70
|
+
puts " -> reduced from #{originalsize} bytes to #{compressedsize} bytes (#{percent}% size total, #{originalsize - compressedsize} bytes saved)"
|
71
|
+
end
|
72
|
+
puts "Elapsed time: #{Time.now - start_time}s"
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "Compress all CSS files from your Rails application"
|
76
|
+
task :css => :pre do
|
77
|
+
puts "=" * 80
|
78
|
+
if SlightAssets::Util.css_compressor.nil?
|
79
|
+
puts "WARN: No CSS compressor was found"
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
writer = AssetWriter.new
|
83
|
+
csslist = writer.css_list
|
84
|
+
start_time = Time.now
|
85
|
+
csslist.each do |cssfile|
|
86
|
+
print "Compressing #{cssfile[(Rails.root.to_s.size+1)..-1]}"
|
87
|
+
time = Time.now
|
88
|
+
minfile = writer.write_compressed_file(cssfile).chomp(".gz")
|
89
|
+
csssize = File.size(cssfile)
|
90
|
+
minsize = File.size(minfile)
|
91
|
+
percent = csssize.zero? ? 100 : (100.0 * minsize / csssize).round
|
92
|
+
puts " (#{Time.now - time}s)\n -> #{csssize} bytes to #{minsize} bytes (#{percent}% size)"
|
93
|
+
end
|
94
|
+
puts "Total: #{csslist.size} CSS files were compressed" if csslist.any?
|
95
|
+
puts "Elapsed time: #{Time.now - start_time}s"
|
96
|
+
if writer.image_report.any?
|
97
|
+
puts "=" * 80
|
98
|
+
writer.image_report.each_pair do |image_file, report|
|
99
|
+
refs = report[:found_in].size
|
100
|
+
puts "Image: #{image_file[(Rails.public_path.size)..-1]} (#{"\033[1;31m" unless refs == 1}#{refs} occurrence#{"s" unless refs == 1}\033[0m)"
|
101
|
+
report[:found_in].each_pair do |css_file, count|
|
102
|
+
mode = report[:exists] ? (report[:embeddable] && count == 1 ? "embedded" : "\033[0;33mnot embedded\033[0m") : "\033[0;31mnot found\033[0m"
|
103
|
+
puts " found in #{css_file[(Rails.public_path.size)..-1]} (#{count} ref#{"s" unless count == 1}, #{mode})"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Compress all JS and CSS files from your Rails application"
|
111
|
+
task :compress => ["compress:js", "compress:css"]
|
112
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
slight_asset: &slight_asset
|
2
|
+
|
3
|
+
# Maximum embedded file size
|
4
|
+
# The limit file size to decide whether or not to embed
|
5
|
+
# Must be less than or equal to "32kB"
|
6
|
+
maximum_embedded_file_size: "32kB"
|
7
|
+
|
8
|
+
# Scope of assets to minify in batch process (rake).
|
9
|
+
# The prefix "-" indicates the files to exclude from minifying.
|
10
|
+
# Keep in mind that the sequence matters.
|
11
|
+
minify_assets:
|
12
|
+
- **/*.js
|
13
|
+
- -**/ckeditor/**/*.js
|
14
|
+
- **/*.css
|
15
|
+
# in the end, the masks below will be used for cleaning (even if omitted).
|
16
|
+
- -**/*.min.js
|
17
|
+
- -**/*.min.css
|
18
|
+
|
19
|
+
# Absolute MHTML base href (Optional)
|
20
|
+
# If present, the CSS images that was referenced twice or more
|
21
|
+
# will be embedded inside a MHTML block
|
22
|
+
#mhtml_base_href: "http://localhost:3000"
|
23
|
+
|
24
|
+
# JavaScript Reducer (Optional)
|
25
|
+
# This mode enables obfuscation for JavaScript compression, reducing
|
26
|
+
# even more the file size.
|
27
|
+
# Sets the JavaScript reduce mode:
|
28
|
+
# "rake" - Reduce only when executing rake task (asset:compress:js)
|
29
|
+
# true - Reduce at runtime or when executing rake
|
30
|
+
# false - Don't reduce JavaScript's
|
31
|
+
js_reducer: false
|
32
|
+
|
33
|
+
development:
|
34
|
+
slight_asset:
|
35
|
+
<<: *slight_asset
|
36
|
+
|
37
|
+
test:
|
38
|
+
slight_asset:
|
39
|
+
<<: *slight_asset
|
40
|
+
|
41
|
+
production:
|
42
|
+
slight_asset:
|
43
|
+
<<: *slight_asset
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module SlightAssets
|
4
|
+
class Config
|
5
|
+
attr_reader :config
|
6
|
+
def initialize
|
7
|
+
@config = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *args)
|
11
|
+
sname = name.to_s
|
12
|
+
case sname
|
13
|
+
when /\?$/
|
14
|
+
@config[$`]
|
15
|
+
when /\=$/
|
16
|
+
@config[$`] = args.first
|
17
|
+
else
|
18
|
+
return @config[sname] if @config.has_key?(sname)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_config(path)
|
24
|
+
return @config unless File.exists? path
|
25
|
+
hash = YAML.load_file(path)
|
26
|
+
hash = hash[::Rails.env] || hash if defined?(::Rails)
|
27
|
+
@config.merge! hash["slight_asset"] if hash.has_key?("slight_asset")
|
28
|
+
rescue TypeError => e
|
29
|
+
puts "could not load #{path}: #{e.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def maximum_embedded_file_size
|
33
|
+
unless defined?(@maximum_embedded_file_size)
|
34
|
+
c = @config["maximum_embedded_file_size"]
|
35
|
+
if c.is_a?(Fixnum)
|
36
|
+
@maximum_embedded_file_size = c
|
37
|
+
elsif c =~ /\A(\d+)\s*(\w+)\z/
|
38
|
+
c = $1.to_i
|
39
|
+
case $2
|
40
|
+
when "kB", "kb"
|
41
|
+
c = c * 1_024
|
42
|
+
end
|
43
|
+
@maximum_embedded_file_size = [c, 32 * 1_024].min
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@maximum_embedded_file_size
|
47
|
+
end
|
48
|
+
|
49
|
+
def url_matcher
|
50
|
+
/url\(['"]?(.*?)(?:\?\d+)?['"]?\)/
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Cfg = Config.new
|
55
|
+
Cfg.load_config File.expand_path(File.join(*%w[.. settings default_config.yml]), __FILE__)
|
56
|
+
Cfg.load_config ::Rails.root.join(*%w[config assets.yml]) if defined?(::Rails)
|
57
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "zlib"
|
3
|
+
|
4
|
+
module SlightAssets
|
5
|
+
module Util
|
6
|
+
def write_static_gzipped_file(file_path, content = nil)
|
7
|
+
return file_path if file_path.nil? || file_path.end_with?(".gz") || ! File.exists?(file_path)
|
8
|
+
zip_path = "#{file_path}.gz"
|
9
|
+
return zip_path if File.exists?(zip_path)
|
10
|
+
content = File.read(file_path) if content.nil?
|
11
|
+
Zlib::GzipWriter.open(zip_path, Zlib::BEST_COMPRESSION) {|f| f.write(content) }
|
12
|
+
# Set mtime to the latest of the file to allow for
|
13
|
+
# consistent ETag without a shared filesystem.
|
14
|
+
mt = File.mtime(file_path)
|
15
|
+
File.utime(mt, mt, zip_path)
|
16
|
+
zip_path
|
17
|
+
end
|
18
|
+
module_function :write_static_gzipped_file
|
19
|
+
|
20
|
+
def write_static_minified_asset(file_path)
|
21
|
+
return file_path if file_path.nil? || file_path =~ /\.min\./ || file_path !~ /\.(?:js|css)\z/ || ! File.exists?(file_path)
|
22
|
+
min_path = file_path.gsub(/\.(js|css)\z/, '.min.\1')
|
23
|
+
compressor = nil
|
24
|
+
case extension = $1
|
25
|
+
when "css"
|
26
|
+
compressor = css_compressor
|
27
|
+
when "js"
|
28
|
+
compressor = js_compressor
|
29
|
+
end
|
30
|
+
return file_path if compressor.nil?
|
31
|
+
content = File.read(file_path)
|
32
|
+
c = compressor.compress(content)
|
33
|
+
content = c if c.bytesize < content.bytesize
|
34
|
+
content = embed_images(content, min_path) if extension == "css"
|
35
|
+
reduced_content = content
|
36
|
+
if extension == "js"
|
37
|
+
reduced_content = js_reduce(content)
|
38
|
+
reduced_content = content if reduced_content.bytesize > content.bytesize
|
39
|
+
end
|
40
|
+
File.open(min_path, "w") { |f| f.write(reduced_content) }
|
41
|
+
mt = File.mtime(file_path)
|
42
|
+
File.utime(mt, mt, min_path)
|
43
|
+
[min_path, content]
|
44
|
+
end
|
45
|
+
module_function :write_static_minified_asset
|
46
|
+
|
47
|
+
def js_reduce(content)
|
48
|
+
unless (Cfg.js_reducer? == "rake" && is_rake?) || Cfg.js_reducer? == true
|
49
|
+
content
|
50
|
+
else
|
51
|
+
JsReducer.new.compress(content)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
module_function :js_reduce
|
55
|
+
|
56
|
+
def embed_images(content, file_path)
|
57
|
+
return content if file_path =~ /\A(?:\w+:)?\/\//
|
58
|
+
images = extract_images(content, file_path)
|
59
|
+
file_url = asset_url(file_path)
|
60
|
+
image_contents = {}
|
61
|
+
multipart = ["/*\r\nContent-Type: multipart/related; boundary=\"MHTML_IMAGES\"\r\n"]
|
62
|
+
replace_imports = Cfg.replace_css_imports?
|
63
|
+
content = content.gsub(Cfg.url_matcher) do |url_match|
|
64
|
+
image_path = $1
|
65
|
+
quot = (url_match =~ /"/ ? "\"" : "")
|
66
|
+
if image_path =~ /\A(?:[\\\/]|\w+:)/
|
67
|
+
url_match
|
68
|
+
elsif image_path =~ /(\.min)?\.css\z/ && replace_imports
|
69
|
+
if $1
|
70
|
+
url_match
|
71
|
+
elsif File.exists?(asset_expand_path(image_path, file_path))
|
72
|
+
"url(#{quot}#{image_path.gsub(/\.css\z/, ".min.css")}#{quot})"
|
73
|
+
else
|
74
|
+
url_match
|
75
|
+
end
|
76
|
+
else
|
77
|
+
image_file_path = asset_expand_path(image_path, file_path)
|
78
|
+
if (mt = image_mime_type(image_path)) &&
|
79
|
+
(encode64 = encoded_file_contents(image_file_path, file_path))
|
80
|
+
if image_contents[image_file_path].nil?
|
81
|
+
part_name = "img#{multipart.size}_#{File.basename(image_file_path)}"
|
82
|
+
image_contents[image_file_path] = [part_name, encode64]
|
83
|
+
else
|
84
|
+
part_name = image_contents[image_file_path].first
|
85
|
+
end
|
86
|
+
if images[image_file_path] > 1
|
87
|
+
if file_url
|
88
|
+
multipart << [
|
89
|
+
"\r\n--MHTML_IMAGES",
|
90
|
+
"\r\nContent-Location: ",
|
91
|
+
part_name,
|
92
|
+
"\r\nContent-Type: ",
|
93
|
+
mt,
|
94
|
+
"\r\nContent-Transfer-Encoding: base64\r\n\r\n",
|
95
|
+
encode64
|
96
|
+
]
|
97
|
+
"url(#{quot}mhtml:#{file_url}!#{part_name}#{quot})"
|
98
|
+
else
|
99
|
+
url_match
|
100
|
+
end
|
101
|
+
else
|
102
|
+
"url(#{quot}data:#{mt};base64,#{encode64}#{quot})"
|
103
|
+
end
|
104
|
+
else
|
105
|
+
url_match
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
if multipart.size > 1
|
110
|
+
content = content.gsub(/(\*?)(background-image:[^;\}]*?url\("?mhtml)/, '*\2')
|
111
|
+
(multipart + ["\r\n--MHTML_IMAGES--\r\n*/\r\n", content]).flatten.join("")
|
112
|
+
else
|
113
|
+
content
|
114
|
+
end
|
115
|
+
end
|
116
|
+
module_function :embed_images
|
117
|
+
|
118
|
+
def asset_url(file_path)
|
119
|
+
if defined?(::Rails) && file_path.start_with?(::Rails.public_path) && Cfg.mhtml_base_href?
|
120
|
+
URI.join(Cfg.mhtml_base_href, file_path[(::Rails.public_path.size)..-1])
|
121
|
+
end
|
122
|
+
end
|
123
|
+
module_function :asset_url
|
124
|
+
|
125
|
+
def extract_images(content, file_path)
|
126
|
+
images = {}
|
127
|
+
content.scan(Cfg.url_matcher).flatten.each do |image_path|
|
128
|
+
image_file_path = asset_expand_path(image_path, file_path)
|
129
|
+
images[image_file_path] = (images[image_file_path] || 0) + 1
|
130
|
+
end
|
131
|
+
images
|
132
|
+
end
|
133
|
+
module_function :extract_images
|
134
|
+
|
135
|
+
def asset_expand_path(relative_asset_path, absolute_source_file_path)
|
136
|
+
File.expand_path(File.join("..", relative_asset_path), absolute_source_file_path)
|
137
|
+
end
|
138
|
+
module_function :asset_expand_path
|
139
|
+
|
140
|
+
def image_mime_type(path)
|
141
|
+
return if path !~ /\.([^\.]+)$/
|
142
|
+
case $1.downcase.to_sym
|
143
|
+
when :jpg, :jpeg
|
144
|
+
"image/jpeg"
|
145
|
+
when :png
|
146
|
+
"image/png"
|
147
|
+
when :gif
|
148
|
+
"image/gif"
|
149
|
+
when :tif, :tiff
|
150
|
+
"image/tiff"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
module_function :image_mime_type
|
154
|
+
|
155
|
+
def encoded_file_contents(file_path, css_file_path = nil)
|
156
|
+
if css_file_path && is_rake?
|
157
|
+
@image_report[file_path] ||= {:found_in => {}}
|
158
|
+
@image_report[file_path][:found_in][css_file_path] =
|
159
|
+
(@image_report[file_path][:found_in][css_file_path] || 0) + 1
|
160
|
+
if @image_report[file_path][:exists] = File.exists?(file_path)
|
161
|
+
@image_report[file_path][:embeddable] =
|
162
|
+
File.size(file_path) <= Cfg.maximum_embedded_file_size
|
163
|
+
end
|
164
|
+
end
|
165
|
+
if File.exists?(file_path) && File.size(file_path) <= Cfg.maximum_embedded_file_size
|
166
|
+
Base64.encode64(File.read(file_path)).gsub(/\n/, "")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
module_function :encoded_file_contents
|
170
|
+
|
171
|
+
def is_runtime?
|
172
|
+
! is_rake?
|
173
|
+
end
|
174
|
+
module_function :is_runtime?
|
175
|
+
|
176
|
+
def is_rake?
|
177
|
+
defined?(@image_report) && @image_report.is_a?(Hash)
|
178
|
+
end
|
179
|
+
module_function :is_rake?
|
180
|
+
|
181
|
+
def async_write_static_compressed_file(file_path, &block)
|
182
|
+
lock_file_path = "#{file_path}.locked"
|
183
|
+
return if File.exists?(lock_file_path) && File.mtime(lock_file_path) > (Time.now - 120)
|
184
|
+
File.open(lock_file_path, "w"){} # touch
|
185
|
+
Thread.new do
|
186
|
+
begin
|
187
|
+
file_path = write_static_compressed_file(file_path)
|
188
|
+
ensure
|
189
|
+
File.delete(lock_file_path)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
module_function :async_write_static_compressed_file
|
194
|
+
|
195
|
+
def write_static_compressed_file(file_path)
|
196
|
+
file_path, content = write_static_minified_asset(file_path)
|
197
|
+
file_path = write_static_gzipped_file(file_path, content)
|
198
|
+
end
|
199
|
+
module_function :write_static_compressed_file
|
200
|
+
|
201
|
+
def js_compressor
|
202
|
+
closure_compiler_js_compressor || yui_js_compressor
|
203
|
+
end
|
204
|
+
module_function :js_compressor
|
205
|
+
|
206
|
+
def css_compressor
|
207
|
+
yui_css_compressor
|
208
|
+
end
|
209
|
+
module_function :css_compressor
|
210
|
+
|
211
|
+
protected
|
212
|
+
|
213
|
+
begin
|
214
|
+
require "closure-compiler"
|
215
|
+
def closure_compiler_js_compressor
|
216
|
+
Closure::Compiler.new
|
217
|
+
end
|
218
|
+
rescue LoadError => e
|
219
|
+
STDERR.puts "WARN: #{e.message}"
|
220
|
+
def closure_compiler_js_compressor
|
221
|
+
end
|
222
|
+
end
|
223
|
+
module_function :closure_compiler_js_compressor
|
224
|
+
|
225
|
+
begin
|
226
|
+
require "yui/compressor"
|
227
|
+
def yui_js_compressor
|
228
|
+
YUI::JavaScriptCompressor.new
|
229
|
+
end
|
230
|
+
def yui_css_compressor
|
231
|
+
YUI::CssCompressor.new
|
232
|
+
end
|
233
|
+
rescue LoadError => e
|
234
|
+
STDERR.puts "WARN: #{e.message}"
|
235
|
+
def yui_js_compressor
|
236
|
+
end
|
237
|
+
def yui_css_compressor
|
238
|
+
end
|
239
|
+
end
|
240
|
+
module_function :yui_js_compressor
|
241
|
+
module_function :yui_css_compressor
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
module SlightAssets
|
2
|
+
version = nil
|
3
|
+
version = $1 if ::File.expand_path('../..', __FILE__) =~ /\/slight_assets-(\d[\w\.]+)/
|
4
|
+
version = ::StepUp::Driver::Git.new.last_version_tag if version.nil? && ::File.exists?(::File.expand_path('../../../.git', __FILE__))
|
5
|
+
VERSION = version.gsub(/^v?([^\+]+)\+?\d*$/, '\1')
|
6
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "slight_assets/base"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
slight_asset: &slight_asset
|
2
|
+
|
3
|
+
# Maximum embedded file size
|
4
|
+
# The limit file size to decide whether or not to embed
|
5
|
+
# Must be less than or equal to "32kB"
|
6
|
+
maximum_embedded_file_size: "32kB"
|
7
|
+
|
8
|
+
# Scope of assets to minify in batch process (rake).
|
9
|
+
# The prefix "-" indicates the files to exclude from minifying.
|
10
|
+
# Keep in mind that the sequence matters.
|
11
|
+
minify_assets:
|
12
|
+
- **/*.js
|
13
|
+
- **/*.css
|
14
|
+
# in the end, the masks below will be used for cleaning (even if omitted).
|
15
|
+
- -**/*.min.js
|
16
|
+
- -**/*.min.css
|
17
|
+
|
18
|
+
# Absolute MHTML base href (Optional)
|
19
|
+
# If present, the CSS images that was referenced twice or more
|
20
|
+
# will be embedded inside a MHTML block
|
21
|
+
#mhtml_base_href: "http://localhost:3000"
|
22
|
+
|
23
|
+
# JavaScript Reducer (Optional)
|
24
|
+
# This mode enables obfuscation for JavaScript compression, reducing
|
25
|
+
# even more the file size.
|
26
|
+
# Sets the JavaScript reduce mode:
|
27
|
+
# "rake" - Reduce only when executing rake task (asset:compress:js)
|
28
|
+
# true - Reduce at runtime or when executing rake
|
29
|
+
# false - Don't reduce JavaScript's
|
30
|
+
js_reducer: false
|
31
|
+
|
32
|
+
development:
|
33
|
+
slight_asset:
|
34
|
+
<<: *slight_asset
|
35
|
+
|
36
|
+
test:
|
37
|
+
slight_asset:
|
38
|
+
<<: *slight_asset
|
39
|
+
|
40
|
+
production:
|
41
|
+
slight_asset:
|
42
|
+
<<: *slight_asset
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slight_assets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Marcelo Manzan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-20 00:00:00 -03:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: yui-compressor
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 55
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 9
|
33
|
+
- 6
|
34
|
+
version: 0.9.6
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: step-up
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - "="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 6
|
49
|
+
- 0
|
50
|
+
version: 0.6.0
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rspec
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: mocha
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
version_requirements: *id004
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: closure-compiler
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
type: :development
|
94
|
+
version_requirements: *id005
|
95
|
+
description: Optimize the assets of your Rails application without change any line of code.
|
96
|
+
email:
|
97
|
+
- manzan@gmail.com
|
98
|
+
executables: []
|
99
|
+
|
100
|
+
extensions: []
|
101
|
+
|
102
|
+
extra_rdoc_files: []
|
103
|
+
|
104
|
+
files:
|
105
|
+
- generators/slight_assets/slight_assets_generator.rb
|
106
|
+
- lib/slight_assets.rb
|
107
|
+
- lib/slight_assets/base.rb
|
108
|
+
- lib/slight_assets/js_reducer.rb
|
109
|
+
- lib/slight_assets/rails.rb
|
110
|
+
- lib/slight_assets/rake_tasks.rb
|
111
|
+
- lib/slight_assets/settings.rb
|
112
|
+
- lib/slight_assets/settings/default_config.yml
|
113
|
+
- lib/slight_assets/util.rb
|
114
|
+
- lib/slight_assets/version.rb
|
115
|
+
- templates/install/config/assets.yml
|
116
|
+
- templates/install/config/initializers/assets.rb
|
117
|
+
has_rdoc: true
|
118
|
+
homepage: https://github.com/kawamanza/slight_assets
|
119
|
+
licenses: []
|
120
|
+
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 23
|
141
|
+
segments:
|
142
|
+
- 1
|
143
|
+
- 3
|
144
|
+
- 6
|
145
|
+
version: 1.3.6
|
146
|
+
requirements: []
|
147
|
+
|
148
|
+
rubyforge_project: slight_assets
|
149
|
+
rubygems_version: 1.3.7
|
150
|
+
signing_key:
|
151
|
+
specification_version: 3
|
152
|
+
summary: All you need to make your rails application more fast.
|
153
|
+
test_files: []
|
154
|
+
|