var UglifyJS = require("uglify-js"); var transform = require("./transform").transform; var fs = require("fs"); function compress(sources, opts) { if (!opts) opts = {}; if (typeof sources == "string") sources = [{source: sources, file: ""}]; var toplevel = null; var literals = []; sources.forEach(function(pkg) { if (pkg.source == undefined && pkg.file) pkg.source = fs.readFileSync(pkg.file, "utf8"); if (/"disable compress"/.test(pkg.source)) return literals.push(pkg.source); var source = pkg.source; try { source = transform(source, { mode: "es5" }); } catch(e) { logParseError(e, pkg); throw e; } // if (pkg.file) console.log("Adding '" + pkg.file + "'."); try { toplevel = UglifyJS.parse(source, { filename: (pkg.file || pkg.id || "").replace(new RegExp("^" + opts.basepath + "/"), ""), //@todo remove prefix toplevel: toplevel }); } catch (e) { logParseError(e, pkg); throw e; } }); if (!toplevel) { return { code: literals.join("\n"), map: null }; } if (opts.compress == "dryrun") { return { code: "dryrun\n" }; } /** * UglifyJS contains a scope analyzer that you need to call manually before * compressing or mangling. Basically it augments various nodes in the AST * with information about where is a name defined, how many times is a name * referenced, if it is a global or not, if a function is using eval or the * with statement etc. I will discuss this some place else, for now what's * important to know is that you need to call the following before doing * anything with the tree: */ toplevel.figure_out_scope(); var compressor = UglifyJS.Compressor({ warnings: !!opts.verbose, unsafe: false }); var compressed_ast = toplevel.transform(compressor); /** * After compression it is a good idea to call again figure_out_scope * (since the compressor might drop unused variables / unreachable code and * this might change the number of identifiers or their position). * Optionally, you can call a trick that helps after Gzip (counting * character frequency in non-mangleable words). */ compressed_ast.figure_out_scope(); compressed_ast.compute_char_frequency(); // Right now this breaks the LogicBlox workspace; needs further investigation if (opts.obfuscate) { compressed_ast.mangle_names({except: ["$", "require", "exports", "initBaseUrls", "initSender"]}); } var outputOptions = opts.oneLine ? {} : { quote_keys : false, // quote all keys in object literals? space_colon : false, // add a space after colon signs? ascii_only : false, // output ASCII-safe? (encodes Unicode characters as ASCII) inline_script : false, // escape " MAX_LEN) { line = line.slice(col - MAX_LEN); col = MAX_LEN; } if (line.length > col + MAX_LEN) line = line.slice(0, col + MAX_LEN); line = line + "\n" + Array(col + 1).join(" ") + "^"; console.error(line); } } compress.withCache = function(sources, opts) { var cache = opts.cache || {}; if (cache && !cache.compress) cache.compress = Object.create(null); if (typeof sources == "string") sources = [{source: sources, file: ""}]; var code = sources.map(function(pkg) { if (pkg.id && cache.compress[pkg.id]) { // console.log("Compress Cache Hit " + pkg.id); return cache.compress[pkg.id]; } if (opts.exclude && opts.exclude.test(pkg.id)) return pkg.source; var code = compress([pkg], opts).code; if (/[([]/.test(code[0])) code = ";" + code; return (cache.compress[pkg.id] = code); }).join("\n"); return {code: code, map: null}; }; module.exports = compress; // console.log(compress([{source: "/*disable compress*/var a = 0; var b = 1+1;"}, {source: "var a = 0; var b = 1+1;"} ]))