You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
618 lines
20 KiB
618 lines
20 KiB
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]); |
|
} |
|
} |
|
}); |
|
|
|
}; |
|
});
|
|
|