Skip to content

Commit

Permalink
Update to order plugin that allows using fetch/do not execute until a…
Browse files Browse the repository at this point in the history
…ttached to DOM approach for IE. Means reaching into require.js a bit deeper then desired, but means the ordered code can stay mostly out of require.js, with just some small allowances for it.
  • Loading branch information
jrburke committed Sep 27, 2011
1 parent 9764fb9 commit 64e95f2
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 41 deletions.
117 changes: 89 additions & 28 deletions order.js
Expand Up @@ -12,32 +12,42 @@
/*requirejs namespace: true */

(function () {

//Sadly necessary browser inference due to differences in the way
//that browsers load and execute dynamically inserted javascript
//and whether the script/cache method works.
//Currently, Gecko and Opera do not load/fire onload for scripts with
//and whether the script/cache method works when ordered execution is
//desired. Currently, Gecko and Opera do not load/fire onload for scripts with
//type="script/cache" but they execute injected scripts in order
//unless the 'async' flag is present.
//However, this is all changing in latest browsers implementing HTML5
//spec. Firefox nightly supports using the .async true by default, and
//spec. With compliant browsers .async true by default, and
//if false, then it will execute in order. Favor that test first for forward
//compatibility. However, it is unclear if webkit/IE will follow suit.
//Latest webkit breaks the script/cache trick.
//Test for document and window so that this file can be loaded in
//a web worker/non-browser env. It will not make sense to use this
//plugin in a non-browser env, but the file should not error out if included
//in a file, then loaded in a non-browser env.
var supportsInOrderExecution = typeof document !== "undefined" &&
typeof window !== "undefined" &&
(document.createElement("script").async ||
(window.opera && Object.prototype.toString.call(window.opera) === "[object Opera]") ||
//compatibility.
var testScript = typeof document !== "undefined" &&
typeof window !== "undefined" &&
document.createElement("script"),

supportsInOrderExecution = testScript && (testScript.async ||
((window.opera &&
Object.prototype.toString.call(window.opera) === "[object Opera]") ||
//If Firefox 2 does not have to be supported, then
//a better check may be:
//('mozIsLocallyAvailable' in window.navigator)
("MozAppearance" in document.documentElement.style)),
("MozAppearance" in document.documentElement.style))),

//This test is true for IE browsers, which will load scripts but only
//execute them once the script is added to the DOM.
supportsLoadSeparateFromExecute = testScript &&
testScript.readyState === 'uninitialized',

readyRegExp = /^(complete|loaded)$/,
waiting = [],
cached = {};
cacheWaiting = [],
cached = {},
scriptNodes = {},
scriptWaiting = [];

//Done with the test script.
testScript = null;

//Callback used by the type="script/cache" callback that indicates a script
//has finished downloading.
Expand All @@ -53,7 +63,7 @@
cached[moduleName] = true;

//Find out how many ordered modules have loaded
for (i = 0; (resource = waiting[i]); i++) {
for (i = 0; (resource = cacheWaiting[i]); i++) {
if (cached[resource.name]) {
resource.req([resource.name], resource.onLoad);
} else {
Expand All @@ -63,9 +73,9 @@
}
}

//If just loaded some items, remove them from waiting.
//If just loaded some items, remove them from cacheWaiting.
if (i > 0) {
waiting.splice(0, i);
cacheWaiting.splice(0, i);
}

//Remove this script tag from the DOM
Expand All @@ -77,25 +87,76 @@
}
}

/**
* Used for the IE case, where fetching is done by creating script element
* but not attaching it to the DOM. This function will be called when that
* happens so it can be determined when the node can be attached to the
* DOM to trigger its execution.
*/
function onFetchOnly(node) {
var i, loadedNode, resourceName;

//Mark this script as loaded.
node.setAttribute('data-orderloaded', 'loaded');

//Cycle through waiting scripts. If the matching node for them
//is loaded, and is in the right order, add it to the DOM
//to execute the script.
for (i = 0; (resourceName = scriptWaiting[i]); i++) {
loadedNode = scriptNodes[resourceName];
if (loadedNode &&
loadedNode.getAttribute('data-orderloaded') === 'loaded') {
delete scriptNodes[resourceName];
require.addScriptToDom(loadedNode);
} else {
break;
}
}

//If just loaded some items, remove them from waiting.
if (i > 0) {
scriptWaiting.splice(0, i);
}
}

define({
version: '0.26.0',

load: function (name, req, onLoad, config) {
var url = req.nameToUrl(name, null);

//If a build, just load the module as usual.
if (config.isBuild) {
req([name], onLoad);
return;
}
var url = req.nameToUrl(name, null),
node, context;

//Make sure the async attribute is not set for any pathway involving
//this script.
require.s.skipAsync[url] = true;
if (supportsInOrderExecution) {
if (supportsInOrderExecution || config.isBuild) {
//Just a normal script tag append, but without async attribute
//on the script.
req([name], onLoad);
} else if (supportsLoadSeparateFromExecute) {
//Just fetch the URL, but do not execute it yet. The
//non-standards IE case. Really not so nice because it is
//assuming and touching requrejs internals. OK though since
//ordered execution should go away after a long while.
context = require.s.contexts._;

if (!context.urlFetched[url] && !context.loaded[name]) {
//Indicate the script is being fetched.
context.urlFetched[url] = true;

//Stuff from require.load
require.resourcesReady(false);
context.scriptCount += 1;

//Fetch the script now, remember it.
node = require.attach(url, context, name, null, null, onFetchOnly);
scriptNodes[name] = node;
scriptWaiting.push(name);
}

//Do a normal require for it, once it loads, use it as return
//value.
req([name], onLoad);
} else {
//Credit to LABjs author Kyle Simpson for finding that scripts
//with type="script/cache" allow scripts to be downloaded into
Expand All @@ -106,7 +167,7 @@
if (req.specified(name)) {
req([name], onLoad);
} else {
waiting.push({
cacheWaiting.push({
name: name,
req: req,
onLoad: onLoad
Expand Down
64 changes: 51 additions & 13 deletions require.js
Expand Up @@ -1077,6 +1077,7 @@ var requirejs, require, define;
specified: specified,
loaded: loaded,
urlMap: urlMap,
urlFetched: urlFetched,
scriptCount: 0,
defined: defined,
paused: [],
Expand Down Expand Up @@ -1629,6 +1630,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.
*
Expand All @@ -1644,7 +1664,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;
Expand Down Expand Up @@ -1678,8 +1698,13 @@ 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) {
req.attach = function (url, context, moduleName, callback, type, fetchOnlyFunction) {
var node;
if (isBrowser) {
//In the browser so use a script tag
Expand Down Expand Up @@ -1721,23 +1746,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
Expand Down

0 comments on commit 64e95f2

Please sign in to comment.