assets_booster 0.0.1
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/.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
|
+
|