systemjs/loader.js
2013-08-26 13:51:43 +02:00

696 lines
24 KiB
JavaScript

/*
* ES6 RequireJS-style module loader
*
* Supports RequireJS-inspired map, packages and plugins.
*
* https://github.com/jspm/jspm-loader
*
* MIT
*
*/
(function() {
(function() {
var isBrowser = typeof window != 'undefined';
var global = isBrowser ? window : exports;
var startConfig = global.jspm || {};
var config = {};
config.waitSeconds = 20;
config.map = config.map || {};
config.locations = config.locations || {};
config.depends = config.depends || {};
global.createLoader = function(Module, Loader, System) {
delete global.createLoader;
config.baseURL = config.baseURL || isBrowser ? document.URL.substring(0, window.location.href.lastIndexOf('\/') + 1) : './';
config.locations.plugin = config.locations.plugin || config.baseURL;
// -- helpers --
// es6 module regexs to check if it is a module or a global script
var importRegEx = /^\s*import\s+./m;
var exportRegEx = /^\s*export\s+(\{|\*|var|class|function|default)/m;
var moduleRegEx = /^\s*module\s+("[^"]+"|'[^']+')\s*\{/m;
// AMD and CommonJS regexs for support
var amdDefineRegEx = /^\s*define\s*\(\s*("[^"]+"\s*,|'[^']+'\s*,)?\s*(\[(\s*("[^"]+"|'[^']+')\s*,)*(\s*("[^"]+"|'[^']+')\s*)?\])?/m;
var cjsDefineRegEx = /^\s*define\s*\(\s*(function\s*|{|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*\))/m;
var cjsRequireRegEx = /\s*require\s*\(\s*("([^"]+)"|'([^']+)')\s*\)/gm;
var cjsExportsRegEx = /\s*exports\s*\[\s*('[^']+'|"[^"]+")\s*\]|\exports\s*\.\s*[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*|exports\s*\=/m;
// regex to check absolute urls
var absUrlRegEx = /^\/|([^\:\/]*:\/\/)/;
// function to remove the comments from a string
function removeComments(str) {
// output
var curOutIndex = 0,
outString = '';
// mode variables
var singleQuote = false,
doubleQuote = false,
regex = false,
blockComment = false,
lineComment = false;
// character buffer
var lastChar, curChar,
nextChar = str.charAt(0);
for (var i = 0, l = str.length; i < l; i++) {
lastChar = curChar;
curChar = nextChar;
nextChar = str.charAt(i + 1);
if (singleQuote) {
if (curChar === "'" && lastChar !== '\\')
singleQuote = false;
}
else if (doubleQuote) {
if (curChar === '"' && lastChar !== '\\')
doubleQuote = false;
}
else if (regex) {
if (curChar === '/' && lastChar !== '\\')
regex = false;
}
else if (blockComment) {
if (curChar === '/' && lastChar === '*') {
blockComment = false;
curOutIndex = i + 1;
}
}
else if (lineComment) {
if (nextChar === '\n' || nextChar === '\r' || nextChar == '') {
lineComment = false;
curOutIndex = i + 1;
}
}
else {
doubleQuote = curChar === '"';
singleQuote = curChar === "'";
if (curChar !== '/')
continue;
if (nextChar === '*') {
blockComment = true;
outString += str.substring(curOutIndex, i);
}
else if (nextChar === '/') {
lineComment = true;
outString += str.substring(curOutIndex, i);
}
else {
regex = true;
}
}
}
return outString + str.substr(curOutIndex);
}
// configuration object extension
// objects extend, everything else replaces
var extend = function(objA, objB) {
for (var p in objB) {
if (typeof objA[p] == 'object' && !(objA[p] instanceof Array))
extend(objA[p], objB[p])
else
objA[p] = objB[p];
}
}
// check if a module name starts with a given prefix
// the key check is not to match the prefix 'jquery'
// to the module name jquery-ui/some/thing, and only
// to jquery/some/thing or jquery:some/thing
// (multiple ':'s is a module name error)
var prefixMatch = function(name, prefix) {
var prefixParts = prefix.split(/[\/:]/);
var nameParts = name.split(/[\/:]/);
if (prefixParts.length > nameParts.length)
return false;
for (var i = 0; i < prefixParts.length; i++)
if (nameParts[i] != prefixParts[i])
return false;
return true;
}
// check if the module is defined on a location
var getLocation = function(name) {
var locationParts = name.split(':');
return locationParts[1] !== undefined && !name.match(absUrlRegEx) ? locationParts[0] : '';
}
// given a resolved module name and normalized parent name,
// apply the map configuration
var applyMap = function(name, parentName) {
parentName = parentName || '';
var location = getLocation(name);
var parentLocation = getLocation(parentName);
// if there is a parent location, and there is no location, add it here
if (parentLocation && !location)
name = parentLocation + ':' + name;
// check for most specific map config
var parentPrefixMatch = ''; // the matching parent refix
var mapPrefixMatch = ''; // the matching map prefix
var mapMatch = ''; // the matching map value
for (var p in config.map) {
var curMap = config.map[p];
// do the global map check
if (p == '*')
continue;
if (typeof curMap == 'string') {
if (!prefixMatch(name, p))
continue;
if (p.length <= mapPrefixMatch.length)
continue;
mapPrefixMatch = p;
mapMatch = curMap;
}
if (!prefixMatch(parentName, p))
continue;
// now check if this matches our current name
for (var _p in curMap) {
if (!prefixMatch(name, _p))
continue;
// the most specific mapPrefix wins first
if (_p.length < mapPrefixMatch.length)
continue;
// then the most specific prefixMatch on the parent name
if (_p.length == mapPrefixMatch.length && p.length < parentPrefixMatch.length)
continue;
parentPrefixMatch = p;
mapPrefixMatch = _p;
mapMatch = curMap[_p];
}
}
// now compare against the global map config
for (var _p in config.map['*'] || {}) {
if (!prefixMatch(name, _p))
continue;
if (_p.length <= mapPrefixMatch.length)
continue;
mapPrefixMatch = _p;
mapMatch = config.map['*'][_p];
}
// apply map config
if (mapPrefixMatch)
name = mapMatch + name.substr(mapPrefixMatch.length);
return name;
}
// given a module's global dependencies, prepare the global object
// to contain the union of the defined properties of its dependent modules
var globalObj = {};
function setGlobal(deps) {
// first, we add all the dependency module properties to the global
if (deps) {
for (var i = 0; i < deps.length; i++) {
var dep = deps[i];
for (var m in dep)
jspm.global[m] = dep[m];
}
}
// now we store a complete copy of the global object
// in order to detect changes
for (var g in jspm.global) {
if (jspm.global.hasOwnProperty(g))
globalObj[g] = jspm.global[g];
}
}
// go through the global object to find any changes
// the differences become the returned global for this object
// the global object is left as is
function getGlobal() {
var moduleGlobal = {};
for (var g in jspm.global) {
if (jspm.global.hasOwnProperty(g) && g != 'window' && globalObj[g] != jspm.global[g])
moduleGlobal[g] = jspm.global[g];
}
return moduleGlobal;
}
var pluginRegEx = /(\.[^\/\.]+)?!(.*)/;
var nodeProcess = {
nextTick: function(f) {
setTimeout(f, 7);
}
};
// -- /helpers --
var jspm = global.jspm = new Loader({
normalize: function(name, referer) {
name = name.trim();
// allow inline depends configuration
var inlineShim;
if (name.indexOf('|') != -1) {
var inlineShim = name.split('|');
name = inlineShim.splice(0, 1)[0].trim();
}
var parentName = referer && referer.name;
// if it has a js extension, and not a url or plugin, remove the js extension
if (!pluginMatch && name.substr(name.length - 3, 3) == '.js' && !name.match(absUrlRegEx))
name = name.substr(0, name.length - 3);
// check for a plugin (some/name!plugin)
var pluginMatch = name.match(pluginRegEx);
// if a plugin, remove the plugin part to do normalization
var pluginName;
if (pluginMatch) {
pluginName = pluginMatch[2] || pluginMatch[1].substr(1);
name = name.substr(0, name.length - pluginMatch[2].length - 1);
}
if (name.substr(0, 1) != '#') {
// treat an initial '/' as location relative
if (name.substr(0, 1) == '/')
name = name.substr(1);
// do standard normalization (resolve relative module name)
name = System.normalize(name, referer);
// do map config
name = applyMap(name, parentName);
}
if (pluginName)
name = name + '!' + pluginName;
// apply inline depends configuration
if (inlineShim) {
// normalize inline depends
for (var i = 0; i < inlineShim.length; i++)
inlineShim[i] = jspm.normalize(inlineShim[i], { name: name });
var sConfig = {};
sConfig[name] = inlineShim;
jspm.config({ depends: sConfig });
}
return name;
},
resolve: function(name, options) {
var pluginMatch = name.match(pluginRegEx);
// remove plugin part
if (pluginMatch)
name = name.substr(0, name.length - pluginMatch[2].length - 1);
// ondemand
for (var r in this.ondemandTable)
if (this.ondemandTable[r].indexOf(name) != -1)
return name;
if (name.match(absUrlRegEx))
return name;
// locations
var location = getLocation(name);
// js extension
if (!pluginMatch)
name += '.js';
if (location)
name = config.locations[location] + (config.locations[location].substr(config.locations[location].length - 1, 1) != '/' ? '/' : '') + name.substring(location.length + 1);
else
// always cache bust on the local baseURL
name = this.baseURL + (this.baseURL.substr(this.baseURL.length - 1, 1) != '/' ? '/' : '') + name;// + '?' + (new Date()).getTime();
return name;
},
fetch: function(url, callback, errback, options) {
options = options || {};
var pluginMatch = (options.normalized || '').match(pluginRegEx);
if (!pluginMatch) {
// do a fetch with a timeout
var rejected = false;
if (config.waitSeconds) {
var waitTime = 0;
setTimeout(function() {
waitTime++;
if (waitTime >= config.waitSeconds) {
rejected = true;
errback();
}
}, 1000);
}
System.fetch(url, function(source) {
if (!rejected)
callback(source);
}, errback, options);
return;
}
// for plugins, we first need to load the plugin module itself
var pluginName = pluginMatch[2];
jspm.import('plugin:' + pluginName, function(plugin) {
plugin(options.normalized.substr(0, options.normalized.indexOf('!')), url, jspm.fetch, callback, errback);
});
},
link: function(source, options) {
source = source || '';
if (config.onLoad)
config.onLoad(options.normalized, source, options);
if (source.match(importRegEx) || source.match(exportRegEx) || source.match(moduleRegEx))
return;
// remove comments before doing regular expressions
source = removeComments(source);
var match;
// depends config
var _imports = config.depends[options.normalized] ? [].concat(config.depends[options.normalized]) : [];
// check if this module uses AMD form
// define([.., .., ..], ...)
// define('modulename', [.., ..., ..])
if ((match = source.match(amdDefineRegEx)) && (match[2] || match[1])) {
_imports = _imports.concat(eval(match[2] || '[]'));
// remove any reserved words
var requireIndex, exportsIndex, moduleIndex;
if ((requireIndex = _imports.indexOf('require')) != -1)
_imports.splice(requireIndex, 1);
if ((exportsIndex = _imports.indexOf('exports')) != -1)
_imports.splice(exportsIndex, 1);
if ((moduleIndex = _imports.indexOf('module')) != -1)
_imports.splice(moduleIndex, 1);
return {
imports: _imports,
execute: function() {
var deps = Array.prototype.splice.call(arguments, 0);
// add system dependencies
var exports;
if (moduleIndex != -1)
deps.splice(moduleIndex, 0, { id: options.normalized, uri: options.address });
if (exportsIndex != -1)
deps.splice(exportsIndex, 0, exports = {});
if (requireIndex != -1)
deps.splice(requireIndex, 0, function(names, callback, errback) {
if (typeof names == 'object' && !(names instanceof Array))
return require.apply(null, Array.prototype.splice.call(arguments, 1));
return jspm.require(names, callback, errback, { name: options.normalized, address: options.address });
});
var output;
jspm.global.require = jspm.global.requirejs = jspm.require;
jspm.global.define = function(name, dependencies, factory) {
// anonymous define
if (typeof name != 'string') {
factory = dependencies;
name = undefined;
}
// no dependencies
if (!(dependencies instanceof Array))
factory = dependencies;
// run the factory function
if (typeof factory == 'function')
output = factory.apply(jspm.global, deps);
// otherwise factory is the value
else
output = factory;
if (name && name != options.normalized)
jspm.set(name, { default: output });
}
jspm.global.define.amd = {};
scopedEval(source, jspm.global, options.address);
delete jspm.global.define;
delete jspm.global.require;
delete jspm.global.requirejs;
return new Module({ 'default': output || exports });
}
};
}
// check if it uses the AMD CommonJS form
// define(varName); || define(function() {}); || define({})
if (source.match(cjsDefineRegEx)) {
var match;
while (match = cjsRequireRegEx.exec(source))
_imports.push(match[2] || match[3]);
return {
imports: _imports,
execute: function() {
var depMap = {};
for (var i = 0; i < _imports.length; i++)
depMap[_imports[i]] = arguments[i]['default'] || arguments[i];
var exports = {};
var module = { id: options.normalized, uri: options.address };
jspm.global.require = jspm.global.requirejs = jspm.require;
var output;
jspm.global.define = function(factory) {
output = typeof factory == "function" ? factory.call(jspm.global, function(d) {
return depMap[d];
}, exports) : factory;
};
jspm.global.define.amd = {};
scopedEval(source, jspm.global, options.address);
delete jspm.global.require;
delete jspm.global.requirejs;
delete jspm.global.define;
return new Module({ 'default': output || exports });
}
};
}
// CommonJS
// require('...') || exports[''] = ... || exports.asd = ... || module.exports = ...
if (source.match(cjsExportsRegEx) || source.match(cjsRequireRegEx)) {
var match;
while (match = cjsRequireRegEx.exec(source))
_imports.push(match[2] || match[3]);
return {
imports: _imports, // clone the array as we still need it
execute: function(deps) {
var depMap = {};
for (var i = 0; i < _imports.length; i++)
depMap[_imports[i]] = arguments[i]['default'] || arguments[i];
var exports = {};
var dirname = options.address.split('/');
dirname.pop();
dirname = dirname.join('/');
var global = {
process: nodeProcess,
console: console,
require: function(d) {
return depMap[d]
},
__filename: options.address,
__dirname: dirname,
module: { exports: exports },
exports: exports
};
var process = global.process;
var require = global.require;
var __filename = global.__filename;
var __dirname = global.__dirname;
var module = global.module;
var exports = global.exports;
eval(source + (options.address ? '\n//# sourceURL=' + options.address : ''));
return new Module({ 'default': global.module.exports });
}
};
}
// global script
return {
// apply depends config
imports: _imports,
execute: function(deps) {
if (source == '')
return new Module({});
setGlobal(deps);
scopedEval(source, jspm.global, options.address);
return new Module(getGlobal());
}
};
}
});
var _import = jspm.import;
jspm.import = function(name, callback, errback, referer) {
_import.call(jspm, name, function() {
var newArgs = [];
for (var i = 0; i < arguments.length; i++) {
var isDefaultOnly = true;
for (var q in arguments[i])
if (arguments[i].hasOwnProperty(q)) {
if (q != 'default') {
isDefaultOnly = false;
break;
}
}
if (isDefaultOnly && arguments[i] && arguments[i].default)
newArgs[i] = arguments[i].default;
else
newArgs[i] = arguments[i];
}
if (callback)
callback.apply(null, newArgs);
}, errback, referer);
}
jspm.baseURL = config.baseURL;
// ondemand functionality
jspm.ondemandTable = {};
jspm.ondemand = System.ondemand;
jspm._config = config;
jspm.config = function(newConfig) {
if (newConfig.paths)
extend(newConfig.map = newConfig.map || {}, newConfig.paths);
if (newConfig.packages)
throw 'Package configuration not support';
extend(config, newConfig);
if (newConfig.baseURL)
jspm.baseURL = newConfig.baseURL;
if (newConfig.baseUrl)
jspm.baseURL = newConfig.baseUrl;
}
jspm.ondemand = function(resolvers) {
jspm.ondemand(resolvers);
}
/*
AMD & CommonJS-compatible require
To copy RequireJS, set window.require = window.requirejs = jspm.require
*/
jspm.require = function(names, callback, errback, referer) {
// in amd, first arg can be a config object
if (typeof names == 'object' && !(names instanceof Array)) {
jspm.config(names);
return jspm.require.apply(null, Array.prototype.splice.call(arguments, 1));
}
if (typeof callback == 'object') {
referer = callback;
callback = undefined;
}
else if (typeof errback == 'object') {
referer = errback;
errback = undefined;
}
// amd require
if (names instanceof Array)
return jspm.import(names, callback, errback, referer);
// commonjs require
else if (typeof names == 'string')
return jspm.get(names);
else
throw 'Invalid require';
}
jspm.require.config = jspm.config;
// add convenience locations
jspm.config({
locations: {
github: 'https://github.jspm.io',
npm: 'https://npm.jspm.io',
cdnjs: 'https://cdnjs.cloudflare.com/ajax/libs',
jspm: 'https://registry.jspm.io',
plugin: 'https://github.jspm.io/jspm/plugins@0.0.6'
}
});
// add initial config
jspm.config(startConfig);
}
// dynamically polyfill the es6 loader if necessary
if (!global.Loader) {
if (!isBrowser) {
var loader = require('es6-module-loader');
global.createLoader(loader.Module, loader.Loader, loader.System);
return;
}
// determine the current script path as the base path
var scripts = document.getElementsByTagName('script');
var head = document.getElementsByTagName('head')[0];
var curPath = scripts[scripts.length - 1].src;
var basePath = curPath.substr(0, curPath.lastIndexOf('/') + 1);
document.write(
'<' + 'script type="text/javascript" src="' + basePath + 'es6-module-loader.js">' + '<' + '/script>' +
'<' + 'script type="text/javascript">' + 'createLoader(Module, Loader, System);' + '<' + '/script>'
);
}
else
createLoader(Module, Loader, System);
})();
// carefully scoped eval with given global
var scopedEval = function(source, global, sourceURL) {
eval('(function(window) { with(global) { ' + source + ' } }).call(global, global);' + (sourceURL ? '\n//# sourceURL=' + sourceURL : ''));
}
})();