assets_booster 0.0.1

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