control-freak-ide/server/nodejs/util/build/transforms/depsScan.js
plastic-hub-dev-node-saturn 538369cff7 latest
2021-05-12 18:35:18 +02:00

619 lines
20 KiB
JavaScript

define([
"require",
"../buildControl",
"../fileUtils",
"../removeComments",
"dojo/json",
"dojo/_base/lang",
"dojo/_base/loader",
"../fs"
], function(require, bc, fileUtils, removeComments, json, lang, syncLoader, fs){
return function(resource){
var
newline = bc.newline,
mix = function(dest, src){
dest = dest || {};
for(var p in src){
dest[p] = src[p];
}
return dest;
},
absMid = 0,
aggregateDeps = [],
defineApplied = 0,
simulatedDefine = function(mid, dependencies, factory){
defineApplied = 1;
var arity = arguments.length,
args = 0,
defaultDeps = ["require", "exports", "module"];
// TODO: add the factory scan?
if(bc.factoryScan && arity == 1 && typeof mid === 'function'){
dependencies = [];
mid.toString()
.replace(/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg, "")
.replace(/require\(["']([\w\!\-_\.\/]+)["']\)/g, function(match, dep){
dependencies.push(dep);
});
args = [0, defaultDeps.concat(dependencies), mid];
resource.text = resource.text.replace(/define\s*\(/, 'define(["' + args[1].join('","') + '"],');
}
if(!args){
args = arity == 1 ? [0, defaultDeps, mid] :
(arity == 2 ? (mid instanceof Array ? [0, mid, dependencies] : [mid, defaultDeps, dependencies]) :
[mid, dependencies, factory]);
}
if(args[1].some(function(item){
return !lang.isString(item);
})){
throw new Error("define dependency vector contains elements that are not of type string.");
}
absMid = args[0];
aggregateDeps = aggregateDeps.concat(args[1]);
},
_tag_simulatedDefine = simulatedDefine.amd = {
vendor:"dojotoolkit.org",
context:"build"
},
simulatedRequire = function(depsOrConfig, callbackOrDeps){
// add contents of deps vector to aggregateDeps iff it contains no relative ids; do not process deps property in config
var hasRelativeIds = function(deps){ return deps.some(function(item){ return /^\./.test(item); }); };
if(lang.isArray(depsOrConfig) && !hasRelativeIds(depsOrConfig)){
aggregateDeps = aggregateDeps.concat(depsOrConfig);
}else if(lang.isArray(callbackOrDeps) && !hasRelativeIds(callbackOrDeps)){
aggregateDeps = aggregateDeps.concat(callbackOrDeps);
}
},
slashName = function(dottedName){
return dottedName.replace(/\./g, "/");
},
pluginStrategyRequired =
// truthy if dojo.loadInit|require[After]If|platformRequire detected that cannot be resolved at build-time; falsy otherwise
0,
dojoProvides =
// vector of modules dojo.provide'd by the resource
[],
dojoRequires =
// vector of modules dojo.require'd by the resource
[],
simulatedDojo =
// the dojo legacy loader API
{
require:function(moduleName, omitModuleCheck){
dojoRequires.push(slashName(moduleName));
},
provide:function(moduleName){
dojoProvides.push(slashName(moduleName));
},
requireLocalization: function(moduleName, bundleName, locale){
aggregateDeps.push("dojo/i18n!" + slashName(moduleName) + "/nls/" + (!locale || /root/i.test(locale) ? "" : locale + "/") + slashName(bundleName));
},
platformRequire:function(modMap){
pluginStrategyRequired = 1;
(modMap.common || []).concat((bc.platform && modMap[bc.platform]) || []).forEach(function(item){
dojoRequires.push(lang.isArray(item) ? slashName(item[0]) : slashName(item));
});
},
loadInit:function(callback){
pluginStrategyRequired = 1;
callback();
},
requireIf:function(expr, moduleName, omitModuleCheck){
pluginStrategyRequired = 1;
expr && dojoRequires.push(slashName(moduleName));
},
requireAfterIf:function(expr, moduleName, omitModuleCheck){
pluginStrategyRequired = 1;
expr && dojoRequires.push(slashName(moduleName));
}
},
evaluatorWithNoRuntime =
new Function("dojo", "__text", "eval(__text);"),
applyLegacyCalls = function(callList){
var evaluator;
if(resource.pack.runtime){
// if a runtime is provided, then a special evaluator has to be constructed
var runtime = resource.pack.runtime,
args = [],
params = [],
p;
runtime.dojo = mix(runtime.dojo, simulatedDojo);
for(p in runtime){
args.push(runtime[p]);
params.push(p);
}
evaluator = new Function("__bc", "__args", "__text", "(function(" + params.join(",") + "){ eval(__text); }).apply(__bc, __args);");
args = [bc, args];
}else{
args = [simulatedDojo];
evaluator = evaluatorWithNoRuntime;
}
// apply the legacy API calls
var results = callList.map(function(application){
try{
evaluator.apply(bc, args.concat(application));
return 0;
}catch(e){
pluginStrategyRequired = 1;
return [e, application];
}
});
// report the results
results.forEach(function(item){
if(item){
bc.log("legacyFailedEval", ["module", resource.mid, "text", item[0], "error", item[1]]);
}
});
},
tagAbsMid = function(absMid){
if(absMid && absMid!=resource.mid){
bc.log("amdInconsistentMid", ["module", resource.mid, "specified", absMid]);
}
if(absMid){
resource.tag.hasAbsMid = 1;
}
},
processPureAmdModule = function(){
// find the dependencies for this resource using the fast path if the module says it's OK
// pure AMD says the module can be executed in the build environment
// note: the user can provide a build environment with TODO
try{
if(resource.mid!="dojo/_base/loader" && /dojo\.(require|provide)\s*\(/.test(removeComments(resource.text))){
bc.log("amdPureContainedLegacyApi", ["module", resource.mid]);
}
(new Function("define", "require", resource.text))(simulatedDefine, simulatedRequire);
tagAbsMid(absMid);
}catch (e){
bc.log("amdFailedEval", ["module", resource.mid, "error", e]);
}
},
convertToStrings = function(text){
var strings = [],
// a DFA, the states...
spaces = "spaces",
string = "string",
endOfString = "endOfString",
done = "done",
error = "error",
// the machine...
dfa = {
spaces:function(c){
if(/\s/.test(c)){
return spaces;
}
if(c=="'" || c=='"'){
quoteType = c;
current = "";
return string;
}
if(c==0){
return done;
}
return error;
},
string:function(c){
if(c==quoteType){
strings.push(current);
return "endOfString";
}else{
current+= c;
return "string";
}
},
endOfString:function(c){
if(/\s/.test(c)){
return endOfString;
}
if(c==0){
return done;
}
if(c==","){
return spaces;
}
return error;
}
},
state = spaces,
quoteType, current;
for(var i = 0; i<text.length; i++){
state = dfa[state](text.charAt(i));
if(state==error){
return 0;
}
}
if(dfa[state](0)!=error){
return strings;
}
return 0;
},
processPossibleAmdWithRegExs = function(text){
// look for AMD define and/or require; require must not have relative mids; require signature with config argument is not discovered
// (remember, a config could have a string or regex that could have an unmatched right "}", so there is not way to guarantee we can find the correct
// end of the config arg without parsing)
var amdCallCount =
// the number of AMD applications found
0,
defineExp=
// look for define applications with an optional string first arg and an optional array second arg;
// notice the regex stops after the second arg
// a test run in the console
// test = [
// 'define("test")',
// 'define("test", ["test1"])',
// 'define("test", ["test1", "test2"])',
// 'define(["test1"])',
// 'define(["test1", "test2"])',
// 'define("test", ["test1"], function(test){ hello;})',
// 'define("test", function(test){ hello;})',
// 'define(["test1"], function(test){ hello;})',
// 'define(function(test){ hello;})',
// 'define({a:1})'
// ]
// 2 3 4 5
/(^|\s)define\s*\(\s*(["'][^'"]+['"])?\s*(,)?\s*(\[[^\]]*?\])?\s*(,)?/g,
result;
while((result = defineExp.exec(text)) != null){
try{
if(result[2]){
// first arg a string
if(result[3]){
// first arg a module id
if(result[5]){
// (mid, deps, <factory>)
result = result[0] + "{})";
}else if(result[4]){
// (mid, <factory:array value>)
result = result[0] + ")";
}else{
// (mid, <factory>)
result = result[0] + "{})";
}
}else{
// (<factory:string-value>)
result = result[0] + ")";
}
}else if(result[4]){
// first arg an array
if(result[5]){
// (deps, <factory>)
result = result[0] + "{})";
}else{
// (<factory:array-value>)
result = result[0] + ")";
}
}else{
//just a factory
result = "define({})";
}
amdCallCount++;
(new Function("define", result))(simulatedDefine);
tagAbsMid(absMid);
}catch(e){
amdCallCount--;
bc.log("amdFailedDefineEval", ["module", resource.mid, "text", result, "error", e]);
}
}
var requireExp=
// look for require applications with an array for the first arg; notice the regex stops after the first arg and config signature is not processed
/(^|\s)require\s*\(\s*\[([^\]]*?)\]/g;
while((result = requireExp.exec(text)) != null){
var mids = convertToStrings(result[2]);
if(mids){
amdCallCount++;
aggregateDeps = aggregateDeps.concat(mids.filter(function(item){return item.charAt(0)!=".";}));
}
}
return amdCallCount;
},
evalNlsResource = function(resource){
var bundleValue = 0;
try{
function simulatedDefine(a1, a2){
if(lang.isString(a1) && lang.isObject(a2)){
tagAbsMid(a1);
bundleValue = a2;
}else if(lang.isObject(a1)){
bundleValue = a1;
}
}
(new Function("define", resource.text))(simulatedDefine);
if(bundleValue){
resource.bundleValue = bundleValue;
resource.bundleType = "amd";
return;
}
}catch(e){
// TODO: consider a profile flag to cause errors to be logged
}
try{
bundleValue = (new Function("return " + resource.text + ";"))();
if(lang.isObject(bundleValue)){
resource.bundleValue = bundleValue;
resource.bundleType = "legacy";
return;
}
}catch(e){
// TODO: consider a profile flag to cause errors to be logged
}
// if not building flattened layer bundles, then it's not necessary for the bundle
// to be evaluable; still run processPureAmdModule to compute possible dependencies
processPureAmdModule();
if(!defineApplied){
bc.log("i18nImproperBundle", ["module", resource.mid]);
}
},
processNlsBundle = function(){
// either a v1.x sync bundle or an AMD NLS bundle
// compute and remember the set of localized bundles; attach this info to the root bundle
var match = resource.mid.match(/(^.*\/nls\/)(([^\/]+)\/)?([^\/]+)$/),
prefix = resource.prefix = match[1],
locale = resource.locale = match[3],
bundle = resource.bundle = match[4],
rootPath = prefix + bundle,
rootBundle = bc.amdResources[rootPath];
// if not root, don't process any localized bundles; a missing root bundle serves as a signal
// to other transforms (e.g., writeAmd) to ignore this bundle family
if(!rootBundle){
bc.log("i18nNoRoot", ["bundle", resource.mid]);
return;
}
// accumulate all the localized versions in the root bundle
if(!rootBundle.localizedSet){
rootBundle.localizedSet = {};
}
// try to compute the value of the bundle; sets properties bundleValue and bundleType
evalNlsResource(resource);
if((bc.localeList || resource.bundleType=="legacy") && !resource.bundleValue){
// profile is building flattened layer bundles or converting a legacy-style bundle
// to an AMD-style bundle; either way, we need the value of the bundle
bc.log("i18nUnevaluableBundle", ["module", resource.mid]);
}
if(resource.bundleType=="legacy" && resource===rootBundle && resource.bundleValue){
resource.bundleValue = {root:resource.bundleValue};
}
if(resource!==rootBundle){
rootBundle.localizedSet[locale] = resource;
}
},
interningDojoUriRegExpString =
// the following is a direct copy from the v1.6- build util; this is so janky, we dare not touch
//23 4 5 6 78 9 0 1
"(((templatePath|templateCssPath)\\s*(=|:)\\s*)dojo\\.(module)?Url\\(|dojo\\.cache\\s*\\(\\s*)\\s*?[\\\"\\']([\\w\\.\\/]+)[\\\"\\'](([\\,\\s]*)[\\\"\\']([\\w\\.\\/-]*)[\\\"\\'])?(\\s*,\\s*)?([^\\)]*)?\\s*\\)",
interningGlobalDojoUriRegExp = new RegExp(interningDojoUriRegExpString, "g"),
interningLocalDojoUriRegExp = new RegExp(interningDojoUriRegExpString),
internStrings = function() {
var getText = function(src){
return fs.readFileSync(src, "utf8");
},
skipping = [],
notFound = [],
nothing = [];
resource.text = resource.text.replace(interningGlobalDojoUriRegExp, function(matchString){
var parts = matchString.match(interningLocalDojoUriRegExp);
var textModuleInfo = bc.getSrcModuleInfo(fileUtils.catPath(parts[6].replace(/\./g, "/"), parts[9]), 0, true);
if(bc.internSkip(textModuleInfo.mid, resource)){
return matchString;
}
var textModule = bc.resources[textModuleInfo.url];
if(!textModule){
notFound.push(textModuleInfo.url);
return matchString;
}
// note: it's possible the module is being processed by a set of transforms that don't add a
// getText method (e.g., copy); therefore, we provide one for these cases
var text = (textModule.getText && textModule.getText()) || getText(textModule.src);
if(!text){
nothing.push(textModule.src);
return matchString;
}
text = json.stringify(text);
if(matchString.indexOf("dojo.cache") != -1){
//Handle dojo.cache-related interning.
var endContent = parts[11];
if(!endContent){
endContent = text;
}else{
var braceIndex = endContent.indexOf("{");
if(braceIndex != -1){
endContent = endContent.substring(0, braceIndex + 1)
+ 'value: ' + text + ','
+ endContent.substring(braceIndex + 1, endContent.length);
}
}
return 'dojo.cache("' + parts[6] + '", "' + parts[9] + '", ' + endContent + ')';
}else if(parts[3] == "templatePath"){
//Replace templatePaths
return "templateString" + parts[4] + text;
}else{
//Dealing with templateCssPath; not doing this anymore
return matchString;
}
});
if(skipping.length || notFound.length || nothing.length){
var logArgs = ["module", resource.mid];
if(skipping.length){
logArgs.push("skipping", skipping);
}
if(notFound.length){
logArgs.push("not found", notFound);
}
if(nothing.length){
logArgs.push("nothing to intern", nothing);
}
bc.log("internStrings", logArgs);
}
},
processWithRegExs = function(){
// try to figure out if the module is legacy or AMD and then process the loader applications found
//
// Warning: the process is flawed because regexs will find things that are not there and miss things that are,
// and there is no way around this without a proper parser. Note however, this kind of process has been in use
// with the v1.x build system from the beginning.
//
// TODO: replace this process with a parser
//
// do it the unreliable way; first try to find "dojo.provide" et al since those names are less likely
// to be overloaded than "define" and "require"
if(bc.internStrings){
internStrings();
}
var text =
// apply any replacements before processing
resource.getText(),
names =
bc.scopeNames,
extractResult =
// a vector of legacy loader API applications as pairs of [function-name, complete-function-application-text] + the following two properties
// * text: the original text with all dojo.loadInit applications preceeded by 0 &&, thereby causing those applications to be discarded by the minifier
// * extractText: all legacy loader applications, with all dojo.loadInit applications moved to the beginning
// See dojo.js
syncLoader.extractLegacyApiApplications(text, removeComments(text));
if(!extractResult.extractText && processPossibleAmdWithRegExs(removeComments(text))){
// zero legacy calls detected *and* at least one AMD call detected; therefore, assume it's AMD
bc.log("amdNotPureContainedNoLegacyApi", ["module", resource.mid]);
return;
}
bc.log("legacyAssumed", ["module", resource.mid]);
if(!extractResult){
// no legacy API calls to worry about; therefore...
resource.getText = function(){ return "define(" + json.stringify(names) + ", function(" + names.join(",") + "){" + newline + text + "});" + newline; };
return;
}
// apply the legacy calls in a special environment
applyLegacyCalls(extractResult[2]);
// check for multiple or irrational dojo.provides
if(dojoProvides.length){
if(dojoProvides.length>1){
bc.log("legacyMultipleProvides", ["module", resource.mid, "provides", dojoProvides]);
}
dojoProvides.forEach(function(item){
if(item.replace(/\./g, "/")!=resource.mid){
bc.log("legacyImproperProvide", ["module", resource.mid, "provide", item]);
}
});
}
if(pluginStrategyRequired){
// some loadInit and/or require[After]If and/or platformRequire applications that could not be resolved at build time
bc.log("legacyUsingLoadInitPlug", ["module", resource.mid]);
// construct and start the synthetic plugin resource
var pluginText, mid, pluginResource, pluginResourceId;
pluginText =
"// generated by build app" + newline +
"define([], {" + newline +
"\tnames:" + json.stringify(names) + "," + newline +
"\tdef:function(" + names.join(",") + "){" + newline + extractResult[1] + "}" + newline +
"});" + newline;
mid = resource.mid + "-loadInit";
pluginResource = mix(mix({}, resource), {
src:resource.src.substring(0, resource.src.length-3) + "-loadInit.js",
dest:bc.getDestModuleInfo(mid).url,
mid:mid,
tag:{loadInitResource:1},
deps:[],
getText:function(){ return pluginText; }
});
bc.start(pluginResource);
pluginResourceId = "dojo/loadInit!" + mid;
aggregateDeps.push(pluginResourceId);
}else if(dojoRequires.length){
aggregateDeps.push("dojo/require!" + dojoRequires.join(","));
}
aggregateDeps = names.concat(aggregateDeps);
// need to use extractResult[0] since it may delete the dojo.loadInit applications
resource.getText = function(){ return "// wrapped by build app" + newline + "define(" + json.stringify(aggregateDeps) + ", function(" + names.join(",") + "){" + newline + extractResult[0] + newline + "});" + newline; };
};
// scan the resource for dependencies
if(resource.tag.nls){
processNlsBundle();
}else if(resource.tag.amd || /\/\/>>\s*pure-amd/.test(resource.text)){
processPureAmdModule();
}else{
processWithRegExs();
}
// resolve the dependencies into modules
var deps = resource.deps;
resource.aggregateDeps = aggregateDeps;
aggregateDeps.forEach(function(dep){
if(!(/^(require|exports|module)$/.test(dep))){
try{
var module = bc.getAmdModule(dep, resource);
if(lang.isArray(module)){
module.forEach(function(module){ deps.push(module); });
}else if(module){
deps.push(module);
}else{
bc.log("amdMissingDependency", ["module", resource.mid, "dependency", dep]);
}
}catch(e){
bc.log("amdMissingDependency", ["module", resource.mid, "dependency", dep, "error", e]);
}
}
});
};
});