assets_booster 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README +0 -0
- data/Rakefile +2 -0
- data/asset_booster.gemspec +21 -0
- data/lib/assets_booster.rb +7 -0
- data/lib/assets_booster/compiler/closure.rb +36 -0
- data/lib/assets_booster/compiler/dummy.rb +17 -0
- data/lib/assets_booster/compiler/jsmin.rb +234 -0
- data/lib/assets_booster/compiler/node-js/parse-js.js +1254 -0
- data/lib/assets_booster/compiler/node-js/process.js +1602 -0
- data/lib/assets_booster/compiler/node-js/squeeze-more.js +22 -0
- data/lib/assets_booster/compiler/node-js/uglify.js +203 -0
- data/lib/assets_booster/compiler/rainpress.rb +17 -0
- data/lib/assets_booster/compiler/uglify.rb +19 -0
- data/lib/assets_booster/configuration.rb +104 -0
- data/lib/assets_booster/merger/css.rb +79 -0
- data/lib/assets_booster/merger/simple.rb +22 -0
- data/lib/assets_booster/package/base.rb +71 -0
- data/lib/assets_booster/package/javascript.rb +24 -0
- data/lib/assets_booster/package/stylesheet.rb +24 -0
- data/lib/assets_booster/packager.rb +33 -0
- data/lib/assets_booster/railtie.rb +19 -0
- data/lib/assets_booster/tasks/tasks.rake +29 -0
- data/lib/assets_booster/version.rb +3 -0
- data/lib/assets_booster/view_helper.rb +17 -0
- metadata +93 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
var jsp = require("./parse-js"),
|
2
|
+
pro = require("./process"),
|
3
|
+
slice = jsp.slice,
|
4
|
+
member = jsp.member,
|
5
|
+
PRECEDENCE = jsp.PRECEDENCE,
|
6
|
+
OPERATORS = jsp.OPERATORS;
|
7
|
+
|
8
|
+
function ast_squeeze_more(ast) {
|
9
|
+
var w = pro.ast_walker(), walk = w.walk;
|
10
|
+
return w.with_walkers({
|
11
|
+
"call": function(expr, args) {
|
12
|
+
if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
|
13
|
+
// foo.toString() ==> foo+""
|
14
|
+
return [ "binary", "+", expr[1], [ "string", "" ]];
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}, function() {
|
18
|
+
return walk(ast);
|
19
|
+
});
|
20
|
+
};
|
21
|
+
|
22
|
+
exports.ast_squeeze_more = ast_squeeze_more;
|
@@ -0,0 +1,203 @@
|
|
1
|
+
#! /usr/bin/env node
|
2
|
+
// -*- js2 -*-
|
3
|
+
|
4
|
+
global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
|
5
|
+
var fs = require("fs"),
|
6
|
+
jsp = require("./parse-js"),
|
7
|
+
pro = require("./process");
|
8
|
+
|
9
|
+
var options = {
|
10
|
+
ast: false,
|
11
|
+
mangle: true,
|
12
|
+
mangle_toplevel: false,
|
13
|
+
squeeze: true,
|
14
|
+
make_seqs: true,
|
15
|
+
dead_code: true,
|
16
|
+
beautify: false,
|
17
|
+
verbose: false,
|
18
|
+
show_copyright: true,
|
19
|
+
out_same_file: false,
|
20
|
+
max_line_length: 32 * 1024,
|
21
|
+
unsafe: false,
|
22
|
+
reserved_names: null,
|
23
|
+
beautify_options: {
|
24
|
+
indent_level: 4,
|
25
|
+
indent_start: 0,
|
26
|
+
quote_keys: false,
|
27
|
+
space_colon: false
|
28
|
+
},
|
29
|
+
output: true // stdout
|
30
|
+
};
|
31
|
+
|
32
|
+
var args = jsp.slice(process.argv, 2);
|
33
|
+
var filename;
|
34
|
+
|
35
|
+
out: while (args.length > 0) {
|
36
|
+
var v = args.shift();
|
37
|
+
switch (v) {
|
38
|
+
case "-b":
|
39
|
+
case "--beautify":
|
40
|
+
options.beautify = true;
|
41
|
+
break;
|
42
|
+
case "-i":
|
43
|
+
case "--indent":
|
44
|
+
options.beautify_options.indent_level = args.shift();
|
45
|
+
break;
|
46
|
+
case "-q":
|
47
|
+
case "--quote-keys":
|
48
|
+
options.beautify_options.quote_keys = true;
|
49
|
+
break;
|
50
|
+
case "-mt":
|
51
|
+
case "--mangle-toplevel":
|
52
|
+
options.mangle_toplevel = true;
|
53
|
+
break;
|
54
|
+
case "--no-mangle":
|
55
|
+
case "-nm":
|
56
|
+
options.mangle = false;
|
57
|
+
break;
|
58
|
+
case "--no-squeeze":
|
59
|
+
case "-ns":
|
60
|
+
options.squeeze = false;
|
61
|
+
break;
|
62
|
+
case "--no-seqs":
|
63
|
+
options.make_seqs = false;
|
64
|
+
break;
|
65
|
+
case "--no-dead-code":
|
66
|
+
options.dead_code = false;
|
67
|
+
break;
|
68
|
+
case "--no-copyright":
|
69
|
+
case "-nc":
|
70
|
+
options.show_copyright = false;
|
71
|
+
break;
|
72
|
+
case "-o":
|
73
|
+
case "--output":
|
74
|
+
options.output = args.shift();
|
75
|
+
break;
|
76
|
+
case "--overwrite":
|
77
|
+
options.out_same_file = true;
|
78
|
+
break;
|
79
|
+
case "-v":
|
80
|
+
case "--verbose":
|
81
|
+
options.verbose = true;
|
82
|
+
break;
|
83
|
+
case "--ast":
|
84
|
+
options.ast = true;
|
85
|
+
break;
|
86
|
+
case "--unsafe":
|
87
|
+
options.unsafe = true;
|
88
|
+
break;
|
89
|
+
case "--max-line-len":
|
90
|
+
options.max_line_length = parseInt(args.shift(), 10);
|
91
|
+
break;
|
92
|
+
case "--reserved-names":
|
93
|
+
options.reserved_names = args.shift().split(",");
|
94
|
+
break;
|
95
|
+
default:
|
96
|
+
filename = v;
|
97
|
+
break out;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
if (options.verbose) {
|
102
|
+
pro.set_logger(function(msg){
|
103
|
+
sys.debug(msg);
|
104
|
+
});
|
105
|
+
}
|
106
|
+
|
107
|
+
if (filename) {
|
108
|
+
fs.readFile(filename, "utf8", function(err, text){
|
109
|
+
if (err) throw err;
|
110
|
+
output(squeeze_it(text));
|
111
|
+
});
|
112
|
+
} else {
|
113
|
+
var stdin = process.openStdin();
|
114
|
+
stdin.setEncoding("utf8");
|
115
|
+
var text = "";
|
116
|
+
stdin.on("data", function(chunk){
|
117
|
+
text += chunk;
|
118
|
+
});
|
119
|
+
stdin.on("end", function() {
|
120
|
+
output(squeeze_it(text));
|
121
|
+
});
|
122
|
+
}
|
123
|
+
|
124
|
+
function output(text) {
|
125
|
+
var out;
|
126
|
+
if (options.out_same_file && filename)
|
127
|
+
options.output = filename;
|
128
|
+
if (options.output === true) {
|
129
|
+
out = process.stdout;
|
130
|
+
} else {
|
131
|
+
out = fs.createWriteStream(options.output, {
|
132
|
+
flags: "w",
|
133
|
+
encoding: "utf8",
|
134
|
+
mode: 0644
|
135
|
+
});
|
136
|
+
}
|
137
|
+
out.write(text);
|
138
|
+
if (options.output !== true) {
|
139
|
+
out.end();
|
140
|
+
}
|
141
|
+
};
|
142
|
+
|
143
|
+
// --------- main ends here.
|
144
|
+
|
145
|
+
function show_copyright(comments) {
|
146
|
+
var ret = "";
|
147
|
+
for (var i = 0; i < comments.length; ++i) {
|
148
|
+
var c = comments[i];
|
149
|
+
if (c.type == "comment1") {
|
150
|
+
ret += "//" + c.value + "\n";
|
151
|
+
} else {
|
152
|
+
ret += "/*" + c.value + "*/";
|
153
|
+
}
|
154
|
+
}
|
155
|
+
return ret;
|
156
|
+
};
|
157
|
+
|
158
|
+
function squeeze_it(code) {
|
159
|
+
var result = "";
|
160
|
+
if (options.show_copyright) {
|
161
|
+
var tok = jsp.tokenizer(code), c;
|
162
|
+
c = tok();
|
163
|
+
result += show_copyright(c.comments_before);
|
164
|
+
}
|
165
|
+
try {
|
166
|
+
var ast = time_it("parse", function(){ return jsp.parse(code); });
|
167
|
+
if (options.mangle) ast = time_it("mangle", function(){
|
168
|
+
return pro.ast_mangle(ast, {
|
169
|
+
toplevel: options.mangle_toplevel,
|
170
|
+
except: options.reserved_names
|
171
|
+
});
|
172
|
+
});
|
173
|
+
if (options.squeeze) ast = time_it("squeeze", function(){
|
174
|
+
ast = pro.ast_squeeze(ast, {
|
175
|
+
make_seqs : options.make_seqs,
|
176
|
+
dead_code : options.dead_code,
|
177
|
+
keep_comps : !options.unsafe
|
178
|
+
});
|
179
|
+
if (options.unsafe)
|
180
|
+
ast = pro.ast_squeeze_more(ast);
|
181
|
+
return ast;
|
182
|
+
});
|
183
|
+
if (options.ast)
|
184
|
+
return sys.inspect(ast, null, null);
|
185
|
+
result += time_it("generate", function(){ return pro.gen_code(ast, options.beautify && options.beautify_options) });
|
186
|
+
if (!options.beautify && options.max_line_length) {
|
187
|
+
result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) });
|
188
|
+
}
|
189
|
+
return result;
|
190
|
+
} catch(ex) {
|
191
|
+
sys.debug(ex.stack);
|
192
|
+
sys.debug(sys.inspect(ex));
|
193
|
+
sys.debug(JSON.stringify(ex));
|
194
|
+
}
|
195
|
+
};
|
196
|
+
|
197
|
+
function time_it(name, cont) {
|
198
|
+
if (!options.verbose)
|
199
|
+
return cont();
|
200
|
+
var t1 = new Date().getTime();
|
201
|
+
try { return cont(); }
|
202
|
+
finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
|
203
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module AssetsBooster
|
2
|
+
module Compiler
|
3
|
+
class Rainpress
|
4
|
+
def self.name
|
5
|
+
'Rainpress'
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.compile(css)
|
9
|
+
require 'rainpress'
|
10
|
+
::Rainpress.compress(css)
|
11
|
+
rescue LoadError => e
|
12
|
+
raise "To use the Rainpress CSS Compressor, please install the rainpress gem first"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module AssetsBooster
|
2
|
+
module Compiler
|
3
|
+
class Uglify
|
4
|
+
def self.name
|
5
|
+
'UglifyJS running on Node.js'
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.compile(code)
|
9
|
+
raise CompileError.new("You need to install node.js in order to compile using UglifyJS.") unless %x[which node].length > 1
|
10
|
+
IO.popen("cd #{Pathname.new(File.join(File.dirname(__FILE__),'node-js')).realpath} && node uglify.js", "r+") do |io|
|
11
|
+
io.write(code)
|
12
|
+
io.close_write
|
13
|
+
io.read
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module AssetsBooster
|
3
|
+
class Configuration
|
4
|
+
cattr_accessor :filename
|
5
|
+
cattr_accessor :config
|
6
|
+
|
7
|
+
self.filename = File.join(Rails.root, "config", "assets_booster.yml")
|
8
|
+
|
9
|
+
def self.load
|
10
|
+
self.config = YAML.load_file(filename)
|
11
|
+
AssetsBooster::Packager.packages = {}
|
12
|
+
config['packages'].each_pair do |type, packages|
|
13
|
+
type = type.to_sym
|
14
|
+
AssetsBooster::Packager.packages[type] = {}
|
15
|
+
packages.each_pair do |name, assets|
|
16
|
+
AssetsBooster::Packager.packages[type][name] = get_package(type).new(name, assets)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.asset_path(file)
|
22
|
+
File.join(Rails.root, "public", file)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.compiler_for_type(type)
|
26
|
+
get_compiler(config['options'][type.to_s]['compiler'])
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.defaults
|
30
|
+
{
|
31
|
+
'packages' => {
|
32
|
+
'javascript' => {},
|
33
|
+
'stylesheet' => {},
|
34
|
+
},
|
35
|
+
'options' => {
|
36
|
+
'compiler' => {
|
37
|
+
'javascript' => {
|
38
|
+
'name' => "closure",
|
39
|
+
'options' => "",
|
40
|
+
},
|
41
|
+
'stylesheet' => {
|
42
|
+
'name' => "rainpress",
|
43
|
+
'options' => "",
|
44
|
+
}
|
45
|
+
},
|
46
|
+
'environments' => %w(staging production),
|
47
|
+
}
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.create
|
52
|
+
if File.exists?(filename)
|
53
|
+
AssetsBooster.log "#{filename} already exists. Aborting task..."
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
config = defaults
|
58
|
+
config['packages']['javascript'] = {
|
59
|
+
"base" => file_list("#{Rails.root}/public/javascripts", "js"),
|
60
|
+
}
|
61
|
+
config['packages']['stylesheet'] = {
|
62
|
+
"base" => file_list("#{Rails.root}/public/stylesheets", "css"),
|
63
|
+
}
|
64
|
+
|
65
|
+
File.open(filename, "w") do |out|
|
66
|
+
YAML.dump(config, out)
|
67
|
+
end
|
68
|
+
self.config = config
|
69
|
+
|
70
|
+
AssetsBooster.log("#{filename} example file created!")
|
71
|
+
AssetsBooster.log("Please reorder files under 'base' so dependencies are loaded in correct order.")
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.boosted_environment?
|
75
|
+
@boosted_environment ||= config['options']['environments'].include?(Rails.env)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def self.file_list(path, extension)
|
81
|
+
Dir[File.join(path, "*.#{extesnsion}")].map do |file|
|
82
|
+
file.chomp(".#{extension}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.get_package(type)
|
87
|
+
require "assets_booster/package/base"
|
88
|
+
require "assets_booster/package/#{type}"
|
89
|
+
klass = "AssetsBooster::Package::#{type.to_s.camelize}"
|
90
|
+
klass.constantize
|
91
|
+
rescue LoadError => e
|
92
|
+
raise "You've specified an invalid package type '#{type}'"
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.get_compiler(name)
|
96
|
+
require "assets_booster/compiler/#{name}"
|
97
|
+
klass = "AssetsBooster::Compiler::#{name.to_s.camelize}"
|
98
|
+
klass.constantize
|
99
|
+
rescue LoadError => e
|
100
|
+
raise "You've specified an invalid compiler '#{name}'"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module AssetsBooster
|
2
|
+
module Merger
|
3
|
+
class CSS
|
4
|
+
cattr_accessor :assets
|
5
|
+
|
6
|
+
def self.name
|
7
|
+
"CSS Merger"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.merge(sources, target)
|
11
|
+
self.assets = []
|
12
|
+
sources.each do |source|
|
13
|
+
load_source(source)
|
14
|
+
end
|
15
|
+
|
16
|
+
target_folder = File.dirname(target)
|
17
|
+
assets.inject("") do |code, asset|
|
18
|
+
source_folder = File.dirname(asset[:source])
|
19
|
+
rewrite_urls!(asset[:css], source_folder, target_folder)
|
20
|
+
code << asset[:css]+"\n"
|
21
|
+
end.strip
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.mtime(sources)
|
25
|
+
self.assets = []
|
26
|
+
sources.each do |source|
|
27
|
+
load_source(source)
|
28
|
+
end
|
29
|
+
assets.map{ |asset| File.mtime(asset[:source]) }.max
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def self.load_source(source)
|
35
|
+
css = File.read(source)
|
36
|
+
source_folder = File.dirname(source)
|
37
|
+
css.gsub!(/@import\s+([^;\n\s]+)/).each do |import|
|
38
|
+
url = $1.gsub(/^url\((.+)\)/i, '\1')
|
39
|
+
url, quotes = extract_url(url.strip)
|
40
|
+
|
41
|
+
# we don't want to statically import external stylesheets
|
42
|
+
next import if absolute_url?(url)
|
43
|
+
|
44
|
+
# recursively process the imported css
|
45
|
+
load_source(source_folder+"/"+url)
|
46
|
+
""
|
47
|
+
end
|
48
|
+
self.assets << {
|
49
|
+
:source => source,
|
50
|
+
:css => css
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.rewrite_urls!(css, source_folder, target_folder)
|
55
|
+
# difference between the source and target location
|
56
|
+
url_prepend = source_folder[target_folder.length+1..-1]
|
57
|
+
return unless url_prepend
|
58
|
+
|
59
|
+
css.gsub!(/url\(([^)]+)\)/i) do |match|
|
60
|
+
url, quotes = extract_url($1.strip)
|
61
|
+
|
62
|
+
# we don't want to change references to external assets
|
63
|
+
next match if absolute_url?(url)
|
64
|
+
|
65
|
+
"url(#{quotes}#{url_prepend}/#{url}#{quotes})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.extract_url(quoted_url)
|
70
|
+
(quoted_url[0].chr =~ /["']/) ? [quoted_url.slice(1, quoted_url.length-2), quoted_url[0].chr] : [quoted_url, ""]
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.absolute_url?(url)
|
74
|
+
url[0].chr =~ /^(\/|https?:\/\/)/i
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|