619 lines
20 KiB
JavaScript
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]);
|
|
}
|
|
}
|
|
});
|
|
|
|
};
|
|
});
|