ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
288 lines (267 loc) • 8.71 kB
JavaScript
(function() {
/* jshint node: true */
var
fs = require("fs"),
path = require("path"),
walker = require("walker"),
uglify = require("uglify-js"),
nopt = require("nopt"),
less = require("less");
var basename = path.basename(__filename),
w = console.log,
e = console.error,
opt;
// Shimming path.relative with 0.8.8's version if it doesn't exist
if(!path.relative){
path.relative = require('./path-relative-shim').relative;
}
function printUsage() {
w("Enyo 2.0 Minifier");
w("Usage: " + __filename + " [Flags] [path/to/package.js]");
w("Flags:");
w("-no-less:", "Don't compile less; instad substitute css for less");
w("-no-alias:", "Don't use path macros");
w("-alias:", "Give paths a macroized alias");
w("-enyo ENYOPATH:", "Relative path to enyo folder (enyo)");
w("-lib LIBPATH:", "Relative path to lib folder ($enyo/../lib)");
w("-destdir DESTDIR:", "Target directory, prepended to any output file but skipped within generated files (current dir)");
w("-output RELPATH/PREFIX:", "Folder + output file prefix, relative to DESTDIR (build/out)");
w("-beautify:", "Output pretty version that's less compressed but has code on separate lines");
w("-f", "Remote source mapping: from local path");
w("-t", "Remote source mapping: to remote path");
w("-h, -?, -help:", "Show this message");
}
// properly split path based on platform
function pathSplit(inPath) {
var sep = process.platform == "win32" ? "\\" : "/";
return inPath.split(sep);
}
function concatCss(sheets, doneCB) {
w("");
var blob = "";
var addToBlob = function(sheet, code) {
// fix url paths
code = code.replace(/url\([^)]*\)/g, function(inMatch) {
// find the url path, ignore quotes in url string
var matches = /url\s*\(\s*(('([^']*)')|("([^"]*)")|([^'"]*))\s*\)/.exec(inMatch);
var urlPath = matches[3] || matches[5] || matches[6];
// handle the case url('') or url("").
if(!urlPath){
return "url()";
}
// skip data urls
if (/^data:/.test(urlPath)) {
return "url('" + urlPath + "')";
}
// skip an external link
if (/^http(:?s)?:/.test(urlPath)) {
return "url('" + urlPath + "'')";
}
// Make relative asset path from 'top-of-the-tree/build'
var relPath = path.join("..", opt.relsrcdir, path.dirname(sheet), urlPath);
if (process.platform == "win32") {
relPath = pathSplit(relPath).join("/");
}
console.log("opt.relsrcdir:", opt.relsrcdir);
console.log("sheet:", sheet);
console.log("urlPath:", urlPath);
console.log("relPath:", relPath);
return "url('" + relPath + "')";
});
blob += "\n/* " + path.relative(process.cwd(), sheet) + " */\n\n" + code + "\n";
};
// Pops one sheet off the sheets[] array, reads (and parses if less), and then
// recurses again from the async callback until no sheets left, then calls doneCB
function readAndParse() {
var sheet = sheets.shift();
if (sheet) {
w(sheet);
var isLess = (sheet.slice(-4) == "less");
if (isLess && (opt.less !== true)) {
sheet = sheet.slice(0, sheet.length-4) + "css";
isLess = false;
w(" (Substituting CSS: " + sheet + ")");
}
var code = fs.readFileSync(sheet, "utf8");
if (isLess) {
var parser = new(less.Parser)({filename:sheet, paths:[path.dirname(sheet)]});
parser.parse(code, function (err, tree) {
if (err) {
console.error(err);
} else {
addToBlob(sheet, tree.toCSS());
}
readAndParse(sheets);
});
} else {
addToBlob(sheet, code);
readAndParse(sheets);
}
} else {
doneCB(blob);
}
}
readAndParse();
}
var concatJs = function(loader, scripts) {
w("");
var blob = "";
for (var i=0, script; (script=scripts[i]); i++) {
w(script);
blob += "\n// " + path.relative(process.cwd(), script) + "\n" + compressJsFile(script) + "\n";
}
return blob;
};
var compressJsFile = function(inPath) {
var outputOpts = {
// beautify: false,
// indent_level: 4,
ascii_only: true
};
if (opt.beautify) {
outputOpts.beautify = true;
outputOpts.indent_level = 4;
}
var result = uglify.minify(inPath, {output: outputOpts});
return result.code;
};
var walkerFinished = function(loader, chunks) {
var outfolder = path.dirname(path.join(opt.destdir, opt.output));
var exists = fs.existsSync || path.existsSync;
var currChunk = 1;
var topDepends;
if (outfolder != "." && !exists(outfolder)) {
fs.mkdirSync(outfolder);
}
if ((chunks.length == 1) && (typeof chunks[0] == "object")) {
topDepends = false;
currChunk = "";
} else {
topDepends = [];
}
var processNextChunk = function(done) {
if (chunks.length > 0) {
var chunk = chunks.shift();
if (typeof chunk == "string") {
topDepends.push(chunk);
processNextChunk(done);
} else {
concatCss(chunk.sheets, function(css) {
if (css.length) {
w("");
var cssFile = opt.output + currChunk + ".css";
fs.writeFileSync(path.resolve(opt.destdir, cssFile), css, "utf8");
if (topDepends) {
topDepends.push(cssFile);
}
}
var js = concatJs(loader, chunk.scripts);
if (js.length) {
w("");
var jsFile = opt.output + currChunk + ".js";
fs.writeFileSync(path.resolve(opt.destdir, jsFile), js, "utf8");
if (topDepends) {
topDepends.push(jsFile);
}
}
currChunk++;
processNextChunk(done);
});
}
} else {
done();
}
};
processNextChunk(function() {
if (topDepends) {
var js = "";
// Add path aliases to the mapped sources
for (var i=0; i<opt.mapfrom.length; i++) {
js = js + "enyo.path.addPath(\"" + opt.mapfrom[i] + "\", \"" + opt.mapto[i] + "\");\n";
}
// Override the default rule that $lib lives next to $enyo, since enyo may be remote
js = js + "enyo.path.addPath(\"lib\", \"lib\");\n";
// Add depends for all of the top-level files
js = js + "enyo.depends(\n\t\"" + topDepends.join("\",\n\t\"") + "\"\n);";
fs.writeFileSync(path.resolve(opt.destdir, opt.output + ".js"), js, "utf8");
fs.writeFileSync(path.resolve(opt.destdir, opt.output + ".css"), "/* CSS loaded via enyo.depends() call in " + opt.output + ".js */", "utf8");
}
w("");
w("done.");
w("");
// required to properly terminate a
// node.process.fork() call, as defined by
// <http://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options>
process.exit(0);
});
};
var knownOpts = {
"alias": Boolean,
"enyo": String, // relative path
"lib": String, // relative path
"destdir": path, // absolute path (resolved by nopt)
"srcdir": path, // absolute path (resolved by nopt)
"output": String, // relative path
"help": Boolean,
"beautify": Boolean,
"mapfrom": [String, Array],
"mapto": [String, Array]
};
var shortHands = {
"alias": ['--alias'],
"enyo": ['--enyo'],
"lib": ['--lib'],
"srcdir": ['--srcdir'],
"destdir": ['--destdir'],
"output": ['--output'],
"h": ['--help'],
"?": ['--help'],
"help": ['--help'],
"beautify": ['--beautify'],
"f": ['--mapfrom'],
"t": ['--mapto']
};
opt = nopt(knownOpts, shortHands, process.argv, 2);
opt.packagejs = opt.argv.remain[0] || "package.js";
opt.srcdir = opt.srcdir || process.cwd();
if (opt.packagejs) {
// walker only works from top-level package.js...
process.chdir(path.dirname(opt.packagejs));
}
// ...but we still want to (relatively) track the top of the
// tree, because this is the root from which the LESS sheets
// are resolved (unlike the JS dependencies, which are
// resolved from the folder of the top-level package.js).
opt.relsrcdir = path.relative(opt.srcdir, process.cwd());
if (opt.help) {
printUsage();
process.exit();
}
// Send message to parent node process, if any
process.on('uncaughtException', function (err) {
e(err.stack);
if (process.send) {
// only available if parent-process is node
process.send({error: err});
}
process.exit(1);
});
// receive error messages from child node processes
process.on('message', function(msg) {
console.dir(basename, msg);
if (msg.error && msg.error.stack) {
console.error(basename, msg.error.stack);
}
if (process.send) {
process.send(msg);
}
});
opt.destdir = opt.destdir || process.cwd();
opt.output = opt.output || "build/out";
if (path.resolve(opt.output) === opt.output) {
throw new Error("-output must be a relative path prefix");
}
w(opt);
walker.init(opt.enyo, opt.lib || opt.enyo + "/../lib", opt.mapfrom, opt.mapto);
walker.walk(path.basename(opt.packagejs), walkerFinished);
})();