dojo-util
Version:
Dojo utilities including build system for optimizing JavaScript application performance, and DOH testing tool
289 lines (264 loc) • 9.85 kB
JavaScript
//
// The Dojo Build System
//
// This is application is implemented as an AMD package intended to be loaded and executed by dojo. It is known to work correctly
// with node.js (fast!) and rhino (slow!). The program may be started from a command prompt as follows:
//
// node.js:
// >node path/to/dojotoolkit/dojo/dojo.js load=build <arguments>
//
// rhino:
// >java -jar path/to/js.jar path/to/dojotoolkit/dojo/dojo.js baseUrl=path/to/dojotoolkit/dojo load=build <arguments>
//
// * notice that, owing to the defective design of rhino, it is impossible for a script to know the location from
// which it was executed; therefore, the baseUrl must be provided.
//
// util/buildscripts/bng:
// TODOC
//
// The application proceeds as follows:
//
// 1. Process the command line and then process the build control script(s)/profile as specified by the command line.
// 2. Discover all resources as instructed by the build control script
// 3. Move the resources through an ordered set of gates. Zero to many synchronous and/or asynchronous transforms may be applied to various
// resources as specified by the build control script. Different resources can be subject to different transforms. Resources are allowed
// to move through gates without stopping until a "synchronized" gate is encountered. All transforms must complete for the previous gate before
// any transform is allowed on the synchronized gate.
// 4. After the last gate has been completed, print a done message and terminate.
//
// See also:
//
// project home: http://bdframework.org/bdBuild/index
// fossil: http://bdframework.org/bdBuild/repo
// github: https://github.com/altoviso/bdBuild
// docs: http://bdframework.org/bdBuild/docs
define(["require", "dojo/has"], function(require, has){
// host-dependent environment initialization
if(has("host-node")){
define("commandLineArgs", function(){
//arg[0] is node; argv[1] is dojo.js; therefore, start with argv[2]
return process.argv.slice(2);
});
// helps during dev or heavily async node...
var util = require.nodeRequire("util");
debug = function(it, depth, inspect){
util.debug(inspect ? util.inspect(it, false, depth) : it);
};
has.add("is-windows", process.platform == "win32");
}else if(has("host-rhino")){
define("commandLineArgs", [], function(){
var result = [];
require.rawConfig.commandLineArgs.forEach(function(item){
var parts = item.split("=");
if(parts[0]!="baseUrl"){
result.push(item);
}
});
return result;
});
// TODO: make this real
has.add("is-windows", /indows/.test(environment["os.name"]));
}else{
console.log("unknown environment; terminating.");
return 0;
}
this.require.scopeify = function(moduleList){
for(var p, mid, module, text = "", contextRequire = this, args = moduleList.split(","), i = 0; i<args.length;){
mid = args[i++].match(/\S+/)[0];
module = contextRequire(mid);
mid = mid.match(/[^\/]+$/)[0];
for(p in module){
text+= "var " + p + "=" + mid + "." + p + ";\n";
}
}
return text;
};
// run the build program
require(["./buildControl", "./process"], function(bc, process){
var
gateListeners = bc.gateListeners = [],
transforms = bc.transforms,
transformJobs = bc.transformJobs,
transformJobsLength = transformJobs.length,
// all discovered resources
resources = [],
reportError = function(resource, err){
bc.log("transformFailed", ["resource", resource.src, "transform", resource.jobPos, "error", err]);
resource.error = true;
},
returnFromAsyncProc = function(resource, err){
bc.waiting--;
if(err){
// notice reportError can decide to continue or panic
reportError(resource, err);
}
advance(resource, true);
},
advance = function(resource, continuingSameGate){
if(resource.error){
return;
}
if(!continuingSameGate){
// first time trying to advance through the current gate
bc.waiting++;
}
// apply all transforms with a gateId <= the current gate for resource that have not yet been applied
var err, nextJobPos, candidate;
while(1){
nextJobPos = resource.jobPos + 1;
candidate = nextJobPos<resource.job.length && resource.job[nextJobPos];
// candidate (if any) is a [transformProc, gateId] pair
if(candidate && candidate[1]<=bc.currentGate){
resource.jobPos++;
bc.waiting++;
err = candidate[0](resource, returnFromAsyncProc);
if(err===returnFromAsyncProc){
// the transform proc must call returnFromAsyncProc when complete
return;
}
bc.waiting--;
if(err){
// notice we reportError can decide to continue or panic
reportError(resource, err);
// if reportError didn't panic, then this break will cause this resource to clear the next
// gate; when all resources have cleared the next gate, passGate will notice error count and
// quit
break;
}
}else{
break;
}
}
// got through the gate; advise passGate which will decrement the lock we set at top of this function
passGate();
},
advanceGate = function(currentGate){
while(1){
bc.currentGate = ++currentGate;
bc.log("pacify", "starting " + bc.gates[bc.currentGate][2] + "...");
gateListeners.forEach(function(listener){
listener(bc.gates[bc.currentGate][1]);
});
if(currentGate==bc.gates.length-1 || bc.gates[currentGate+1][0]){
// if we've either advanced to the last gate or the next gate is a synchronized gate, then hold at the current gate
return;
}
}
},
passGate = bc.passGate = function(){
if(--bc.waiting){
return;
} // else all processes have passed through bc.currentGate
if(bc.checkDiscovery){
//passing the first gate which is dicovery and just echoing discovery; therefore
process.exit(0);
}
if(bc.currentGate<bc.gates.length-1){
advanceGate(bc.currentGate);
// hold the next gate until all resources have been advised
bc.waiting++;
resources.forEach(function(resource){ advance(resource, 0); });
// release the hold placed above
passGate();
}else{
if(!resources.length){
bc.log("discoveryFailed");
}
bc.log("pacify", "Process finished normally.\n\terrors: " + bc.getErrorCount() + "\n\twarnings: " + bc.getWarnCount() + "\n\tbuild time: " + ((new Date()).getTime() - bc.startTimestamp.getTime()) / 1000 + " seconds");
if(!bc.exitCode && bc.getErrorCount()){
bc.exitCode = 1;
}
process.exit(bc.exitCode);
// that's all, folks...
}
};
bc.start = function(resource){
// check for collisions
var
src = resource.src,
dest = resource.dest;
if(bc.resourcesByDest[src]){
// a dest is scheduled to overwrite a source
bc.log("overwrite", ["input", src, "resource destined for same location: ", bc.resourcesByDest[src].src]);
return;
}
if(bc.resourcesByDest[dest]){
// multiple srcs scheduled to write into a single dest
if(src !== bc.resourcesByDest[dest].src){ // don't squawk if same file
bc.log("outputCollide", ["source-1", src, "source-2", bc.resourcesByDest[dest].src]);
}
return;
}
// remember the resources in the global maps
bc.resources[resource.src] = resource;
bc.resourcesByDest[resource.dest] = resource;
if(bc.checkDiscovery){
bc.log("pacify", src + "-->" + dest);
return;
}
// find the transformJob and start it...
for(var i = 0; i<transformJobsLength; i++){
if(transformJobs[i][0](resource, bc)){
// job gives a vector of functions to apply to the resource
// jobPos says the index in the job vector that has been applied
resources.push(resource);
resource.job = transformJobs[i][1];
resource.jobPos = -1;
advance(resource);
return;
}
}
bc.log("noTransform", ["resoures", resource.src]);
};
function doBuild(){
var
transformNames = [],
pluginNames = [],
deps = [];
bc.discoveryProcs.forEach(function(mid){ deps.push(mid); });
for(var p in bc.transforms){
// each item is a [AMD-MID, gateId] pair
transformNames.push(p);
deps.push(bc.transforms[p][0]);
}
for(p in bc.plugins){
pluginNames.push(p);
deps.push(bc.plugins[p]);
}
bc.plugins = {};
require(deps, function(){
// pull out the discovery procedures
for(var discoveryProcs = [], argsPos = 0; argsPos<bc.discoveryProcs.length; discoveryProcs.push(arguments[argsPos++]));
// replace the transformIds in the transformJobs with the actual transform procs; similarly for plugins
for(var id, proc, i=0; i<transformNames.length;){
id = transformNames[i++];
proc = arguments[argsPos++];
// replace every occurence of id with proc
transformJobs.forEach(function(item){
// item is a [predicate, vector of [transformId, gateId] pairs] pairs
for(var transforms=item[1], i = 0; i<transforms.length; i++){
if(transforms[i][0]==id){
transforms[i][0] = proc;
break;
}
}
});
}
for(i=0; i<pluginNames.length;){
bc.plugins[bc.getSrcModuleInfo(pluginNames[i++]).mid] = arguments[argsPos++];
}
// start the transform engine: initialize bc.currentGate and bc.waiting, then discover and start each resource.
// note: discovery procs will call bc.start with each discovered resource, which will call advance, which will
// enter each resource in a race to the next gate, which will result in many bc.waiting incs/decs
bc.waiting = 1; // matches *1*
bc.log("pacify", "discovering resources...");
advanceGate(-1);
discoveryProcs.forEach(function(proc){ proc(); });
passGate(); // matched *1*
});
}
if(!bc.getErrorCount() && bc.release){
doBuild();
}
});
});