dojo-util
Version:
Dojo utilities including build system for optimizing JavaScript application performance, and DOH testing tool
760 lines (688 loc) • 24.7 kB
JavaScript
define([
"require",
"dojo/_base/lang",
"./argv",
"./fs",
"./fileUtils",
"./buildControlDefault",
"./v1xProfiles",
"./stringify",
"./process",
"./messages",
"dojo/text!./help.txt"
], function(require, lang, argv, fs, fileUtils, bc, v1xProfiles, stringify, process, messages, helpText){
//
// Process the arguments given on the command line to build up a profile object that is used to instruct and control
// the build process.
//
// This modules is a bit tedious. Is methodically goes through each option set, cleaning and conditioning user input making it
// easy to use for the remainder of the program. Readers are advised to tackle it top-to-bottom. There is no magic...just
// a whole bunch of imperative programming.
//
if(!isNaN(argv)){
// if argv is a number, then it's an exit code
bc.exitCode = argv;
return bc;
}
eval(require.scopeify("./fs, ./fileUtils, ./v1xProfiles"));
var
isString = function(it){
return typeof it === "string";
},
isNonemptyString = function(it){
return isString(it) && it.length;
},
isDefined = function(it){
return typeof it !="undefined";
},
cleanupFilenamePair = function(item, srcBasePath, destBasePath, hint){
var result;
if(isString(item)){
result = [computePath(item, srcBasePath), computePath(item, destBasePath)];
}else{
result = [computePath(item[0], srcBasePath), computePath(item[1], destBasePath)].concat(item.slice(2));
}
if(!isAbsolutePath(result[0]) || !isAbsolutePath(result[1])){
bc.log("inputInvalidPath", ["path", item, "hint", hint]);
}
return result;
},
slashTerminate = function(path){
return path + /\/$/.test(path) ? "" : "/";
},
isEmpty = function(it){
for(var p in it) return false;
return true;
},
cleanDeprecated = function(o, inputFile){
var deprecated = [];
for(var p in o){
if(/^(log|loader|xdDojoPath|scopeDjConfig|xdScopeArgs|xdDojoScopeName|expandProvide|buildLayers|query|removeDefaultNameSpaces|addGuards)$/.test(p)){
deprecated.push(p);
bc.log("inputDeprecated", ["switch", p, inputFile]);
}
}
deprecated.forEach(function(p){
delete o[p];
});
},
mix = function(dest, src){
dest = dest || {};
src = src || {};
for(var p in src) dest[p] = src[p];
return dest;
},
mixPackage = function(packageInfo){
var name = packageInfo.name;
bc.packageMap[name] = mix(bc.packageMap[name], packageInfo);
},
// mix a profile object into the global profile object
mixProfileObject = function(src){
cleanDeprecated(src, src.selfFilename);
// the profile properties...
// paths, plugins, transforms, staticHasFeatures
// ...are mixed one level deep; messageCategories, messages, packages, and packagePaths require special handling; all others are over-written
// FIXME: the only way to modify the transformJobs vector is to create a whole new vector?
for(var p in src){
if(!/paths|plugins|messages|transforms|staticHasFeatures|packages|packagePaths|defaultConfig/.test(p)){
bc[p] = src[p];
}
}
// the one-level-deep mixers
["paths","plugins","transforms","staticHasFeatures"].forEach(function(p){
bc[p] = mix(bc[p], src[p]);
});
// messages require special handling
if(src.messageCategories){
for(p in src.messageCategories){
bc.addCategory(p, src.messageCategories[p]);
}
}
(src.messages || []).forEach(function(item){bc.addMessage.apply(bc, item);});
// packagePaths and packages require special processing to get their contents into packageMap; do that first...
// process packagePaths before packages before packageMap since packagePaths is less specific than
// packages is less specific than packageMap. Notice that attempts to edit an already-existing package
// only edits specific package properties given (see mixPackage, above)
for(var base in src.packagePaths){
src.packagePaths[base].forEach(function(packageInfo){
if(isString(packageInfo)){
packageInfo = {name:packageInfo};
}
packageInfo.location = catPath(base, packageInfo.name);
mixPackage(packageInfo);
});
}
(src.packages || []).forEach(function(packageInfo){
if(isString(packageInfo)){
packageInfo = {name:packageInfo};
}
mixPackage(packageInfo);
});
// defaultConfig requires special handling
for(p in src.defaultConfig){
if(p=="hasCache"){
mix(bc.defaultConfig.hasCache, src.defaultConfig.hasCache);
}else{
bc.defaultConfig[p] = src.defaultConfig[p];
}
}
};
argv.args.profiles.forEach(function(item){
var temp = mix({}, item),
build = item.build;
delete temp.build;
mixProfileObject(temp);
build && mixProfileObject(build);
});
cleanDeprecated(argv.args, "command line");
// lastly, explicit command line switches override any evaluated profile objects
for(var argName in argv.args) if(argName!="profiles"){
bc[argName] = argv.args[argName];
}
// at this point the raw profile object has been fully initialized; clean it up and look for errors...
bc.basePath = computePath(bc.basePath, process.cwd());
var releaseDir = catPath(bc.releaseDir || "../release", bc.releaseName || "");
bc.destBasePath = computePath(releaseDir, bc.basePath);
// compute global copyright, if any
bc.copyright = isNonemptyString(bc.copyright) ? (maybeRead(computePath(bc.copyright, bc.basePath)) || bc.copyright) : "";
bc.copyrightLayers = !!bc.copyrightLayers;
bc.copyrightNonlayers = !!bc.copyrightNonlayers;
// compute files, dirs, and trees
(function(){
for(var property in {files:1, dirs:1, trees:1}){
if(bc[property] instanceof Array){
bc[property] = bc[property].map(function(item){
return cleanupFilenamePair(item, bc.basePath, bc.destBasePath, property);
});
}
}
})();
// cleanup the replacements (if any)
(function(){
var cleanSet = {}, src, dest;
for(src in bc.replacements){
cleanSet[computePath(src, bc.basePath)] = bc.replacements[src];
}
bc.replacements = cleanSet;
})();
// explicit mini and/or copyTests wins; explicit copyTests ignores explicit mini
if(!("mini" in bc)){
bc.mini = true;
}
if(!("copyTests" in bc)){
bc.copyTests = !bc.mini;
}
if(isString(bc.copyTests)){
bc.copyTests = bc.copyTests.toLowerCase();
}
if(bc.copyTests!="build"){
// convert to pure boolean
bc.copyTests = !!bc.copyTests;
}
function getDiscreteLocales(locale){
for(var locales = locale.split("-"), result = [], current = "", i = 0; i<locales.length; i++){
result.push(current += (i ? "-" : "") + locales[i]);
}
return result;
}
if(isString(bc.localeList)){
bc.localeList = bc.localeList.split(",");
if(bc.localeList.length){
bc.localeList = bc.localeList.map(function(locale){ return lang.trim(locale); });
}
}
if(bc.localeList && bc.localeList.length){
if(bc.localeList.indexOf("ROOT")==-1){
bc.localeList.push("ROOT");
}
var localeList = {};
bc.localeList.forEach(function(locale){
locale = lang.trim(locale);
localeList[locale] = getDiscreteLocales(locale);
});
bc.localeList.discreteLocales = localeList;
}else{
bc.localeList = false;
}
(function(){
function processPackage(pack){
var packName = pack.name,
basePath = pack.basePath || bc.basePath;
if(!pack.packageJson){
pack.packageJson = argv.readPackageJson(catPath(computePath(pack.location || ("./" + packName), basePath), "package.json"), "missingPackageJson");
}
var packageJson = pack.packageJson;
if(packageJson){
if(packageJson.version){
bc.log("packageVersion", ["package", packName, "version", packageJson.version]);
// new for 1.7, if version is not provided, the version of the dojo package is used
if(typeof bc.version=="undefined" && packName=="dojo"){
bc.version = packageJson.version;
}
}
if(packageJson.main && !pack.main){
pack.main= packageJson.main;
}
if(packageJson.directories && packageJson.directories.lib && !pack.location){
pack.location = catPath(getFilepath(packageJson.selfFilename), packageJson.directories.lib);
}
if("dojoBuild" in packageJson){
var defaultProfile = argv.readProfile("profile", catPath(getFilepath(packageJson.selfFilename), packageJson.dojoBuild));
for(var p in defaultProfile){
if(!(p in pack)){
pack[p] = defaultProfile[p];
}else if(p in {resourceTags:1}){
// these are mixed one level deep
// TODO: review all profile properties and see if there are any others than resourceTags that ought to go here
mix(pack[p], defaultProfile[p]);
}
}
}else{
bc.log("missingProfile", ["package", packageJson.name]);
}
}
// build up info to tell all about a package; all properties semantically identical to definitions used by dojo loader/bdLoad
pack.main = isString(pack.main) ? pack.main : "main";
if(pack.main.indexOf("./")===0){
pack.main = pack.main.substring(2);
}
if(pack.destMain && pack.destMain.indexOf("./")===0){
pack.destMain = pack.destMain.substring(2);
}
pack.location = computePath(pack.location || ("./" + packName), basePath);
pack.copyright = isNonemptyString(pack.copyright) ?
(maybeRead(computePath(pack.copyright, pack.location)) || maybeRead(computePath(pack.copyright, bc.basePath)) || pack.copyright) :
(pack.copyright ? bc.copyright : "");
pack.copyrightLayers = isDefined(pack.copyrightLayers) ? !!pack.copyrightLayers : bc.copyrightLayers;
pack.copyrightNonlayers = isDefined(pack.copyrightNonlayers) ? !!pack.copyrightNonlayers : bc.copyrightNonlayers;
// dest says where to output the compiled code stack
var destPack = bc.destPackages[packName] = {
name:pack.destName || packName,
main:pack.destMain || pack.main,
location:computePath(pack.destLocation || ("./" + (pack.destName || packName)), bc.destBasePath)
};
delete pack.destName;
delete pack.destMain;
delete pack.destLocation;
if(!pack.trees){
// copy the package tree; don't copy any hidden directorys (e.g., .git, .svn) or temp files
pack.trees = [[pack.location, destPack.location, /(\/\.)|(^\.)|(~$)/]];
} // else the user has provided explicit copy instructions
// filenames, dirs, trees just like global, except relative to the pack.(src|dest)Location
for(var property in {files:1, dirs:1, trees:1}){
pack[property] = (pack[property] || []).map(function(item){
return cleanupFilenamePair(item, pack.location, destPack.location, property + " in package " + packName);
});
}
}
// so far, we've been using bc.packageMap to accumulate package info as it is provided by packagePaths and/or packages
// in zero to many profile scripts. This routine moves each package config into bc.packages which is a map
// from package name to package config (this is different from the array the user uses to pass package config info). Along
// the way, each package config object is cleaned up and all default values are calculated.
bc.packages = bc.packageMap;
delete bc.packageMap;
bc.destPackages = {};
for(var packageName in bc.packages){
var pack = bc.packages[packageName];
pack.name = pack.name || packageName;
processPackage(pack);
}
// now that we know the dojo path, we can automatically add DOH, if required
if(bc.copyTests && !bc.packages.doh){
bc.packages.doh = {
name:"doh",
location:compactPath(bc.packages.dojo.location + "/../util/doh"),
destLocation:"util/doh"
};
processPackage(bc.packages.doh);
}
// get this done too...
require.computeAliases(bc.aliases, (bc.aliasesMap = []));
require.computeMapProg(bc.paths, (bc.pathsMapProg = []));
bc.mapProgs = [];
require.computeMapProg(bc.map, bc.mapProgs);
bc.mapProgs.forEach(function(item){
item[1] = require.computeMapProg(item[1], []);
if(item[0]=="*"){
bc.mapProgs.star = item;
}
});
// add some methods to bc to help with resolving AMD module info
bc.srcModules = {};
bc.destModules = {};
var trimLastChars = function(text, n){
return text.substring(0, text.length-n);
};
bc.getSrcModuleInfo = function(mid, referenceModule, ignoreFileType){
if(ignoreFileType){
var result = require.getModuleInfo(mid+"/x", referenceModule, bc.packages, bc.srcModules, bc.basePath + "/", bc.mapProgs, bc.pathsMapProg, bc.aliasesMap, true);
result.mid = trimLastChars(result.mid, 2);
if(result.pid!==0){
// trim /x.js
result.url = trimLastChars(result.url, 5);
}
return result;
}else{
return require.getModuleInfo(mid, referenceModule, bc.packages, bc.srcModules, bc.basePath + "/", bc.mapProgs, bc.pathsMapProg, bc.aliasesMap, true);
}
};
bc.getDestModuleInfo = function(mid, referenceModule, ignoreFileType){
// notice no mapping, paths, aliases in the dest getModuleInfo....just send stuff to where it should go without manipulation
if(ignoreFileType){
var result = require.getModuleInfo(mid+"/x", referenceModule, bc.destPackages, bc.destModules, bc.destBasePath + "/", [], [], [], true);
result.mid = trimLastChars(result.mid, 2);
if(result.pid!==0){
// trim /x.js
result.url = trimLastChars(result.url, 5);
}
return result;
}else{
return require.getModuleInfo(mid, referenceModule, bc.destPackages, bc.destModules, bc.destBasePath + "/", [], [], [], true);
}
};
bc.getAmdModule = function(
mid,
referenceModule
){
var match = mid.match(/^([^\!]+)\!(.*)$/);
if(match){
var pluginModuleInfo = bc.getSrcModuleInfo(match[1], referenceModule),
pluginModule = pluginModuleInfo && bc.amdResources[pluginModuleInfo.mid],
pluginId = pluginModule && pluginModule.mid,
pluginProc = bc.plugins[pluginId];
if(!pluginModule){
return 0;
}else if(!pluginProc){
if(!pluginModule.noBuildResolver){
bc.log("missingPluginResolver", ["module", referenceModule.mid, "plugin", pluginId]);
}
return pluginModule;
}else{
// flatten the list of modules returned from the plugin
var modules = [].concat(pluginProc.start(match[2], referenceModule, bc));
return modules.concat.apply([], modules);
}
}else{
var moduleInfo = bc.getSrcModuleInfo(mid, referenceModule),
module = moduleInfo && bc.amdResources[moduleInfo.mid];
return module;
}
};
})();
if(bc.selectorEngine && bc.defaultConfig && bc.defaultConfig.hasCache){
bc.defaultConfig.hasCache["config-selectorEngine"] = bc.selectorEngine;
}
(function(){
// a layer is a module that should be written with all of its dependencies, as well as all modules given in
// the include vector together with their dependencies, excluding modules contained in the exclude vector and their dependencies
var layer, fixedLayers = {};
for(var mid in bc.layers){
layer = bc.layers[mid];
layer.exclude = layer.exclude || [];
layer.include = layer.include || [];
layer.boot = !!layer.boot;
layer.discard = !!layer.discard;
layer.compat = layer.compat!==undefined ? layer.compat : (bc.layerCompat ||"");
layer.noref = !!(layer.noref!==undefined ? layer.noref : (layer.compat=="1.6" ? true : bc.noref));
var tlm = mid.split("/")[0],
pack = bc.packages[tlm],
packLocation = pack && pack.location,
packCopyright = pack && pack.copyright,
packCopyrightLayers = pack && pack.copyrightLayers;
if(isNonemptyString(layer.copyright)){
// if relative, first try basePath, then try package location, otherwise, just use what's given
layer.copyright = (packLocation && maybeRead(computePath(layer.copyright, packLocation))) || maybeRead(computePath(layer.copyright, bc.basePath)) || layer.copyright;
}else if(isDefined(layer.copyright)){
// some kind of truthy other than a string
layer.copyright = layer.copyright ? (packCopyright || bc.copyright) : "";
}else{
layer.copyright = pack ? (packCopyrightLayers && (packCopyright || bc.copyright)) : (bc.copyrightLayers && bc.copyright);
}
if(!layer.copyright){
layer.copyright = "";
}
fixedLayers[mid] = layer;
}
bc.layers = fixedLayers;
// if (and only if) we're doing a build that includes the dojo tree, then ensure the loader layer is defined correctly
// and make sure all other layers exclude the loader unless they are marked with custome base
if(bc.packages.dojo){
if(!bc.layers["dojo/dojo"]){
bc.layers["dojo/dojo"] = {name:"dojo/dojo", copyright:bc.defaultCopyright + bc.defaultBuildNotice, include:["dojo/main"], exclude:[]};
}
for(var p in bc.layers){
layer = bc.layers[p];
if(p=="dojo/dojo"){
if(!layer.customBase){
// the purpose of the layer is to simply add some additional modules to a standard dojo boot
if(layer.include.indexOf("dojo/main")==-1){
layer.include.push("dojo/main");
}
}else{
// this is a custom base dojo.js; it's up the the user to say exactly what they want
}
}else{
if((layer.boot || !layer.customBase) && layer.exclude.indexOf("dojo/dojo")==-1){
// the layer has dojo/dojo if it is booting, or assumes dojo/dojo if its not explicitly saying customBase
layer.exclude.push("dojo/dojo");
}
// by definition...
layer.customBase = layer.boot;
}
}
}
})();
// for the static has flags, -1 means its not static; this gives a way of combining several static has flag sets
// and still allows later sets to delete flags set in earlier sets
var deleteStaticHasFlagSet = [];
for(var p in bc.staticHasFeatures) if(bc.staticHasFeatures[p]==-1) deleteStaticHasFlagSet.push(p);
deleteStaticHasFlagSet.forEach(function(flag){delete bc.staticHasFeatures[flag];});
if(bc.action){
bc.action.split(/\W|\s/).forEach(function(action){
action = action.match(/\s*(\S+)\s*/)[1];
switch(action){
case "check":
bc.check = true;
break;
case "clean":
bc.clean = true;
break;
case "release":
bc.release = true;
break;
default:
bc.log("inputUnknownAction", ["action", action]);
}
});
}
if(bc.clean){
bc.log("cleanRemoved");
}
// understand stripConsole from dojo 1.3 and before
var stripConsole = bc.stripConsole;
if(!stripConsole || stripConsole=="none"){
stripConsole = false;
}else if(stripConsole == "normal,warn"){
bc.log("inputDeprecatedStripConsole", ["deprecated", "normal,warn", "use", "warn"]);
stripConsole = "warn";
}else if(stripConsole == "normal,error"){
bc.log("inputDeprecatedStripConsole", ["deprecated", "normal,error", "use", "all"]);
stripConsole = "all";
}else if(!/normal|warn|all|none/.test(stripConsole)){
bc.log("inputUnknownStripConsole", ["value", stripConsole]);
}
bc.stripConsole = stripConsole;
function fixupOptimize(value){
if(value){
value = value + "";
value = value.toLowerCase();
if(!/^(((comments|shrinksafe)(\.keeplines)?)|(closure(\.keeplines)?|uglify(\.keeplines)?))$/.test(value)){
bc.log("inputUnknownOptimize", ["value", value]);
value = 0;
}else{
if(/shrinksafe/.test(value) && stripConsole){
value+= "." + stripConsole;
}
}
}
return value;
}
bc.optimize = fixupOptimize(bc.optimize);
bc.layerOptimize = fixupOptimize(bc.layerOptimize);
if(/closure/.test(bc.optimize) || /closure/.test(bc.layerOptimize)){
bc.optimizeOptions = bc.optimizeOptions || {};
// ECMASCRIPT_2017 is necessary to avoid throwing errors on block-scoped functions
// https://github.com/google/closure-compiler/issues/3189
bc.optimizeOptions.languageIn = bc.optimizeOptions.languageIn || 'ECMASCRIPT_2017';
// ECMASCRIPT3 is necessary to preserve compatibility with older browsers/JS runtimes
bc.optimizeOptions.languageOut = bc.optimizeOptions.languageOut || 'ECMASCRIPT3';
}
(function(){
var fixedScopeMap = {dojo:"dojo", dijit:"dijit", dojox:"dojox"};
(bc.scopeMap || []).forEach(function(pair){
if(!pair[1]){
delete fixedScopeMap[pair[0]];
}else{
fixedScopeMap[pair[0]] = pair[1];
}
});
bc.scopeMap = fixedScopeMap;
bc.scopeNames = [];
for(var p in fixedScopeMap){
bc.scopeNames.push(p);
}
})();
bc.internSkip = function(){return false;};
if(bc.internSkipList){
bc.internSkip = function(mid, referenceModule){
return bc.internSkipList.some(function(item){
var result = false;
if(item instanceof RegExp){
result = item.test(mid);
}else if(item instanceof Function){
result = item(mid, referenceModule);
}else{
result = item==mid;
}
if(result){
bc.log("internStrings", ["module", referenceModule.mid, "skipping", mid]);
}
return result;
});
};
}
// dump bc (if requested) before changing gate names to gate ids below
if(bc.check){
(function(){
var toDump = {
aliases:1,
basePath:1,
buildReportDir:1,
buildReportFilename:1,
closureCompilerPath:1,
copyright:1,
copyrightLayers:1,
copyrightNonlayers:1,
copyTests:1,
destBasePath:1,
destModules:1,
destPackages:1,
destPathTransforms:1,
dirs:1,
discoveryProcs:1,
files:1,
insertAbsMids:1,
internStringsSkipList:1,
layers:1,
localeList:1,
includeLocales: 1,
maxOptimizationProcesses:1,
mini:1,
optimize:1,
layerOptimize:1,
"package":1,
packages:1,
paths:1,
pathsMapProg:1,
plugins:1,
replacements:1,
startTimestamp:1,
staticHasFeatures:1,
stripConsole:1,
trees:1,
useSourceMaps:1
};
for(var p in toDump){
toDump[p] = bc[p];
}
bc.log("pacify", stringify(toDump));
})();
bc.release = 0;
}
if(bc.writeProfile){
// TODO
// fs.writeFileSync(bc.writeProfile, "dependencies = " + dojo.toJson(profileProperties, true), "utf8");
}
if(bc.debugCheck){
(function(){
var toDump = {};
for(var p in bc){
if(bc[p]!==messages[p] && typeof bc[p]!="function"){
toDump[p] = bc[p];
}
}
console.log("profile:");
console.log(stringify(toDump));
toDump = {};
for(p in require){
if(p!="modules" && p!="module" && p!="rawConfig" && typeof require[p]!="function"){
toDump[p] = require[p];
}
}
console.log("require config:");
console.log(stringify(toDump));
})();
bc.release = 0;
}
// clean up the gates and transforms
(function(){
// check that each transform references a valid gate
for(var gates = {}, i = 0; i<bc.gates.length; i++){
gates[bc.gates[i][1]] = i;
}
var
transforms = bc.transforms,
gateId;
for(var transformId in transforms){
// each item is a [AMD-MID, gateName] pair
gateId = gates[transforms[transformId][1]];
if(typeof gateId == "undefined"){
bc.log("inputUnknownGate", ["transform", transformId, "gate", transforms[transformId][1]]);
}else{
transforms[transformId][1] = gateId;
}
}
})();
// clean up the transformJobs
(function(){
// check that that each transformId referenced in transformJobs references an existing item in transforms
// ensure proper gate order of the transforms given in transformJobs; do not disturb order within a given
// gate--this is the purview of the user
var transforms = bc.transforms;
bc.transformJobs.forEach(function(item){
// item is a [predicate, vector of transformId] pairs
var error = false;
var tlist = item[1].map(function(id){
// item is a transformId
if(transforms[id]){
// return a [trandformId, gateId] pair
return [id, transforms[id][1]];
}else{
error = true;
bc.log("inputUnknownTransform", ["transform", id]);
return 0;
}
});
// tlist is a vector of [transformId, gateId] pairs than need to be checked for order
if(!error){
for(var i = 0, end = tlist.length - 1; i<end;){
if(tlist[i][1]>tlist[i+1][1]){
var t = tlist[i];
tlist[i] = tlist[i+1];
tlist[i+1] = t;
i && i--;
}else{
i++;
}
}
// now replace the vector of transformIds with the sorted list
item[1] = tlist;
}
});
})();
if(argv.args.unitTest=="dumpbc"){
console.log(stringify(bc) + "\n");
}
if(bc.quiet){
(function(){
var delSet = {};
for(var p in bc.pacifySet){
if(bc.messageMap[p][1]>199){
delSet[p] = 1;
}
}
for(p in delSet){
delete bc.pacifySet[p];
}
})();
}
if(bc.unitTestComputedProfile){
bc.unitTestComputedProfile();
// stop the build
bc.release = 0;
}
if(!bc.unitTestComputedProfile && !bc.check && !bc.debugCheck && !bc.clean && !bc.release){
bc.log("pacify", "Nothing to do; you must explicitly instruct the application to do something; use the option --help for help.");
}
return bc;
});