From 307b3884b5491e6cdd086560da855e0bc1847e5f Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 2 Jan 2012 17:33:01 +0100 Subject: [PATCH] update require.js --- demo/kitchen-sink/require.js | 1017 ++++++++++++++++------------------ 1 file changed, 481 insertions(+), 536 deletions(-) diff --git a/demo/kitchen-sink/require.js b/demo/kitchen-sink/require.js index dec3f7e9..72756193 100644 --- a/demo/kitchen-sink/require.js +++ b/demo/kitchen-sink/require.js @@ -1,9 +1,9 @@ /** vim: et:ts=4:sw=4:sts=4 - * @license RequireJS 0.25.0 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved. + * @license RequireJS 1.0.3 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/requirejs for details */ -/*jslint strict: false, plusplus: false */ +/*jslint strict: false, plusplus: false, sub: true */ /*global window: false, navigator: false, document: false, importScripts: false, jQuery: false, clearInterval: false, setInterval: false, self: false, setTimeout: false, opera: false */ @@ -11,9 +11,9 @@ var requirejs, require, define; (function () { //Change this version number for each release. - var version = "0.25.0", - commentRegExp = /(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg, - cjsRequireRegExp = /require\(["']([^'"\s]+)["']\)/g, + var version = "1.0.3", + commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, + cjsRequireRegExp = /require\(\s*["']([^'"\s]+)["']\s*\)/g, currDirRegExp = /^\.\//, jsSuffixRegExp = /\.js$/, ostring = Object.prototype.toString, @@ -31,17 +31,14 @@ var requirejs, require, define; defContextName = "_", //Oh the tragedy, detecting opera. See the usage of isOpera for reason. isOpera = typeof opera !== "undefined" && opera.toString() === "[object Opera]", - reqWaitIdPrefix = "_r@@", empty = {}, contexts = {}, globalDefQueue = [], interactiveScript = null, - isDone = false, checkLoadedDepth = 0, useInteractive = false, req, cfg = {}, currentlyAddingScript, s, head, baseElement, scripts, script, - src, subPath, mainScript, dataMain, i, scrollIntervalId, setReadyState, ctx, - jQueryCheck, checkLoadedTimeoutId; + src, subPath, mainScript, dataMain, i, ctx, jQueryCheck, checkLoadedTimeoutId; function isFunction(it) { return ostring.call(it) === "[object Function]"; @@ -172,9 +169,10 @@ var requirejs, require, define; var context, resume, config = { waitSeconds: 7, - baseUrl: s.baseUrl || "./", + baseUrl: "./", paths: {}, - pkgs: {} + pkgs: {}, + catchError: {} }, defQueue = [], specified = { @@ -187,12 +185,15 @@ var requirejs, require, define; loaded = {}, waiting = {}, waitAry = [], - waitIdCounter = 0, + urlFetched = {}, + managerCounter = 0, managerCallbacks = {}, plugins = {}, - pluginsQueue = {}, - resumeDepth = 0, - normalizedWaiting = {}; + //Used to indicate which modules in a build scenario + //need to be full executed. + needFullExec = {}, + fullExec = {}, + resumeDepth = 0; /** * Trims the . and .. from an array of path segments. @@ -238,7 +239,7 @@ var requirejs, require, define; var pkgName, pkgConfig; //Adjust any relative paths. - if (name.charAt(0) === ".") { + if (name && name.charAt(0) === ".") { //If have a base name, try to normalize against it, //otherwise, assume it is a top-level require that will //be relative to baseUrl in the end. @@ -267,6 +268,10 @@ var requirejs, require, define; if (pkgConfig && name === pkgName + '/' + pkgConfig.main) { name = pkgName; } + } else if (name.indexOf("./") === 0) { + // No baseName, so this is ID is resolved relative + // to baseUrl, pull off the leading dot. + name = name.substring(2); } } return name; @@ -303,41 +308,26 @@ var requirejs, require, define; if (name) { if (prefix) { pluginModule = defined[prefix]; - if (pluginModule) { - //Plugin is loaded, use its normalize method, otherwise, - //normalize name as usual. - if (pluginModule.normalize) { - normalizedName = pluginModule.normalize(name, function (name) { - return normalize(name, parentName); - }); - } else { - normalizedName = normalize(name, parentName); - } + if (pluginModule && pluginModule.normalize) { + //Plugin is loaded, use its normalize method. + normalizedName = pluginModule.normalize(name, function (name) { + return normalize(name, parentName); + }); } else { - //Plugin is not loaded yet, so do not normalize - //the name, wait for plugin to load to see if - //it has a normalize method. To avoid possible - //ambiguity with relative names loaded from another - //plugin, use the parent's name as part of this name. - normalizedName = '__$p' + parentName + '@' + name; + normalizedName = normalize(name, parentName); } } else { + //A regular module. normalizedName = normalize(name, parentName); - } - url = urlMap[normalizedName]; - if (!url) { - //Calculate url for the module, if it has a name. - if (req.toModuleUrl) { - //Special logic required for a particular engine, - //like Node. - url = req.toModuleUrl(context, normalizedName, parentModuleMap); - } else { + url = urlMap[normalizedName]; + if (!url) { + //Calculate url for the module, if it has a name. url = context.nameToUrl(normalizedName, null, parentModuleMap); - } - //Store the URL mapping for later. - urlMap[normalizedName] = url; + //Store the URL mapping for later. + urlMap[normalizedName] = url; + } } } @@ -347,7 +337,7 @@ var requirejs, require, define; parentMap: parentModuleMap, url: url, originalName: originalName, - fullName: prefix ? prefix + "!" + normalizedName : normalizedName + fullName: prefix ? prefix + "!" + (normalizedName || '') : normalizedName }; } @@ -372,23 +362,12 @@ var requirejs, require, define; return priorityDone; } - /** - * Helper function that creates a setExports function for a "module" - * CommonJS dependency. Do this here to avoid creating a closure that - * is part of a loop. - */ - function makeSetExports(moduleObj) { - return function (exports) { - moduleObj.exports = exports; - }; - } - function makeContextModuleFunc(func, relModuleMap, enableBuildCallback) { return function () { //A version of a require function that passes a moduleName //value for items that may need to //look up paths relative to the moduleName - var args = [].concat(aps.call(arguments, 0)), lastArg; + var args = aps.call(arguments, 0), lastArg; if (enableBuildCallback && isFunction((lastArg = args[args.length - 1]))) { lastArg.__requireJsBuild = true; @@ -412,147 +391,50 @@ var requirejs, require, define; toUrl: makeContextModuleFunc(context.toUrl, relModuleMap), defined: makeContextModuleFunc(context.requireDefined, relModuleMap), specified: makeContextModuleFunc(context.requireSpecified, relModuleMap), - ready: req.ready, isBrowser: req.isBrowser }); - //Something used by node. - if (req.paths) { - modRequire.paths = req.paths; - } return modRequire; } - /** - * Used to update the normalized name for plugin-based dependencies - * after a plugin loads, since it can have its own normalization structure. - * @param {String} pluginName the normalized plugin module name. - */ - function updateNormalizedNames(pluginName) { - - var oldFullName, oldModuleMap, moduleMap, fullName, callbacks, - i, j, k, depArray, existingCallbacks, - maps = normalizedWaiting[pluginName]; - - if (maps) { - for (i = 0; (oldModuleMap = maps[i]); i++) { - oldFullName = oldModuleMap.fullName; - moduleMap = makeModuleMap(oldModuleMap.originalName, oldModuleMap.parentMap); - fullName = moduleMap.fullName; - //Callbacks could be undefined if the same plugin!name was - //required twice in a row, so use empty array in that case. - callbacks = managerCallbacks[oldFullName] || []; - existingCallbacks = managerCallbacks[fullName]; - - if (fullName !== oldFullName) { - //Update the specified object, but only if it is already - //in there. In sync environments, it may not be yet. - if (oldFullName in specified) { - delete specified[oldFullName]; - specified[fullName] = true; - } - - //Update managerCallbacks to use the correct normalized name. - //If there are already callbacks for the normalized name, - //just add to them. - if (existingCallbacks) { - managerCallbacks[fullName] = existingCallbacks.concat(callbacks); - } else { - managerCallbacks[fullName] = callbacks; - } - delete managerCallbacks[oldFullName]; - - //In each manager callback, update the normalized name in the depArray. - for (j = 0; j < callbacks.length; j++) { - depArray = callbacks[j].depArray; - for (k = 0; k < depArray.length; k++) { - if (depArray[k] === oldFullName) { - depArray[k] = fullName; - } - } - } - } - } - } - - delete normalizedWaiting[pluginName]; - } - /* * Queues a dependency for checking after the loader is out of a * "paused" state, for example while a script file is being loaded * in the browser, where it may have many modules defined in it. - * - * depName will be fully qualified, no relative . or .. path. */ - function queueDependency(dep) { - //Make sure to load any plugin and associate the dependency - //with that plugin. - var prefix = dep.prefix, - fullName = dep.fullName; - - //Do not bother if the depName is already in transit - if (specified[fullName] || fullName in defined) { - return; - } - - if (prefix && !plugins[prefix]) { - //Queue up loading of the dependency, track it - //via context.plugins. Mark it as a plugin so - //that the build system will know to treat it - //special. - plugins[prefix] = undefined; - - //Remember this dep that needs to have normaliztion done - //after the plugin loads. - (normalizedWaiting[prefix] || (normalizedWaiting[prefix] = [])) - .push(dep); - - //Register an action to do once the plugin loads, to update - //all managerCallbacks to use a properly normalized module - //name. - (managerCallbacks[prefix] || - (managerCallbacks[prefix] = [])).push({ - onDep: function (name, value) { - if (name === prefix) { - updateNormalizedNames(prefix); - } - } - }); - - queueDependency(makeModuleMap(prefix)); - } - - context.paused.push(dep); + function queueDependency(manager) { + context.paused.push(manager); } function execManager(manager) { - var i, ret, waitingCallbacks, err, + var i, ret, err, errFile, errModuleTree, cb = manager.callback, - fullName = manager.fullName, - args = [], - ary = manager.depArray; + map = manager.map, + fullName = map.fullName, + args = manager.deps, + listeners = manager.listeners, + cjsModule; //Call the callback to define the module, if necessary. if (cb && isFunction(cb)) { - //Pull out the defined dependencies and pass the ordered - //values to the callback. - if (ary) { - for (i = 0; i < ary.length; i++) { - args.push(manager.deps[ary[i]]); + if (config.catchError.define) { + try { + ret = req.execCb(fullName, manager.callback, args, defined[fullName]); + } catch (e) { + err = e; } - } - - try { + } else { ret = req.execCb(fullName, manager.callback, args, defined[fullName]); - } catch (e) { - err = e; } if (fullName) { //If setting exports via "module" is in play, //favor that over return value and exports. After that, //favor a non-undefined return value over exports use. - if (manager.cjsModule && manager.cjsModule.exports !== undefined) { + cjsModule = manager.cjsModule; + if (cjsModule && + cjsModule.exports !== undefined && + //Make sure it is not already the exports value + cjsModule.exports !== defined[fullName]) { ret = defined[fullName] = manager.cjsModule.exports; } else if (ret === undefined && manager.usingExports) { //exports already set the defined value. @@ -560,21 +442,32 @@ var requirejs, require, define; } else { //Use the return value from the function. defined[fullName] = ret; + //If this module needed full execution in a build + //environment, mark that now. + if (needFullExec[fullName]) { + fullExec[fullName] = true; + } } } } else if (fullName) { //May just be an object definition for the module. Only //worry about defining if have a module name. ret = defined[fullName] = cb; + + //If this module needed full execution in a build + //environment, mark that now. + if (needFullExec[fullName]) { + fullExec[fullName] = true; + } } //Clean up waiting. Do this before error calls, and before - //calling back waitingCallbacks, so that bookkeeping is correct + //calling back listeners, so that bookkeeping is correct //in the event of an error and error is reported in correct order, - //since the waitingCallbacks will likely have errors if the + //since the listeners will likely have errors if the //onError function does not throw. - if (waiting[manager.waitId]) { - delete waiting[manager.waitId]; + if (waiting[manager.id]) { + delete waiting[manager.id]; manager.isDone = true; context.waitCount -= 1; if (context.waitCount === 0) { @@ -583,66 +476,241 @@ var requirejs, require, define; } } + //Do not need to track manager callback now that it is defined. + delete managerCallbacks[fullName]; + + //Allow instrumentation like the optimizer to know the order + //of modules executed and their dependencies. + if (req.onResourceLoad && !manager.placeholder) { + req.onResourceLoad(context, map, manager.depArray); + } + if (err) { + errFile = (fullName ? makeModuleMap(fullName).url : '') || + err.fileName || err.sourceURL; + errModuleTree = err.moduleTree; err = makeError('defineerror', 'Error evaluating ' + 'module "' + fullName + '" at location "' + - (fullName ? makeModuleMap(fullName).url : '') + '":\n' + - err + '\nfileName:' + (err.fileName || err.sourceURL) + + errFile + '":\n' + + err + '\nfileName:' + errFile + '\nlineNumber: ' + (err.lineNumber || err.line), err); err.moduleName = fullName; + err.moduleTree = errModuleTree; return req.onError(err); } - if (fullName) { - //If anything was waiting for this module to be defined, - //notify them now. - waitingCallbacks = managerCallbacks[fullName]; - if (waitingCallbacks) { - for (i = 0; i < waitingCallbacks.length; i++) { - waitingCallbacks[i].onDep(fullName, ret); - } - delete managerCallbacks[fullName]; - } + //Let listeners know of this manager's value. + for (i = 0; (cb = listeners[i]); i++) { + cb(ret); } return undefined; } + /** + * Helper that creates a callack function that is called when a dependency + * is ready, and sets the i-th dependency for the manager as the + * value passed to the callback generated by this function. + */ + function makeArgCallback(manager, i) { + return function (value) { + //Only do the work if it has not been done + //already for a dependency. Cycle breaking + //logic in forceExec could mean this function + //is called more than once for a given dependency. + if (!manager.depDone[i]) { + manager.depDone[i] = true; + manager.deps[i] = value; + manager.depCount -= 1; + if (!manager.depCount) { + //All done, execute! + execManager(manager); + } + } + }; + } + + function callPlugin(pluginName, depManager) { + var map = depManager.map, + fullName = map.fullName, + name = map.name, + plugin = plugins[pluginName] || + (plugins[pluginName] = defined[pluginName]), + load; + + //No need to continue if the manager is already + //in the process of loading. + if (depManager.loading) { + return; + } + depManager.loading = true; + + load = function (ret) { + depManager.callback = function () { + return ret; + }; + execManager(depManager); + + loaded[depManager.id] = true; + + //The loading of this plugin + //might have placed other things + //in the paused queue. In particular, + //a loader plugin that depends on + //a different plugin loaded resource. + resume(); + }; + + //Allow plugins to load other code without having to know the + //context or how to "complete" the load. + load.fromText = function (moduleName, text) { + /*jslint evil: true */ + var hasInteractive = useInteractive; + + //Indicate a the module is in process of loading. + loaded[moduleName] = false; + context.scriptCount += 1; + + //Indicate this is not a "real" module, so do not track it + //for builds, it does not map to a real file. + context.fake[moduleName] = true; + + //Turn off interactive script matching for IE for any define + //calls in the text, then turn it back on at the end. + if (hasInteractive) { + useInteractive = false; + } + + req.exec(text); + + if (hasInteractive) { + useInteractive = true; + } + + //Support anonymous modules. + context.completeLoad(moduleName); + }; + + //No need to continue if the plugin value has already been + //defined by a build. + if (fullName in defined) { + load(defined[fullName]); + } else { + //Use parentName here since the plugin's name is not reliable, + //could be some weird string with no path that actually wants to + //reference the parentName's path. + plugin.load(name, makeRequire(map.parentMap, true), load, config); + } + } + + /** + * Adds the manager to the waiting queue. Only fully + * resolved items should be in the waiting queue. + */ + function addWait(manager) { + if (!waiting[manager.id]) { + waiting[manager.id] = manager; + waitAry.push(manager); + context.waitCount += 1; + } + } + + /** + * Function added to every manager object. Created out here + * to avoid new function creation for each manager instance. + */ + function managerAdd(cb) { + this.listeners.push(cb); + } + + function getManager(map, shouldQueue) { + var fullName = map.fullName, + prefix = map.prefix, + plugin = prefix ? plugins[prefix] || + (plugins[prefix] = defined[prefix]) : null, + manager, created, pluginManager; + + if (fullName) { + manager = managerCallbacks[fullName]; + } + + if (!manager) { + created = true; + manager = { + //ID is just the full name, but if it is a plugin resource + //for a plugin that has not been loaded, + //then add an ID counter to it. + id: (prefix && !plugin ? + (managerCounter++) + '__p@:' : '') + + (fullName || '__r@' + (managerCounter++)), + map: map, + depCount: 0, + depDone: [], + depCallbacks: [], + deps: [], + listeners: [], + add: managerAdd + }; + + specified[manager.id] = true; + + //Only track the manager/reuse it if this is a non-plugin + //resource. Also only track plugin resources once + //the plugin has been loaded, and so the fullName is the + //true normalized value. + if (fullName && (!prefix || plugins[prefix])) { + managerCallbacks[fullName] = manager; + } + } + + //If there is a plugin needed, but it is not loaded, + //first load the plugin, then continue on. + if (prefix && !plugin) { + pluginManager = getManager(makeModuleMap(prefix), true); + pluginManager.add(function (plugin) { + //Create a new manager for the normalized + //resource ID and have it call this manager when + //done. + var newMap = makeModuleMap(map.originalName, map.parentMap), + normalizedManager = getManager(newMap, true); + + //Indicate this manager is a placeholder for the real, + //normalized thing. Important for when trying to map + //modules and dependencies, for instance, in a build. + manager.placeholder = true; + + normalizedManager.add(function (resource) { + manager.callback = function () { + return resource; + }; + execManager(manager); + }); + }); + } else if (created && shouldQueue) { + //Indicate the resource is not loaded yet if it is to be + //queued. + loaded[manager.id] = false; + queueDependency(manager); + addWait(manager); + } + + return manager; + } + function main(inName, depArray, callback, relModuleMap) { var moduleMap = makeModuleMap(inName, relModuleMap), name = moduleMap.name, fullName = moduleMap.fullName, - uniques = {}, - manager = { - //Use a wait ID because some entries are anon - //async require calls. - waitId: name || reqWaitIdPrefix + (waitIdCounter++), - depCount: 0, - depMax: 0, - prefix: moduleMap.prefix, - name: name, - fullName: fullName, - deps: {}, - depArray: depArray, - callback: callback, - onDep: function (depName, value) { - if (!(depName in manager.deps)) { - manager.deps[depName] = value; - manager.depCount += 1; - if (manager.depCount === manager.depMax) { - //All done, execute! - execManager(manager); - } - } - } - }, - i, depArg, depName, cjsMod; + manager = getManager(moduleMap), + id = manager.id, + deps = manager.deps, + i, depArg, depName, depPrefix, cjsMod; if (fullName) { //If module already defined for context, or already loaded, //then leave. Also leave if jQuery is registering but it does //not match the desired version number in the config. - if (fullName in defined || loaded[fullName] === true || + if (fullName in defined || loaded[id] === true || (fullName === "jquery" && config.jQuery && config.jQuery !== callback().fn.jquery)) { return; @@ -652,8 +720,8 @@ var requirejs, require, define; //as part of a layer, where onScriptLoad is not fired //for those cases. Do this after the inline define and //dependency tracing is done. - specified[fullName] = true; - loaded[fullName] = true; + specified[id] = true; + loaded[id] = true; //If module is jQuery set up delaying its dom ready listeners. if (fullName === "jquery" && callback) { @@ -661,6 +729,13 @@ var requirejs, require, define; } } + //Attach real depArray and callback to the manager. Do this + //only if the module has not been defined already, so do this after + //the fullName checks above. IE can call main() more than once + //for a module. + manager.depArray = depArray; + manager.callback = callback; + //Add the dependencies to the deps field, and register for callbacks //on the dependencies. for (i = 0; i < depArray.length; i++) { @@ -672,6 +747,7 @@ var requirejs, require, define; //Split the dependency name into plugin and name parts depArg = makeModuleMap(depArg, (name ? moduleMap : relModuleMap)); depName = depArg.fullName; + depPrefix = depArg.prefix; //Fix the name in depArray to be just the name, since //that is how it will be called back later. @@ -679,46 +755,54 @@ var requirejs, require, define; //Fast path CommonJS standard dependencies. if (depName === "require") { - manager.deps[depName] = makeRequire(moduleMap); + deps[i] = makeRequire(moduleMap); } else if (depName === "exports") { //CommonJS module spec 1.1 - manager.deps[depName] = defined[fullName] = {}; + deps[i] = defined[fullName] = {}; manager.usingExports = true; } else if (depName === "module") { //CommonJS module spec 1.1 - manager.cjsModule = cjsMod = manager.deps[depName] = { + manager.cjsModule = cjsMod = deps[i] = { id: name, uri: name ? context.nameToUrl(name, null, relModuleMap) : undefined, exports: defined[fullName] }; - cjsMod.setExports = makeSetExports(cjsMod); - } else if (depName in defined && !(depName in waiting)) { - //Module already defined, no need to wait for it. - manager.deps[depName] = defined[depName]; - } else if (!uniques[depName]) { + } else if (depName in defined && !(depName in waiting) && + (!(fullName in needFullExec) || + (fullName in needFullExec && fullExec[depName]))) { + //Module already defined, and not in a build situation + //where the module is a something that needs full + //execution and this dependency has not been fully + //executed. See r.js's requirePatch.js for more info + //on fullExec. + deps[i] = defined[depName]; + } else { + //Mark this dependency as needing full exec if + //the current module needs full exec. + if (fullName in needFullExec) { + needFullExec[depName] = true; + //Reset state so fully executed code will get + //picked up correctly. + delete defined[depName]; + urlFetched[depArg.url] = false; + } - //A dynamic dependency. - manager.depMax += 1; - - queueDependency(depArg); - - //Register to get notification when dependency loads. - (managerCallbacks[depName] || - (managerCallbacks[depName] = [])).push(manager); - - uniques[depName] = true; + //Either a resource that is not loaded yet, or a plugin + //resource for either a plugin that has not + //loaded yet. + manager.depCount += 1; + manager.depCallbacks[i] = makeArgCallback(manager, i); + getManager(depArg, true).add(manager.depCallbacks[i]); } } } //Do not bother tracking the manager if it is all done. - if (manager.depCount === manager.depMax) { + if (!manager.depCount) { //All done, execute! execManager(manager); } else { - waiting[manager.waitId] = manager; - waitAry.push(manager); - context.waitCount += 1; + addWait(manager); } } @@ -728,10 +812,6 @@ var requirejs, require, define; */ function callDefMain(args) { main.apply(null, args); - //Mark the module loaded. Must do it here in addition - //to doing it in define in case a script does - //not call define - loaded[args[0]] = true; } /** @@ -780,9 +860,10 @@ var requirejs, require, define; return undefined; } - var fullName = manager.fullName, + var fullName = manager.map.fullName, depArray = manager.depArray, - depName, i; + i, depName, depManager, prefix, prefixManager, value; + if (fullName) { if (traced[fullName]) { return defined[fullName]; @@ -791,14 +872,24 @@ var requirejs, require, define; traced[fullName] = true; } - //forceExec all of its dependencies. - for (i = 0; i < depArray.length; i++) { - //Some array members may be null, like if a trailing comma - //IE, so do the explicit [i] access and check if it has a value. - depName = depArray[i]; - if (depName) { - if (!manager.deps[depName] && waiting[depName]) { - manager.onDep(depName, forceExec(waiting[depName], traced)); + //Trace through the dependencies. + if (depArray) { + for (i = 0; i < depArray.length; i++) { + //Some array members may be null, like if a trailing comma + //IE, so do the explicit [i] access and check if it has a value. + depName = depArray[i]; + if (depName) { + //First, make sure if it is a plugin resource that the + //plugin is not blocked. + prefix = makeModuleMap(depName).prefix; + if (prefix && (prefixManager = waiting[prefix])) { + forceExec(prefixManager, traced); + } + depManager = waiting[depName]; + if (depManager && !depManager.isDone && loaded[depName]) { + value = forceExec(depManager, traced); + manager.depCallbacks[i](value); + } } } } @@ -892,6 +983,11 @@ var requirejs, require, define; forceExec(manager, {}); } + //If anything got placed in the paused queue, run it down. + if (context.paused.length) { + resume(); + } + //Only allow this recursion to a certain depth. Only //triggered by errors in calling a module in which its //modules waiting on it cannot finish loading, or some circular @@ -913,136 +1009,11 @@ var requirejs, require, define; return undefined; } - function callPlugin(pluginName, dep) { - var name = dep.name, - fullName = dep.fullName, - load; - - //Do not bother if plugin is already defined or being loaded. - if (fullName in defined || fullName in loaded) { - return; - } - - if (!plugins[pluginName]) { - plugins[pluginName] = defined[pluginName]; - } - - //Only set loaded to false for tracking if it has not already been set. - if (!loaded[fullName]) { - loaded[fullName] = false; - } - - load = function (ret) { - //Allow the build process to register plugin-loaded dependencies. - if (req.onPluginLoad) { - req.onPluginLoad(context, pluginName, name, ret); - } - - execManager({ - prefix: dep.prefix, - name: dep.name, - fullName: dep.fullName, - callback: function () { - return ret; - } - }); - loaded[fullName] = true; - }; - - //Allow plugins to load other code without having to know the - //context or how to "complete" the load. - load.fromText = function (moduleName, text) { - /*jslint evil: true */ - var hasInteractive = useInteractive; - - //Indicate a the module is in process of loading. - context.loaded[moduleName] = false; - context.scriptCount += 1; - - //Turn off interactive script matching for IE for any define - //calls in the text, then turn it back on at the end. - if (hasInteractive) { - useInteractive = false; - } - - req.exec(text); - - if (hasInteractive) { - useInteractive = true; - } - - //Support anonymous modules. - context.completeLoad(moduleName); - }; - - //Use parentName here since the plugin's name is not reliable, - //could be some weird string with no path that actually wants to - //reference the parentName's path. - plugins[pluginName].load(name, makeRequire(dep.parentMap, true), load, config); - } - - function loadPaused(dep) { - //Renormalize dependency if its name was waiting on a plugin - //to load, which as since loaded. - if (dep.prefix && dep.name.indexOf('__$p') === 0 && defined[dep.prefix]) { - dep = makeModuleMap(dep.originalName, dep.parentMap); - } - - var pluginName = dep.prefix, - fullName = dep.fullName, - urlFetched = context.urlFetched; - - //Do not bother if the dependency has already been specified. - if (specified[fullName] || loaded[fullName]) { - return; - } else { - specified[fullName] = true; - } - - if (pluginName) { - //If plugin not loaded, wait for it. - //set up callback list. if no list, then register - //managerCallback for that plugin. - if (defined[pluginName]) { - callPlugin(pluginName, dep); - } else { - if (!pluginsQueue[pluginName]) { - pluginsQueue[pluginName] = []; - (managerCallbacks[pluginName] || - (managerCallbacks[pluginName] = [])).push({ - onDep: function (name, value) { - if (name === pluginName) { - var i, oldModuleMap, ary = pluginsQueue[pluginName]; - - //Now update all queued plugin actions. - for (i = 0; i < ary.length; i++) { - oldModuleMap = ary[i]; - //Update the moduleMap since the - //module name may be normalized - //differently now. - callPlugin(pluginName, - makeModuleMap(oldModuleMap.originalName, oldModuleMap.parentMap)); - } - delete pluginsQueue[pluginName]; - } - } - }); - } - pluginsQueue[pluginName].push(dep); - } - } else { - if (!urlFetched[dep.url]) { - req.load(context, fullName, dep.url); - urlFetched[dep.url] = true; - } - } - } - /** * Resumes tracing of dependencies and then checks if everything is loaded. */ resume = function () { - var args, i, p; + var manager, map, url, i, p, args, fullName; resumeDepth += 1; @@ -1073,9 +1044,33 @@ var requirejs, require, define; //Reset paused list context.paused = []; - for (i = 0; (args = p[i]); i++) { - loadPaused(args); + for (i = 0; (manager = p[i]); i++) { + map = manager.map; + url = map.url; + fullName = map.fullName; + + //If the manager is for a plugin managed resource, + //ask the plugin to load it now. + if (map.prefix) { + callPlugin(map.prefix, manager); + } else { + //Regular dependency. + if (!urlFetched[url] && !loaded[fullName]) { + req.load(context, fullName, url); + + //Mark the URL as fetched, but only if it is + //not an empty: URL, used by the optimizer. + //In that case we need to be sure to call + //load() for each module that is mapped to + //empty: so that dependencies are satisfied + //correctly. + if (url.indexOf('empty:') !== 0) { + urlFetched[url] = true; + } + } + } } + //Move the start time for timeout forward. context.startTime = (new Date()).getTime(); context.pausedCount -= p.length; @@ -1107,12 +1102,15 @@ var requirejs, require, define; specified: specified, loaded: loaded, urlMap: urlMap, + urlFetched: urlFetched, scriptCount: 0, - urlFetched: {}, defined: defined, paused: [], pausedCount: 0, plugins: plugins, + needFullExec: needFullExec, + fake: {}, + fullExec: fullExec, managerCallbacks: managerCallbacks, makeModuleMap: makeModuleMap, normalize: normalize, @@ -1178,7 +1176,6 @@ var requirejs, require, define; //Allow tracing some require calls to allow the fetching //of the priority config. context.requireWait = false; - //But first, call resume to register any defined modules that may //be in a data-main built file before the priority config //call. Also grab any waiting define calls for this context. @@ -1205,12 +1202,6 @@ var requirejs, require, define; if (cfg.deps || cfg.callback) { context.require(cfg.deps || [], cfg.callback); } - - //Set up ready callback, if asked. Useful when require is defined as a - //config object before require.js is loaded. - if (cfg.ready) { - req.ready(cfg.ready); - } }, requireDefined: function (moduleName, relModuleMap) { @@ -1224,6 +1215,11 @@ var requirejs, require, define; require: function (deps, callback, relModuleMap) { var moduleName, fullName, moduleMap; if (typeof deps === "string") { + if (isFunction(callback)) { + //Invalid call + return req.onError(makeError("requireargs", "Invalid require call")); + } + //Synchronous access to one module. If require.get is //available (as in the Node adapter), prefer that. //In this case deps is the moduleName and callback is @@ -1250,7 +1246,11 @@ var requirejs, require, define; return defined[fullName]; } - main(null, deps, callback, relModuleMap); + //Call main but only if there are dependencies or + //a callback to call. + if (deps && deps.length || callback) { + main(null, deps, callback, relModuleMap); + } //If the require call does not trigger anything new to load, //then resume the dependency processing. @@ -1263,7 +1263,7 @@ var requirejs, require, define; resume(); } } - return undefined; + return context.require; }, /** @@ -1321,16 +1321,6 @@ var requirejs, require, define; } : null]); } - //Mark the script as loaded. Note that this can be different from a - //moduleName that maps to a define call. This line is important - //for traditional browser scripts. - loaded[moduleName] = true; - - //If a global jQuery is defined, check for it. Need to do it here - //instead of main() since stock jQuery does not register as - //a module via define. - jQueryCheck(); - //Doing this scriptCount decrement branching because sync envs //need to decrement after resume, otherwise it looks like //loading is complete after the first dependency is fetched. @@ -1475,10 +1465,18 @@ var requirejs, require, define; return context.require(deps, callback); }; + /** + * Support require.config() to make it easier to cooperate with other + * AMD loaders on globally agreed names. + */ + req.config = function (config) { + return req(config); + }; + /** * Export require as a global, but only if it does not already exist. */ - if (typeof require === "undefined") { + if (!require) { require = req; } @@ -1491,17 +1489,13 @@ var requirejs, require, define; }; req.version = version; - req.isArray = isArray; - req.isFunction = isFunction; - req.mixin = mixin; + //Used to filter out dependencies that are already paths. req.jsExtRegExp = /^\/|:|\?|\.js$/; s = req.s = { contexts: contexts, //Stores a list of URLs that should not get async script tag treatment. - skipAsync: {}, - isPageLoaded: !isBrowser, - readyCalls: [] + skipAsync: {} }; req.isAsync = req.isBrowser = isBrowser; @@ -1535,14 +1529,7 @@ var requirejs, require, define; * @param {Object} url the URL to the module. */ req.load = function (context, moduleName, url) { - var loaded = context.loaded; - - isDone = false; - - //Only set loaded to false for tracking if it has not already been set. - if (!loaded[moduleName]) { - loaded[moduleName] = false; - } + req.resourcesReady(false); context.scriptCount += 1; req.attach(url, context, moduleName); @@ -1579,7 +1566,7 @@ var requirejs, require, define; * return a value to define the module corresponding to the first argument's * name. */ - define = req.def = function (name, deps, callback) { + define = function (name, deps, callback) { var node, context; //Allow for anonymous functions @@ -1591,14 +1578,14 @@ var requirejs, require, define; } //This module may not have dependencies - if (!req.isArray(deps)) { + if (!isArray(deps)) { callback = deps; deps = []; } //If no name, and callback is a function, then figure out if it a //CommonJS thing with dependencies. - if (!name && !deps.length && req.isFunction(callback)) { + if (!deps.length && isFunction(callback)) { //Remove comments from the callback string, //look for require calls, and pull them into the dependencies, //but only if there are function args. @@ -1623,13 +1610,12 @@ var requirejs, require, define; //work. if (useInteractive) { node = currentlyAddingScript || getInteractiveScript(); - if (!node) { - return req.onError(makeError("interactive", "No matching script interactive for " + callback)); + if (node) { + if (!name) { + name = node.getAttribute("data-requiremodule"); + } + context = contexts[node.getAttribute("data-requirecontext")]; } - if (!name) { - name = node.getAttribute("data-requiremodule"); - } - context = contexts[node.getAttribute("data-requirecontext")]; } //Always save off evaluating the def call until the script onload handler. @@ -1669,6 +1655,25 @@ var requirejs, require, define; return callback.apply(exports, args); }; + + /** + * Adds a node to the DOM. Public function since used by the order plugin. + * This method should not normally be called by outside code. + */ + req.addScriptToDom = function (node) { + //For some cache cases in IE 6-8, the script executes before the end + //of the appendChild execution, so to tie an anonymous define + //call to the module name (which is stored on the node), hold on + //to a reference to this node, but clear after the DOM insertion. + currentlyAddingScript = node; + if (baseElement) { + head.insertBefore(node, baseElement); + } else { + head.appendChild(node); + } + currentlyAddingScript = null; + }; + /** * callback for script loads, used to check status of loading. * @@ -1684,7 +1689,7 @@ var requirejs, require, define; var node = evt.currentTarget || evt.srcElement, contextName, moduleName, context; - if (evt.type === "load" || readyRegExp.test(node.readyState)) { + if (evt.type === "load" || (node && readyRegExp.test(node.readyState))) { //Reset interactive script so a script node is not held onto for //to long. interactiveScript = null; @@ -1718,9 +1723,14 @@ var requirejs, require, define; * @param {moduleName} the name of the module that is associated with the script. * @param {Function} [callback] optional callback, defaults to require.onScriptLoad * @param {String} [type] optional type, defaults to text/javascript + * @param {Function} [fetchOnlyFunction] optional function to indicate the script node + * should be set up to fetch the script but do not attach it to the DOM + * so that it can later be attached to execute it. This is a way for the + * order plugin to support ordered loading in IE. Once the script is fetched, + * but not executed, the fetchOnlyFunction will be called. */ - req.attach = function (url, context, moduleName, callback, type) { - var node, loaded; + req.attach = function (url, context, moduleName, callback, type, fetchOnlyFunction) { + var node; if (isBrowser) { //In the browser so use a script tag callback = callback || req.onScriptLoad; @@ -1761,23 +1771,36 @@ var requirejs, require, define; //However, IE reports the script as being in "interactive" //readyState at the time of the define call. useInteractive = true; - node.attachEvent("onreadystatechange", callback); + + + if (fetchOnlyFunction) { + //Need to use old school onreadystate here since + //when the event fires and the node is not attached + //to the DOM, the evt.srcElement is null, so use + //a closure to remember the node. + node.onreadystatechange = function (evt) { + //Script loaded but not executed. + //Clear loaded handler, set the real one that + //waits for script execution. + if (node.readyState === 'loaded') { + node.onreadystatechange = null; + node.attachEvent("onreadystatechange", callback); + fetchOnlyFunction(node); + } + }; + } else { + node.attachEvent("onreadystatechange", callback); + } } else { node.addEventListener("load", callback, false); } node.src = url; - //For some cache cases in IE 6-8, the script executes before the end - //of the appendChild execution, so to tie an anonymous define - //call to the module name (which is stored on the node), hold on - //to a reference to this node, but clear after the DOM insertion. - currentlyAddingScript = node; - if (baseElement) { - head.insertBefore(node, baseElement); - } else { - head.appendChild(node); + //Fetch only means waiting to attach to DOM after loaded. + if (!fetchOnlyFunction) { + req.addScriptToDom(node); } - currentlyAddingScript = null; + return node; } else if (isWebWorker) { //In a web worker, use importScripts. This is not a very @@ -1786,9 +1809,6 @@ var requirejs, require, define; //are in play, the expectation that a build has been done so that //only one script needs to be loaded anyway. This may need to be //reevaluated if other use cases become common. - loaded = context.loaded; - loaded[moduleName] = false; - importScripts(url); //Account for anonymous modules @@ -1835,36 +1855,8 @@ var requirejs, require, define; } } - //Set baseUrl based on config. - s.baseUrl = cfg.baseUrl; - - //****** START page load functionality **************** - /** - * Sets the page as loaded and triggers check for all modules loaded. - */ - req.pageLoaded = function () { - if (!s.isPageLoaded) { - s.isPageLoaded = true; - if (scrollIntervalId) { - clearInterval(scrollIntervalId); - } - - //Part of a fix for FF < 3.6 where readyState was not set to - //complete so libraries like jQuery that check for readyState - //after page load where not getting initialized correctly. - //Original approach suggested by Andrea Giammarchi: - //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html - //see other setReadyState reference for the rest of the fix. - if (setReadyState) { - document.readyState = "complete"; - } - - req.callReady(); - } - }; - //See if there is nothing waiting across contexts, and if not, trigger - //callReady. + //resourcesReady. req.checkReadyState = function () { var contexts = s.contexts, prop; for (prop in contexts) { @@ -1874,26 +1866,22 @@ var requirejs, require, define; } } } - s.isDone = true; - req.callReady(); + req.resourcesReady(true); }; /** - * Internal function that calls back any ready functions. If you are - * integrating RequireJS with another library without require.ready support, - * you can define this method to call your page ready code instead. + * Internal function that is triggered whenever all scripts/resources + * have been loaded by the loader. Can be overridden by other, for + * instance the domReady plugin, which wants to know when all resources + * are loaded. */ - req.callReady = function () { - var callbacks = s.readyCalls, i, callback, contexts, context, prop; + req.resourcesReady = function (isReady) { + var contexts, context, prop; - if (s.isPageLoaded && s.isDone) { - if (callbacks.length) { - s.readyCalls = []; - for (i = 0; (callback = callbacks[i]); i++) { - callback(); - } - } + //First, set the public variable indicating that resources are loading. + req.resourcesDone = isReady; + if (req.resourcesDone) { //If jQuery with DOM ready delayed, release it now. contexts = s.contexts; for (prop in contexts) { @@ -1908,62 +1896,23 @@ var requirejs, require, define; } }; - /** - * Registers functions to call when the page is loaded - */ - req.ready = function (callback) { - if (s.isPageLoaded && s.isDone) { - callback(); - } else { - s.readyCalls.push(callback); + //FF < 3.6 readyState fix. Needed so that domReady plugin + //works well in that environment, since require.js is normally + //loaded via an HTML script tag so it will be there before window load, + //where the domReady plugin is more likely to be loaded after window load. + req.pageLoaded = function () { + if (document.readyState !== "complete") { + document.readyState = "complete"; } - return req; }; - if (isBrowser) { if (document.addEventListener) { - //Standards. Hooray! Assumption here that if standards based, - //it knows about DOMContentLoaded. - document.addEventListener("DOMContentLoaded", req.pageLoaded, false); - window.addEventListener("load", req.pageLoaded, false); - //Part of FF < 3.6 readystate fix (see setReadyState refs for more info) if (!document.readyState) { - setReadyState = true; document.readyState = "loading"; + window.addEventListener("load", req.pageLoaded, false); } - } else if (window.attachEvent) { - window.attachEvent("onload", req.pageLoaded); - - //DOMContentLoaded approximation, as found by Diego Perini: - //http://javascript.nwbox.com/IEContentLoaded/ - if (self === self.top) { - scrollIntervalId = setInterval(function () { - try { - //From this ticket: - //http://bugs.dojotoolkit.org/ticket/11106, - //In IE HTML Application (HTA), such as in a selenium test, - //javascript in the iframe can't see anything outside - //of it, so self===self.top is true, but the iframe is - //not the top window and doScroll will be available - //before document.body is set. Test document.body - //before trying the doScroll trick. - if (document.body) { - document.documentElement.doScroll("left"); - req.pageLoaded(); - } - } catch (e) {} - }, 30); - } - } - - //Check if document already complete, and if so, just trigger page load - //listeners. NOTE: does not work with Firefox before 3.6. To support - //those browsers, manually call require.pageLoaded(). - if (document.readyState === "complete") { - req.pageLoaded(); } } - //****** END page load functionality **************** //Set up default context. If require was a configuration object, use that as base config. req(cfg); @@ -1985,10 +1934,6 @@ var requirejs, require, define; //global queue, assign them to this context. ctx.takeGlobalQueue(); - //Allow for jQuery to be loaded/already in the page, and if jQuery 1.4.3, - //make sure to hold onto it for readyWait triggering. - ctx.jQueryCheck(); - if (!ctx.scriptCount) { ctx.resume(); }