diff --git a/_base/loader.js b/_base/loader.js index 3e4caa1daf..8d5838ca93 100644 --- a/_base/loader.js +++ b/_base/loader.js @@ -395,7 +395,7 @@ define(["./kernel", "../has", "require", "module", "./json", "./lang", "./array" "define(" + dojo.toJson(names.concat(["dojo/loadInit!"+id])) + ", function(" + names.join(",") + "){\n" + extractResult[0] + "});"; }, - loaderVars = require.initSyncLoader({load:dojoRequirePlugin}, checkDojoRequirePlugin, transformToAmd, isXdUrl), + loaderVars = require.initSyncLoader(dojoRequirePlugin, checkDojoRequirePlugin, transformToAmd, isXdUrl), sync = loaderVars.sync, diff --git a/dojo.js b/dojo.js index 3eeaf407c9..b79f278cd0 100644 --- a/dojo.js +++ b/dojo.js @@ -194,7 +194,8 @@ if(has("dojo-combo-api")){ req.combo = {add:noop}; var comboPending = 0, - combosPending = []; + combosPending = [], + comboPendingTimer = null; } var legacyMode = 0, @@ -480,8 +481,19 @@ if(p=="cacheBust"){ cacheBust = config[p] ? (isString(config[p]) ? config[p] : (new Date()).getTime() + "") : ""; } - if(p=="baseUrl" || p=="combo"){ + if(p=="baseUrl"){ req[p] = config[p]; + if(p=="combo" && combo.plugins){ + for(var pluing in combo.plugins){ + } + } + } + if(has("dojo-combo-api") && p=="combo"){ + var combo = req[p] = config[p], + pluginName; + for(pluginName in combo.plugins){ + mix(mix(getModule(pluginName), combo.plugins[pluginName]), {isCombo:1, executed:"executed", load:1}); + } } if(has("dojo-sync-loader") && p=="async"){ // falsy or "sync" => legacy sync loader @@ -610,8 +622,10 @@ // checkComplete!=0 holds the idle signal; we're not idle if we're injecting dependencies checkCompleteGuard++; forEach(module.deps, injectModule); - if(has("dojo-combo-api") && comboPending){ + if(has("dojo-combo-api") && comboPending && !comboPendingTimer){ + comboPendingTimer = setTimeout(function() { comboPending = 0; + comboPendingTimer = null; req.combo.done(function(mids, url) { var onLoadCallback= function(){ // defQ is a vector of module definitions 1-to-1, onto mids @@ -623,6 +637,7 @@ req.injectUrl(url, onLoadCallback, mids); injectingModule = 0; }, req); + }, 0); } checkIdle(); }, @@ -631,11 +646,8 @@ var module, syntheticMid; if(isString(a1)){ // signature is (moduleId) - module = getModule(a1, referenceModule); - if(module.plugin){ - injectPlugin(module, true); - } - if(module.executed){ + module = getModule(a1, referenceModule, true); + if(module && module.executed){ return module.result; } throw makeError("undefinedModule", a1); @@ -692,6 +704,9 @@ }, createRequire = function(module){ + if(!module){ + return req; + } var result = module.require; if(!result){ result = function(a1, a2, a3){ @@ -866,24 +881,49 @@ return getModuleInfo_(mid, referenceModule, packs, modules, req.baseUrl, packageMapProg, pathsMapProg); }, - getModule = function(mid, referenceModule){ + resolvePluginResourceId = function(plugin, prid, referenceModule){ + return plugin.normalize ? plugin.normalize(prid, function(mid){return toAbsMid(mid, referenceModule);}) : toAbsMid(prid, referenceModule); + }, + + dynamicPluginUidGenerator = 0, + + getModule = function(mid, referenceModule, immediate){ // compute and optionally construct (if necessary) the module implied by the mid with respect to referenceModule var match, plugin, prid, result; match = mid.match(/^(.+?)\!(.*)$/); if(match){ - // name was ! - plugin = getModule(match[1], referenceModule); - plugin.isPlugin = 1; + // name was ! + plugin = getModule(match[1], referenceModule, immediate); + + if(has("dojo-sync-loader") && legacyMode == sync && !plugin.executed){ + injectModule(plugin); + checkCompleteGuard++; + execModule(plugin); + checkIdle(); + promoteModuleToPlugin(plugin); + } + + if(plugin.executed === executed && !plugin.load){ + // executed the module not knowing it was a plugin + promoteModuleToPlugin(plugin); + } + + // if the plugin has not been loaded, then can't resolve the prid and must assume this plugin is dynamic until we find out otherwise + if(plugin.load){ + prid = resolvePluginResourceId(plugin, match[2], referenceModule); + mid = (plugin.mid + "!" + (plugin.dynamic ? ++dynamicPluginUidGenerator + "!" : "") + prid); + }else{ prid = match[2]; - mid = plugin.mid + "!" + (referenceModule ? referenceModule.mid + "!" : "") + prid; - return modules[mid] || (modules[mid] = {plugin:plugin, mid:mid, req:(referenceModule ? createRequire(referenceModule) : req), prid:prid}); + mid = plugin.mid + "!" + (++dynamicPluginUidGenerator) + "!waitingForPlugin"; + } + result = {plugin:plugin, mid:mid, req:createRequire(referenceModule), prid:prid}; }else{ result = getModuleInfo(mid, referenceModule); - return modules[result.mid] || (modules[result.mid] = result); } + return modules[result.mid] || (!immediate && (modules[result.mid] = result)); }, - toAbsMid = req.toAbsMid = function(mid, referenceModule){ + toAbsMid = req.toAbsMid = function(mid, referenceModule){ return getModuleInfo(mid, referenceModule).mid; }, @@ -936,27 +976,64 @@ defOrder = 0, + promoteModuleToPlugin = function(pluginModule){ + var plugin = pluginModule.result; + pluginModule.dynamic = plugin.dynamic; + pluginModule.normalize = plugin.normalize; + pluginModule.load = plugin.load; + return pluginModule; + }, + + resolvePluginLoadQ = function(plugin){ + // plugins is a newly executed module that has a loadQ waiting to run + + // step 1: traverse the loadQ and fixup the mid and prid; remember the map from original mid to new mid + // recall the original mid was created before the plugin was on board and therefore it was impossible to + // compute the final mid; accordingly, prid may or may not change, but the mid will definitely change + var map = {}; + forEach(plugin.loadQ, function(pseudoPluginResource){ + // manufacture and insert the real module in modules + var pseudoMid = pseudoPluginResource.mid, + prid = resolvePluginResourceId(plugin, pseudoPluginResource.prid, pseudoPluginResource.req.module), + mid = plugin.dynamic ? pseudoPluginResource.mid.replace(/waitingForPlugin$/, prid) : (plugin.mid + "!" + prid), + pluginResource = mix(mix({}, pseudoPluginResource), {mid:mid, prid:prid, injected:0}); + if(!modules[mid]){ + // create a new (the real) plugin resource and inject it normally now that the plugin is on board + injectPlugin(modules[mid] = pluginResource); + } // else this was a duplicate request for the same (plugin, rid) for a nondynamic plugin + + // pluginResource is really just a placeholder with the wrong mid (because we couldn't calculate it until the plugin was on board) + // mark is as arrived and delete it from modules; the real module was requested above + map[pseudoPluginResource.mid] = modules[mid]; + setArrived(pseudoPluginResource); + delete modules[pseudoPluginResource.mid]; + }); + plugin.loadQ = 0; + + // step2: replace all references to any placeholder modules with real modules + var substituteModules = function(module){ + for(var replacement, deps = module.deps || [], i = 0; i declarations so that external SVG and XML //documents can be added to a document without worry. Also, if the string @@ -58,15 +49,29 @@ define(["./_base/kernel", "require", "./has", "./has!host-browser?./_base/xhr"], pending = {}, result= { - load:function(id, require, load){ + dynamic: + // the dojo/text caches it's own resources because of dojo.cache + true, + + normalize:function(id, toAbsMid){ // id is something like (path may be relative): // // "path/to/text.html" // "path/to/text.html!strip" + var parts= id.split("!"), + url= parts[0]; + return (/^\./.test(url) ? toAbsMid(url) : url) + (parts[1] ? "!" + parts[1] : ""); + }, + + load:function(id, require, load){ + // id is something like (path is always absolute): + // + // "path/to/text.html" + // "path/to/text.html!strip" var parts= id.split("!"), stripFlag= parts.length>1, - absMid= toAbsMid(parts[0], require), + absMid= parts[0], url = require.toUrl(parts[0]), text = notFound, finish = function(text){ @@ -98,52 +103,52 @@ define(["./_base/kernel", "require", "./has", "./has!host-browser?./_base/xhr"], } }; - dojo.cache= function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){ - // * (string string [value]) => (module, url, value) - // * (object [value]) => (module, value), url defaults to "" - // - // * if module is an object, then it must be convertable to a string - // * (module, url) module + (url ? ("/" + url) : "") must be a legal argument to require.toUrl - // * value may be a string or an object; if an object then may have the properties "value" and/or "sanitize" - var key; - if(typeof module=="string"){ - if(/\//.test(module)){ - // module is a version 1.7+ resolved path - key = module; - value = url; - }else{ - // module is a version 1.6- argument to dojo.moduleUrl - key = require.toUrl(module.replace(/\./g, "/") + (url ? ("/" + url) : "")); - } - }else{ - key = module + ""; + dojo.cache= function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){ + // * (string string [value]) => (module, url, value) + // * (object [value]) => (module, value), url defaults to "" + // + // * if module is an object, then it must be convertable to a string + // * (module, url) module + (url ? ("/" + url) : "") must be a legal argument to require.toUrl + // * value may be a string or an object; if an object then may have the properties "value" and/or "sanitize" + var key; + if(typeof module=="string"){ + if(/\//.test(module)){ + // module is a version 1.7+ resolved path + key = module; value = url; + }else{ + // module is a version 1.6- argument to dojo.moduleUrl + key = require.toUrl(module.replace(/\./g, "/") + (url ? ("/" + url) : "")); } - var - val = (value != undefined && typeof value != "string") ? value.value : value, - sanitize = value && value.sanitize; + }else{ + key = module + ""; + value = url; + } + var + val = (value != undefined && typeof value != "string") ? value.value : value, + sanitize = value && value.sanitize; - if(typeof val == "string"){ - //We have a string, set cache value - theCache[key] = val; - return sanitize ? strip(val) : val; - }else if(val === null){ - //Remove cached value - delete theCache[key]; - return null; - }else{ - //Allow cache values to be empty strings. If key property does - //not exist, fetch it. - if(!(key in theCache)){ - getText(key, true, function(text){ - theCache[key]= text; - }); - } - return sanitize ? strip(theCache[key]) : theCache[key]; + if(typeof val == "string"){ + //We have a string, set cache value + theCache[key] = val; + return sanitize ? strip(val) : val; + }else if(val === null){ + //Remove cached value + delete theCache[key]; + return null; + }else{ + //Allow cache values to be empty strings. If key property does + //not exist, fetch it. + if(!(key in theCache)){ + getText(key, true, function(text){ + theCache[key]= text; + }); } - }; + return sanitize ? strip(theCache[key]) : theCache[key]; + } + }; - return result; + return result; /*===== dojo.cache = function(module, url, value){