/** @preserve
* unrequire.js
*
* Copyright 2011 Sibblingz, Inc.
*
* Licensed under MIT
*/
// XXX Lines between comments with @{{{ and @}}} are removed when building
//@{{{
(function () {
//@}}}
// MIT: http://trac.webkit.org/wiki/DetectingWebKit
var IS_WEBKIT = typeof navigator !== 'undefined' && navigator && / AppleWebKit\//.test(navigator.userAgent);
var HAS_SETTIMEOUT = typeof setTimeout === 'function';
//@{{{
// Logging support
var LOGGING = false;
// Show debug warnings
var WARNINGS = true;
// Aliases support
var ENABLE_ALIASES = true;
// Packages support
var ENABLE_PACKAGES = true;
// Web browser support
var ENABLE_BROWSER = true;
// Web browser should try to make synchronous requests
var BROWSER_SYNC = false;
// Node.JS support
var ENABLE_NODEJS = true;
// Spaceport support
var ENABLE_SPACEPORT = true;
// CommonJS compatibility
var COMMONJS_COMPAT = true;
// Check for circular dependencies
var CHECK_CYCLES = true;
//@}}}
// Utility functions {{{
var hasOwnProperty = ({ }).hasOwnProperty;
var toString = ({ }).toString;
// For minification
var dot = '.';
var dotdot = '..';
function hasOwn(obj, name) {
return obj && hasOwnProperty.call(obj, name);
}
function isArray(x) {
return toString.call(x) === '[object Array]';
}
function isPlainOldObject(x) {
return toString.call(x) === '[object Object]';
}
function map(array, fn, context) {
// TODO Fallback if Function.prototype.map is missing
return array.map(fn, context);
}
var forEach = map;
function extend(base, extension) {
var key;
for (key in extension) {
if (hasOwn(extension, key)) {
base[key] = extension[key];
}
}
return base;
}
function clone(object, extension) {
return extend(extend({ }, object), extension || { });
}
// Utility functions }}}
// Path functions {{{
function stringToPath(parts) {
parts = isArray(parts) ? parts : [ parts ];
var splitParts = [ ];
var i;
for (i = 0; i < parts.length; ++i) {
splitParts = splitParts.concat(parts[i].split(/\//g));
}
return splitParts;
}
function pathToString(path) {
var s = path
.join('/')
.replace(/\/+/g, '/');
if (path.length === 0 && path[0] === '') {
s = '/' + s;
}
return s;
}
function normalizePath(path) {
var newPath = [ ];
var i;
for (i = 0; i < path.length; ++i) {
if (!path[i]) {
// Root
newPath = [ '' ];
} else if (path[i] === dotdot) {
// Go back
if (!newPath.length) {
newPath = [ dotdot ];
} else if (newPath.length === 1) {
if (newPath[0] === dot || !newPath[0]) {
newPath = [ dotdot ];
} else {
newPath.pop();
}
} else {
newPath.pop();
}
} else if (path[i] === dot) {
// Go here
if (!newPath.length) {
newPath = [ dot ];
}
} else {
// Everything else
newPath.push(path[i]);
}
}
return newPath;
}
function resolveUrl(cwd, baseUrl, path) {
var cwdPath = normalizePath(stringToPath(cwd));
var basePath = normalizePath(stringToPath(baseUrl || dot));
var npath = normalizePath(stringToPath(path));
if (npath[0] === dotdot || npath[0] === dot) {
// Relative paths are based on cwd
return pathToString(normalizePath(cwdPath.concat(npath)));
} else if (npath[0] === '') {
// Absolute path stays absolute
return pathToString(npath);
} else {
// Implicit relative paths are based on baseUrl
return pathToString(basePath.concat(npath));
}
}
function resolveCwd(baseUrl, cwd) {
var basePath = normalizePath(stringToPath(baseUrl || dot));
var npath = normalizePath(stringToPath(cwd));
if (npath[0] === dotdot || npath[0] === dot) {
// Relative paths are absolute
return pathToString(npath);
} else if (npath[0] === '') {
// Absolute path stays absolute
return pathToString(npath);
} else {
// Implicit relative paths are based on baseUrl
return pathToString(basePath.concat(npath));
}
}
function dirname(url) {
var path = stringToPath(url);
path = path.slice(0, path.length - 1);
return pathToString(path);
}
// Path functions }}}
// Argument extraction functions {{{
function defArgs(name, config, deps, callback) {
if (typeof name !== 'string') {
// Name omitted
callback = deps;
deps = config;
config = name;
name = null;
}
if (!isPlainOldObject(config)) {
// Config omitted
callback = deps;
deps = config;
config = { };
}
if (!isArray(deps)) {
// Dependencies omitted
callback = deps;
deps = [ ];
}
return {
name: name,
config: config,
deps: deps,
callback: callback
};
}
function reqArgs(config, deps, callback) {
// TODO require(string)
if (typeof config === 'string') {
throw new Error('Not supported');
}
if (!isPlainOldObject(config)) {
// Config omitted
callback = deps;
deps = config;
config = { };
}
if (!isArray(deps)) {
// Dependencies omitted
callback = deps;
deps = [ ];
}
return {
config: config,
deps: deps,
callback: callback
};
}
// Argument extraction functions }}}
function getScriptName(moduleName, config) {
if (ENABLE_ALIASES) {
if (hasOwn(config._aliases, moduleName)) {
return config._aliases[moduleName];
}
}
var scriptName = resolveUrl(config.cwd, config.baseUrl, moduleName);
scriptName = scriptName + (/\.js$/i.test(scriptName) ? '' : '.js');
return scriptName;
}
function mergeConfigInto(base, augmentation) {
// The order of these checks are important, because changes cascade
if (hasOwn(augmentation, 'baseUrl')) {
base.baseUrl = resolveUrl(base.cwd, base.baseUrl, augmentation.baseUrl);
}
if (hasOwn(augmentation, 'cwd')) {
base.cwd = augmentation.cwd;
//base.cwd = resolveCwd(base.baseUrl, augmentation.cwd);
}
if (ENABLE_ALIASES) {
if (hasOwn(base, '_aliases')) {
base._aliases = clone(base._aliases);
} else {
base._aliases = { };
}
if (hasOwn(augmentation, '_aliases')) {
extend(base._aliases, augmentation._aliases);
}
if (hasOwn(augmentation, 'aliases')) {
var aliasName;
for (aliasName in augmentation.aliases) {
if (!hasOwn(augmentation.aliases, aliasName)) {
continue;
}
var aliasTarget = augmentation.aliases[aliasName];
// Aliases are stored as their full script name
base._aliases[aliasName] = getScriptName(aliasTarget, base);
}
}
}
if (ENABLE_PACKAGES) {
if (hasOwn(base, '_packageOwners')) {
base._packageOwners = clone(base._packageOwners);
} else {
base._packageOwners = { };
}
if (hasOwn(augmentation, '_packageOwners')) {
extend(base._packageOwners, augmentation._packageOwners);
}
if (hasOwn(augmentation, 'packages')) {
var packageName;
for (packageName in augmentation.packages) {
if (!hasOwn(augmentation.packages, packageName)) {
continue;
}
var packageOwner = getScriptName(packageName, base);
forEach(augmentation.packages[packageName], function (moduleName) {
base._packageOwners[getScriptName(moduleName, base)] = packageOwner;
});
}
}
}
}
function mergeConfigs(first, second) {
var base = clone(first);
mergeConfigInto(base, second);
return base;
}
function findCycles(graph, vertices) {
var vertexIndices = { };
var vertexLowLinks = { };
var index = 0;
var stack = [ ];
var cycles = [ ];
function strongConnect(v) {
vertexIndices[v] = index;
vertexLowLinks[v] = index;
++index;
stack.push(v);
if (hasOwn(graph, v)) {
graph[v].forEach(function (w) {
if (!hasOwn(vertexIndices, w)) {
strongConnect(w);
vertexLowLinks[v] = Math.min(vertexLowLinks[v], vertexLowLinks[w]);
} else if (stack.indexOf(w) >= 0) {
vertexLowLinks[v] = Math.min(vertexLowLinks[v], vertexIndices[w]);
}
});
}
if (vertexLowLinks[v] === vertexIndices[v]) {
var cycle = [ ];
var w;
do {
w = stack.pop();
cycle.push(w);
} while (w !== v);
cycles.push(cycle);
}
}
vertices.forEach(function (vertex) {
if (!hasOwn(vertexIndices, vertex)) {
strongConnect(vertex);
}
});
return cycles;
}
// dependencyGraph :: Map String [String]
var dependencyGraph = { };
// pulledScripts :: [String]
var pulledScripts = [ ];
function addDependency(from, to) {
if (hasOwn(dependencyGraph, from)) {
dependencyGraph[from].push(to);
} else {
dependencyGraph[from] = [ to ];
}
}
function checkCircularDependencies() {
var cycles = findCycles(dependencyGraph, pulledScripts);
cycles.forEach(function (cycle) {
if (cycle.length > 1) {
throw new Error('Circular dependency detected between scripts: ' + cycle.join(' '));
}
});
}
function getScriptsDependingUpon(scriptName) {
var scripts = [ ];
for (var curScript in dependencyGraph) {
if (hasOwn(dependencyGraph, curScript)) {
if (dependencyGraph[curScript].indexOf(scriptName) >= 0) {
scripts.push(curScript);
}
}
}
return scripts;
}
// requestedScripts :: Map String Bool
var requestedScripts = { };
// requestingScriptCount :: Int
var requestingScriptCount = 0;
// We have two queues here.
//
// The script complete queue is built up while executing scripts. A define
// call adds to this queue. The queue is flushed when the script completes
// execution. This allows us to determine which script was executed
// exactly for asynchronous loads.
//
// A load callback queue is built up after a define call knows its complete
// name configuration. It is executed when that defined module is
// requested. This allows for lazy loading of defiend modules, and also
// allows for asynchronous module definitions. There is a mapping of
// script name to load callback queue, thus this queue is a hash and not an
// array.
// scriptCompleteQueue :: [Maybe Error -> Configuration -> IO ()]
var scriptCompleteQueue = [ ];
// loadCallbackQueues :: Map String [IO ()]
var loadCallbackQueues = { };
// The push-pull mechanism decouples requesters of a module from definers
// of a module. When a module is defined, it is "pushed"; when a module is
// requested, it is "pulled". If a pull is made on an already-pushed
// module name, the pull callback is executed immediately. Else, the pull
// callback is executed immediately when the appropriate push is made.
// pushed :: Map String a
var pushed = { };
// pulling :: Map String [Maybe Error -> a -> IO ()]
var pulling = { };
function checkPullForLoadCallback(scriptName) {
if (hasOwn(pulling, scriptName) && hasOwn(loadCallbackQueues, scriptName)) {
var callbacks = loadCallbackQueues[scriptName];
delete loadCallbackQueues[scriptName];
forEach(callbacks, function (callback) {
callback();
});
}
}
function checkPullForPush(scriptName, value) {
if (hasOwn(pulling, scriptName) && hasOwn(pushed, scriptName)) {
var callbacks = pulling[scriptName];
delete pulling[scriptName];
forEach(callbacks, function (callback) {
callback(null, pushed[scriptName]);
});
}
}
function enqueueLoadCallback(scriptName, callback) {
if (hasOwn(loadCallbackQueues, scriptName)) {
loadCallbackQueues[scriptName].push(callback);
} else {
loadCallbackQueues[scriptName] = [ callback ];
}
checkPullForLoadCallback(scriptName);
}
function enqueueScriptCompleteCallback(callback) {
if (requestingScriptCount > 0) {
scriptCompleteQueue.push(callback);
} else {
callback(null, { });
}
}
function push(scriptName, value) {
if (hasOwn(pushed, scriptName)) {
throw new Error('Should not push value for ' + scriptName + ' again');
}
pushed[scriptName] = value;
checkPullForPush(scriptName);
}
function pull(scriptName, callback) {
if (CHECK_CYCLES) {
pulledScripts.push(scriptName);
}
if (hasOwn(pulling, scriptName)) {
pulling[scriptName].push(callback);
} else {
pulling[scriptName] = [ callback ];
}
checkPullForLoadCallback(scriptName);
checkPullForPush(scriptName);
}
function needsRequest(scriptName) {
return !hasOwn(requestedScripts, scriptName) && !hasOwn(pushed, scriptName) && !hasOwn(loadCallbackQueues, scriptName);
}
// Entry points {{{
function create(configuration) {
var context = extend({
'require': req,
'define': def,
'reconfigure': reconfigure,
'userCallback': defaultUserCallback
}, configuration);
var baseConfig = {
cwd: '.',
baseUrl: '.'
};
context.configuration = configuration;
req.config = config;
req.debug = debug;
return context;
function config(config) {
mergeConfigInto(baseConfig, config);
}
function defaultUserCallback(scriptName, data, moduleValues, moduleScripts, moduleNames, callback) {
if (LOGGING) {
console.log('Executing', scriptName);
}
var moduleValue;
if (typeof data === 'function') {
if (COMMONJS_COMPAT && data.length === 3 && moduleNames.length === 0) {
moduleValues = [
// require
function () {
throw new Error('Not supported');
},
// exports
{ },
// module
{ } // TODO
];
}
moduleValue = data.apply(null, moduleValues);
if (COMMONJS_COMPAT && data.length === 3 && moduleNames.length === 0) {
if (typeof moduleValue === 'undefined') {
moduleValue = moduleValues[1]; // exports
}
}
} else {
moduleValue = data;
}
callback(null, moduleValue);
}
function reconfigure(configuration) {
extend(context, configuration);
}
function getRequestScriptName(scriptName, config) {
if (ENABLE_PACKAGES) {
if (hasOwn(config._packageOwners, scriptName)) {
return config._packageOwners[scriptName];
}
}
return scriptName;
}
function request(scriptName, config, callback) {
if (!needsRequest(scriptName)) {
throw new Error('Should not request ' + scriptName + ' again');
}
if (LOGGING) {
console.log('Requesting script ' + scriptName);
}
requestedScripts[scriptName] = true;
++requestingScriptCount;
function done(err) {
--requestingScriptCount;
var scriptCompleteCallbacks = scriptCompleteQueue;
scriptCompleteQueue = [ ];
if (!err) {
if (scriptCompleteCallbacks.length === 0) {
console.warn('Possibly missing define for script ' + scriptName);
}
}
callback(err, scriptCompleteCallbacks);
}
function tryAsync() {
}
// Try a sync load first
if (context.loadScriptSync) {
// We have this setTimeout logic to handle exceptions thrown by
// loadScriptSync. We do not catch exceptions (so debugging is
// easier for users), or deal with the 'finally' mess, but
// still call done().
var timer;
if (HAS_SETTIMEOUT) {
timer = setTimeout(function () {
done(new Error('Script threw exception'));
}, 0);
}
var success = context.loadScriptSync(scriptName, config);
if (HAS_SETTIMEOUT) {
clearTimeout(timer);
}
if (success) {
done(null);
return;
}
}
if (context.loadScriptAsync) {
return context.loadScriptAsync(scriptName, done, config);
}
done(new Error('Failed to load script'));
}
function requestAndPullMany(scriptNames, config, callback) {
var loaded = [ ];
var values = [ ];
var i;
var called = false;
function checkValues() {
if (called) return;
var i;
for (i = 0; i < scriptNames.length; ++i) {
if (!loaded[i]) return;
}
called = true;
callback(null, values, scriptNames);
}
forEach(scriptNames, function (scriptName, i) {
var requestScriptName = getRequestScriptName(scriptName, config);
if (needsRequest(requestScriptName)) {
request(requestScriptName, config, function (err, callbacks) {
var neoConfig = mergeConfigs(config, { });
neoConfig.cwd = dirname(requestScriptName);
neoConfig.scriptName = scriptName;
forEach(callbacks, function (callback) {
callback(err, neoConfig);
});
if (err) {
var errorString = 'Failed to load ' + requestScriptName;
var dependers = getScriptsDependingUpon(requestScriptName);
if (dependers.length) {
errorString += ' (depended upon by ' + dependers.join(', ') + ')';
}
console.error(errorString, err);
}
});
}
pull(scriptName, function (err, value) {
if (err) throw err;
loaded[i] = true;
values[i] = value;
checkValues();
});
});
// In case we have no scripts to load
checkValues();
}
function req() {
// TODO require(string)
var args = reqArgs.apply(null, arguments);
var config = args.config;
var deps = args.deps;
var callback = args.callback;
if (LOGGING) {
console.log('Requiring [ ' + (deps || [ ]).join(', ') + ' ]');
}
var effectiveConfig = mergeConfigs(baseConfig, config);
enqueueScriptCompleteCallback(function (err, config) {
if (err) throw err;
mergeConfigInto(effectiveConfig, config);
var scriptNames = map(deps, function (dep) {
return getScriptName(dep, effectiveConfig);
});
requestAndPullMany(scriptNames, effectiveConfig, function (err, values) {
if (err) throw err;
context.userCallback(null, callback, values, scriptNames, deps.slice(), function (err, value) {
if (err) throw err;
// Ignore value
});
});
});
}
function def() {
var args = defArgs.apply(null, arguments);
var name = args.name;
var config = args.config;
var deps = args.deps;
var callback = args.callback;
if (LOGGING) {
console.log('Defining ' + (name || 'unnamed package') + ' with dependencies [ ' + (deps || [ ]).join(', ') + ' ]');
}
var effectiveConfig = mergeConfigs(baseConfig, config);
enqueueScriptCompleteCallback(function (err, config) {
if (err) throw err;
var oldEffectiveConfig = clone(effectiveConfig);
// Script name resolution should occur *before* merging config into
// effectiveConfig
mergeConfigInto(effectiveConfig, config);
var scriptName;
if (name) {
scriptName = getScriptName(name, effectiveConfig);
} else {
scriptName = config.scriptName;
}
enqueueLoadCallback(scriptName, function () {
var scriptNames = map(deps, function (dep) {
return getScriptName(dep, effectiveConfig);
});
if (CHECK_CYCLES) {
map(scriptNames, function (dep) {
addDependency(scriptName, dep);
});
}
checkCircularDependencies();
requestAndPullMany(scriptNames, effectiveConfig, function (err, values) {
if (err) throw err;
context.userCallback(scriptName, callback, values, scriptNames, deps.slice(), function (err, value) {
if (err) throw err;
push(scriptName, value);
});
});
});
});
}
function debug() {
console.log('Pulling:', pulling);
}
}
// Entry points }}}
(function () {
var un;
if (ENABLE_SPACEPORT && typeof loadScript === 'function') {
// Must be first, because Spaceport has the window object, too.
un = create({
'loadScriptAsync': function (scriptName, callback) {
loadScript(scriptName, function () {
callback(null);
});
},
'loadScriptSync': function (scriptName) {
return false;
}
});
// Set globals
require = un['require'];
define = un['define'];
} else if (ENABLE_BROWSER && typeof window !== 'undefined') {
var goodResponseCodes = [ 200, 204, 206, 301, 302, 303, 304, 307 ];
var doc = window.document;
var onreadystatechange = 'onreadystatechange';
var onload = 'onload';
var onerror = 'onerror';
function isCleanPath(scriptName) {
// If the path is "back" too much, it's not clean.
var x = stringToPath(dirname(window.location.pathname))
.concat(stringToPath(scriptName));
x = normalizePath(x);
return x[0] !== '..';
}
var webkitOnloadFlag = false;
un = create({
'loadScriptAsync': function loadScriptAsync(scriptName, callback) {
if (!isCleanPath(scriptName)) {
setTimeout(function () {
callback(new Error('Path ' + scriptName + ' is not clean'));
}, 0);
return;
}
var script = doc.createElement('script');
script.async = true;
// Modelled after jQuery (src/ajax/script.js)
script[onload] = script[onreadystatechange] = function () {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
// Remove from DOM
var parent = script.parentNode;
if (parent) {
parent.removeChild(script);
}
// IE likes memleaks
script[onload] = script[onreadystatechange] = script[onerror] = null;
script = null;
callback(null);
}
};
script[onerror] = function () {
callback(new Error('Failed to load script'));
};
// Remember: we need to attach event handlers before
// assigning `src`. Events may be fired as soon as we set
// `src`.
script.src = scriptName;
doc['head'].appendChild(script);
},
'loadScriptSync': function loadScriptSync(scriptName) {
// We provide synchronous script loading via XHR for
// browsers specifically to work around a Webkit bug.
// After document.onload is called, any script dynamically
// loaded will be loaded from Webkit's local cache; *no
// HTTP request is made at all*.
if (!BROWSER_SYNC) {
if (IS_WEBKIT) {
if (/loaded|complete/.test(document.readyState)) {
// Don't load synchronously if the document has already loaded
if (WARNINGS && !webkitOnloadFlag) {
console.warn('Scripts being loaded after document.onload; scripts may be loaded from out-of-date cache');
webkitOnloadFlag = true;
}
if (WARNINGS) {
console.warn('Script possibly loaded from out-of-date cache: ' + scriptName);
}
return false;
}
// Fall through; load synchronously anyway
} else {
return false;
}
}
if (!isCleanPath(scriptName)) {
return false;
}
var scriptSource;
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', scriptName, false);
xhr.send(null);
if (goodResponseCodes.indexOf(xhr.status) < 0) {
return false;
}
scriptSource = xhr.responseText;
scriptSource += '\n\n//*/\n//@ sourceURL=' + scriptName;
} catch (e) {
return false;
}
var fn;
try {
fn = Function(scriptSource);
} catch (e) {
return false;
}
// Don't wrap user code in try/catch
fn();
return true;
}
});
window['require'] = un['require'];
window['define'] = un['define'];
} else if (ENABLE_NODEJS && typeof module !== 'undefined') {
un = module.exports = create({
'context': { },
'loadScriptSync': function (scriptName) {
// require here is the Node.JS-provided require
var code;
try {
code = require('fs')['readFileSync'](scriptName, 'utf8');
} catch (e) {
// TODO Detect file-not-found errors only
return false;
}
require('vm')['runInNewContext'](code, un['context'] || { }, scriptName);
return true;
}
});
un['context']['define'] = un['define'];
un['context']['require'] = un['require'];
} else {
throw new Error('Unsupported environment');
}
}());
//@{{{
}());
//@}}}