UNPKG

dojo-util

Version:

Dojo utilities including build system for optimizing JavaScript application performance, and DOH testing tool

760 lines (688 loc) 24.7 kB
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; });