1// workaround for tty output truncation upon process.exit() 2[process.stdout, process.stderr].forEach(function(stream){ 3 if (stream._handle && stream._handle.setBlocking) 4 stream._handle.setBlocking(true); 5}); 6 7var path = require("path"); 8var fs = require("fs"); 9 10var UglifyJS = exports; 11var FILES = UglifyJS.FILES = [ 12 "../lib/utils.js", 13 "../lib/ast.js", 14 "../lib/parse.js", 15 "../lib/transform.js", 16 "../lib/scope.js", 17 "../lib/output.js", 18 "../lib/compress.js", 19 "../lib/sourcemap.js", 20 "../lib/mozilla-ast.js", 21 "../lib/propmangle.js", 22 "./exports.js", 23].map(function(file){ 24 return require.resolve(file); 25}); 26 27new Function("MOZ_SourceMap", "exports", FILES.map(function(file){ 28 return fs.readFileSync(file, "utf8"); 29}).join("\n\n"))( 30 require("source-map"), 31 UglifyJS 32); 33 34UglifyJS.AST_Node.warn_function = function(txt) { 35 console.error("WARN: %s", txt); 36}; 37 38function read_source_map(code) { 39 var match = /\n\/\/# sourceMappingURL=data:application\/json(;.*?)?;base64,(.*)/.exec(code); 40 if (!match) { 41 UglifyJS.AST_Node.warn("inline source map not found"); 42 return null; 43 } 44 return JSON.parse(new Buffer(match[2], "base64")); 45} 46 47UglifyJS.minify = function(files, options) { 48 options = UglifyJS.defaults(options, { 49 compress : {}, 50 fromString : false, 51 inSourceMap : null, 52 mangle : {}, 53 mangleProperties : false, 54 nameCache : null, 55 outFileName : null, 56 output : null, 57 outSourceMap : null, 58 parse : {}, 59 sourceMapInline : false, 60 sourceMapUrl : null, 61 sourceRoot : null, 62 spidermonkey : false, 63 warnings : false, 64 }); 65 UglifyJS.base54.reset(); 66 67 var inMap = options.inSourceMap; 68 if (typeof inMap == "string" && inMap != "inline") { 69 inMap = JSON.parse(fs.readFileSync(inMap, "utf8")); 70 } 71 72 // 1. parse 73 var toplevel = null, 74 sourcesContent = {}; 75 76 if (options.spidermonkey) { 77 if (inMap == "inline") { 78 throw new Error("inline source map only works with built-in parser"); 79 } 80 toplevel = UglifyJS.AST_Node.from_mozilla_ast(files); 81 } else { 82 function addFile(file, fileUrl) { 83 var code = options.fromString 84 ? file 85 : fs.readFileSync(file, "utf8"); 86 if (inMap == "inline") { 87 inMap = read_source_map(code); 88 } 89 sourcesContent[fileUrl] = code; 90 toplevel = UglifyJS.parse(code, { 91 filename: fileUrl, 92 toplevel: toplevel, 93 bare_returns: options.parse ? options.parse.bare_returns : undefined 94 }); 95 } 96 if (!options.fromString) { 97 files = UglifyJS.simple_glob(files); 98 if (inMap == "inline" && files.length > 1) { 99 throw new Error("inline source map only works with singular input"); 100 } 101 } 102 [].concat(files).forEach(function (files, i) { 103 if (typeof files === 'string') { 104 addFile(files, options.fromString ? i : files); 105 } else { 106 for (var fileUrl in files) { 107 addFile(files[fileUrl], fileUrl); 108 } 109 } 110 }); 111 } 112 if (options.wrap) { 113 toplevel = toplevel.wrap_commonjs(options.wrap, options.exportAll); 114 } 115 116 // 2. compress 117 if (options.compress) { 118 var compress = { warnings: options.warnings }; 119 UglifyJS.merge(compress, options.compress); 120 toplevel.figure_out_scope(options.mangle); 121 var sq = UglifyJS.Compressor(compress); 122 toplevel = sq.compress(toplevel); 123 } 124 125 // 3. mangle properties 126 if (options.mangleProperties || options.nameCache) { 127 options.mangleProperties.cache = UglifyJS.readNameCache(options.nameCache, "props"); 128 toplevel = UglifyJS.mangle_properties(toplevel, options.mangleProperties); 129 UglifyJS.writeNameCache(options.nameCache, "props", options.mangleProperties.cache); 130 } 131 132 // 4. mangle 133 if (options.mangle) { 134 toplevel.figure_out_scope(options.mangle); 135 toplevel.compute_char_frequency(options.mangle); 136 toplevel.mangle_names(options.mangle); 137 } 138 139 // 5. output 140 var output = { max_line_len: 32000 }; 141 if (options.outSourceMap || options.sourceMapInline) { 142 output.source_map = UglifyJS.SourceMap({ 143 // prefer outFileName, otherwise use outSourceMap without .map suffix 144 file: options.outFileName || (typeof options.outSourceMap === 'string' ? options.outSourceMap.replace(/\.map$/i, '') : null), 145 orig: inMap, 146 root: options.sourceRoot 147 }); 148 if (options.sourceMapIncludeSources) { 149 for (var file in sourcesContent) { 150 if (sourcesContent.hasOwnProperty(file)) { 151 output.source_map.get().setSourceContent(file, sourcesContent[file]); 152 } 153 } 154 } 155 156 } 157 if (options.output) { 158 UglifyJS.merge(output, options.output); 159 } 160 var stream = UglifyJS.OutputStream(output); 161 toplevel.print(stream); 162 163 164 var source_map = output.source_map; 165 if (source_map) { 166 source_map = source_map + ""; 167 } 168 169 var mappingUrlPrefix = "\n//# sourceMappingURL="; 170 if (options.sourceMapInline) { 171 stream += mappingUrlPrefix + "data:application/json;charset=utf-8;base64," + new Buffer(source_map).toString("base64"); 172 } else if (options.outSourceMap && typeof options.outSourceMap === "string" && options.sourceMapUrl !== false) { 173 stream += mappingUrlPrefix + (typeof options.sourceMapUrl === "string" ? options.sourceMapUrl : options.outSourceMap); 174 } 175 176 return { 177 code : stream + "", 178 map : source_map 179 }; 180}; 181 182// UglifyJS.describe_ast = function() { 183// function doitem(ctor) { 184// var sub = {}; 185// ctor.SUBCLASSES.forEach(function(ctor){ 186// sub[ctor.TYPE] = doitem(ctor); 187// }); 188// var ret = {}; 189// if (ctor.SELF_PROPS.length > 0) ret.props = ctor.SELF_PROPS; 190// if (ctor.SUBCLASSES.length > 0) ret.sub = sub; 191// return ret; 192// } 193// return doitem(UglifyJS.AST_Node).sub; 194// } 195 196UglifyJS.describe_ast = function() { 197 var out = UglifyJS.OutputStream({ beautify: true }); 198 function doitem(ctor) { 199 out.print("AST_" + ctor.TYPE); 200 var props = ctor.SELF_PROPS.filter(function(prop){ 201 return !/^\$/.test(prop); 202 }); 203 if (props.length > 0) { 204 out.space(); 205 out.with_parens(function(){ 206 props.forEach(function(prop, i){ 207 if (i) out.space(); 208 out.print(prop); 209 }); 210 }); 211 } 212 if (ctor.documentation) { 213 out.space(); 214 out.print_string(ctor.documentation); 215 } 216 if (ctor.SUBCLASSES.length > 0) { 217 out.space(); 218 out.with_block(function(){ 219 ctor.SUBCLASSES.forEach(function(ctor, i){ 220 out.indent(); 221 doitem(ctor); 222 out.newline(); 223 }); 224 }); 225 } 226 }; 227 doitem(UglifyJS.AST_Node); 228 return out + ""; 229}; 230 231function readReservedFile(filename, reserved) { 232 if (!reserved) { 233 reserved = { vars: [], props: [] }; 234 } 235 var data = fs.readFileSync(filename, "utf8"); 236 data = JSON.parse(data); 237 if (data.vars) { 238 data.vars.forEach(function(name){ 239 UglifyJS.push_uniq(reserved.vars, name); 240 }); 241 } 242 if (data.props) { 243 data.props.forEach(function(name){ 244 UglifyJS.push_uniq(reserved.props, name); 245 }); 246 } 247 return reserved; 248} 249 250UglifyJS.readReservedFile = readReservedFile; 251 252UglifyJS.readDefaultReservedFile = function(reserved) { 253 return readReservedFile(require.resolve("./domprops.json"), reserved); 254}; 255 256UglifyJS.readNameCache = function(filename, key) { 257 var cache = null; 258 if (filename) { 259 try { 260 var cache = fs.readFileSync(filename, "utf8"); 261 cache = JSON.parse(cache)[key]; 262 if (!cache) throw "init"; 263 cache.props = UglifyJS.Dictionary.fromObject(cache.props); 264 } catch(ex) { 265 cache = { 266 cname: -1, 267 props: new UglifyJS.Dictionary() 268 }; 269 } 270 } 271 return cache; 272}; 273 274UglifyJS.writeNameCache = function(filename, key, cache) { 275 if (filename) { 276 var data; 277 try { 278 data = fs.readFileSync(filename, "utf8"); 279 data = JSON.parse(data); 280 } catch(ex) { 281 data = {}; 282 } 283 data[key] = { 284 cname: cache.cname, 285 props: cache.props.toObject() 286 }; 287 fs.writeFileSync(filename, JSON.stringify(data, null, 2), "utf8"); 288 } 289}; 290 291// A file glob function that only supports "*" and "?" wildcards in the basename. 292// Example: "foo/bar/*baz??.*.js" 293// Argument `glob` may be a string or an array of strings. 294// Returns an array of strings. Garbage in, garbage out. 295UglifyJS.simple_glob = function simple_glob(glob) { 296 if (Array.isArray(glob)) { 297 return [].concat.apply([], glob.map(simple_glob)); 298 } 299 if (glob.match(/\*|\?/)) { 300 var dir = path.dirname(glob); 301 try { 302 var entries = fs.readdirSync(dir); 303 } catch (ex) {} 304 if (entries) { 305 var pattern = "^" + path.basename(glob) 306 .replace(/[.+^$[\]\\(){}]/g, "\\$&") 307 .replace(/\*/g, "[^/\\\\]*") 308 .replace(/\?/g, "[^/\\\\]") + "$"; 309 var mod = process.platform === "win32" ? "i" : ""; 310 var rx = new RegExp(pattern, mod); 311 var results = entries.filter(function(name) { 312 return rx.test(name); 313 }).map(function(name) { 314 return path.join(dir, name); 315 }); 316 if (results.length) return results; 317 } 318 } 319 return [ glob ]; 320}; 321