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) result = result[0] + "{})"; }else if(result[4]){ // (mid, ) result = result[0] + ")"; }else{ // (mid, ) result = result[0] + "{})"; } }else{ // () result = result[0] + ")"; } }else if(result[4]){ // first arg an array if(result[5]){ // (deps, ) result = result[0] + "{})"; }else{ // () 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]); } } }); }; });