Skip to content

Instantly share code, notes, and snippets.

@kaustubh-karkare
Last active December 27, 2015 12:29
Show Gist options
  • Save kaustubh-karkare/7326488 to your computer and use it in GitHub Desktop.
Save kaustubh-karkare/7326488 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
var fs = require("fs"), path = require("path");
var run = function(root,start){
var fs = require("fs"), path = require("path"); // intentionally redefined
var ext = ".js", index = "index"+ext, pkg = "package.json";
var locate = function(root,name){
var p = name.split(path.sep);
// all relative module references must be inside the specified directory
if(p[0]==="..") throw new Error("Out of Scope");
// navigate to the appropriate location
for(var current = root; p.length>1; p.splice(0,1)) current = current[p[0]];
// select module, appending the extension if required
return current[p[0]+(p[0]+ext in current ? ext : "")];
};
return (function(name,relative){
if(typeof(name)!="string" || name==="" || name.split(path.sep).length<2 ){
// in case of invalid input or external modules
return require(name);
} else {
var relname = path.normalize(path.join(relative,name)),
current = locate(root,relname);
// if this is a directory and a package.json exists, use the main value
check: try {
if(typeof(current)!=="object") break check;
var data = JSON.parse(current[pkg]);
if(typeof(data.main)!=="string") break check;
current = locate(current,path.normalize(data.main));
relname = path.join(relname,data.main);
} catch(e){ }
// if this is a directory, load the index file by default
if(typeof(current)==="object"){
current = current[index];
relname = path.join(relname,index);
}
// load the module appropriately
if(typeof(current)==="function"){
var module = { exports:{}, filename: relname },
f1 = arguments.callee,
f2 = function(x){ return f1(x,path.dirname(relname)) };
return current.cache || (current.cache = current(module, f2));
} else if(typeof(current)==="string"){
return current;
} else throw new Error("Invalid Path");
}
})("."+path.sep+start,"."); // start execution
};
var compress = (function(){
try {
var uglify = require("uglify-js"),
options = {
fromString: true,
reserved: ["module","require"]
};
} catch(e) {
var uglify = null;
}
return function(str){
if(!uglify) return str;
return "(" + uglify.minify("_="+str,options).code.slice(2,-1) + ")";
};
})();
var load = (function(){
var ext = ".js",
prefix = "(function(module,require){\ndelete arguments;",
suffix = "\nreturn module.exports; })";
// scan the directory recursively and read data into a JSON object
return function(current,options){
var name, result = {}, list = fs.readdirSync(current);
// for each item in directory
for(var i=0; i<list.length; ++i){
var fullpath = path.join(current,name=list[i]),
stats = fs.statSync(fullpath),
relpath = path.relative(options.basedir,fullpath);
// skip if found in exclusion list
if(options.exclude.filter(function(x){ return relpath.match(x); }).length) continue;
// if(options.exclude.indexOf( relpath )!=-1) continue;
if(stats.isDirectory()){
// recursively load if this is a directory
result[name] = arguments.callee( fullpath, options );
} else if(stats.isFile()){
// read file and if executable, evaluate the wrapper function
result[name] = fs.readFileSync(fullpath).toString();
if(name.slice(-ext.length)===ext)
try { result[name] = eval(compress(prefix+result[name]+suffix)); }
catch(e){ }
}
}
return result;
};
})();
var combine = (function(){
var stringify = function(x){
// to prevent JSON.stringify from quoting <function>.toString()
if(typeof(x)==="function") return x.toString();
if(typeof(x)!="object") return JSON.stringify(x);
return "{"+Object.keys(x).map(function(key){
return JSON.stringify(key)+":"+stringify(x[key]);
}).join(",")+"}";
};
return function(directory,options,start){
// ensure that directory and start-file exist
if( !fs.statSync(directory).isDirectory() ||
!fs.statSync(path.join(directory,start)).isFile() )
throw new Error("Invalid Options");
// options sanitization
if(typeof(options)!="object" || Array.isArray(options)) options = {};
options.basedir = directory;
options.exclude = !Array.isArray(options.exclude) ? [] :
options.exclude.filter(function(x){
return typeof(x)==="string" || x instanceof RegExp; });
// generate output
var x = compress("("+run.toString()+")"),
y = stringify(load(directory,options));
return x+"("+y+","+stringify(start)+")";
};
})();
if(!module.parent){ // main
var argv = require("optimist").default("e",[".git","node_modules"]).argv;
if(argv._.length<3){
console.log("\nIntent : Combine and compress multiple JavaScript files into a single equivalent file.");
console.log("Usage : "+argv.$0+" <directory> <main-script> <output-file> (-e <exclude-path>)* [-r]\n");
} else {
argv.e = Array.isArray(argv.e) ? argv.e : [argv.e];
var dir = argv._[0], start = argv._[1], target = argv._[2];
var output = combine(dir,{exclude:argv.e},start);
if(target==="-") process.stdout.write(output+"\n");
else fs.writeFile(target,output);
if(argv.r) eval(output);
}
} else { // api
module.exports = combine;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment