es6-module-loader
Advanced tools
Comparing version 0.2.1 to 0.2.3
@@ -8,439 +8,478 @@ /* | ||
*/ | ||
(function () { | ||
(function (global) { | ||
(function() { | ||
var isBrowser = typeof window != 'undefined'; | ||
var global = isBrowser ? window : exports; | ||
// new Loader( options ) - Module loader constructor | ||
// The Loader constructor creates a new loader. The argument is the | ||
// options object | ||
// | ||
// options.global - The loader's global object | ||
// options.intrinsics - The loader's intrinsic methods | ||
// options.strict - should code evaluated in the loader be in strict mode? | ||
// options.normalize( request [, referer] ) - normalize hook | ||
// options.resolve( normalized, { referer, metadata } ) - The URL resolution hook | ||
// options.fetch( resolved, fulfill, reject, { normalized, referer, metadata } ) - The module loading hook | ||
// options.translate( source, { normalized, address, metadata, type } ) - source translation hook | ||
// options.link( source, { normalized, address, metadata, type } ) - the link hook | ||
function Loader(options) { | ||
// Initialization of loader state from options | ||
// new Loader( options ) - Module loader constructor | ||
// The Loader constructor creates a new loader. The argument is the | ||
// options object | ||
// | ||
// options.global - The loader's global object | ||
// options.intrinsics - The loader's intrinsic methods | ||
// options.strict - should code evaluated in the loader be in strict mode? | ||
// options.normalize( request [, referer] ) - normalize hook | ||
// options.resolve( normalized, { referer, metadata } ) - The URL resolution hook | ||
// options.fetch( resolved, fulfill, reject, { normalized, referer, metadata } ) - The module loading hook | ||
// options.translate( source, { normalized, address, metadata, type } ) - source translation hook | ||
// options.link( source, { normalized, address, metadata, type } ) - the link hook | ||
function Loader(options) { | ||
// the global prototype chain is: | ||
// global instance (this._global) -> intrinsics (this._intrinsics) -> initial global (options.global = window) | ||
// global instance is created fresh to have this chain | ||
// also sets global.window = global for full global encapsulation | ||
// Object.create(window) doesn't work... | ||
var Global = function () {} | ||
Global.prototype = options.global || window; | ||
this._intrinsics = new Global(); | ||
// Initialization of loader state from options | ||
// some standard intrinsics can't work through this prototype | ||
// structure so we need to wrap them to allow this global | ||
// abstraction layer | ||
var wrapped = {}; | ||
var wrap = ['addEventListener', 'removeEventListener', 'getComputedStyle', 'setTimeout', 'setInterval']; | ||
for (var i = 0; i < wrap.length; i++) (function (name) { | ||
wrapped[name] = function () { | ||
return window[name].apply(window, arguments); | ||
} | ||
})(wrap[i]); | ||
// the global prototype chain is: | ||
// global instance (this._global) -> intrinsics (this._intrinsics) -> initial global (options.global = window) | ||
// global instance is created fresh to have this chain | ||
// also sets global.window = global for full global encapsulation | ||
// Object.create(window) doesn't work... | ||
var Global = function () {} | ||
Global.prototype = options.global || (isBrowser ? window : {}); | ||
this._builtins = new Global(); | ||
this.defineBuiltins(wrapped); | ||
this.defineBuiltins(options.intrinsics && options.intrinsics._intrinsics || {}); | ||
// some standard builtins can't work through this prototype | ||
// structure so we need to wrap them to allow this global | ||
// abstraction layer | ||
var wrapped = {}; | ||
var wrap = isBrowser ? ['addEventListener', 'removeEventListener', 'getComputedStyle', 'setTimeout', 'setInterval'] : []; | ||
for (var i = 0; i < wrap.length; i++) (function (name) { | ||
wrapped[name] = function () { | ||
return window[name].apply(global, arguments); | ||
} | ||
})(wrap[i]); | ||
Global = function () {} | ||
Global.prototype = this._intrinsics; | ||
this._global = new Global(); | ||
this.defineBuiltins(wrapped); | ||
Object.defineProperty(this._global, 'window', { value: this._global }); | ||
Global = function () {} | ||
Global.prototype = this._builtins; | ||
this._global = new Global(); | ||
this._strict = !!options.strict; | ||
this.normalize = options.normalize || global.System.normalize; | ||
this.resolve = options.resolve || global.System.resolve; | ||
this.fetch = options.fetch || global.System.fetch; | ||
this.translate = options.translate || global.System.translate; | ||
this.link = options.link || global.System.link; | ||
Object.defineProperty(this._global, 'window', { value: this._global }); | ||
// The internal table of module instance objects | ||
this._mios = {}; | ||
this._strict = !!options.strict; | ||
this.normalize = options.normalize || global.System.normalize; | ||
this.resolve = options.resolve || global.System.resolve; | ||
this.fetch = options.fetch || global.System.fetch; | ||
this.translate = options.translate || global.System.translate; | ||
this.link = options.link || global.System.link; | ||
// the internal table of loaded scripts | ||
this._sloaded = {}; | ||
// modules currently loading | ||
// key is normalized name, value is an array of callback functions to be queued (optional) | ||
this._mloads = {}; | ||
// scripts | ||
this._sloads = {}; | ||
} | ||
// The internal table of module instance objects | ||
this._mios = {}; | ||
Object.defineProperty(Loader.prototype, "global", { | ||
configurable: true, | ||
enumerable: true, | ||
get: function () { | ||
return this._global; | ||
// the internal table of loaded scripts | ||
this._sloaded = {}; | ||
// modules currently loading | ||
// key is normalized name, value is an array of callback functions to be queued (optional) | ||
this._mloads = {}; | ||
// scripts | ||
this._sloads = {}; | ||
} | ||
}); | ||
// Loader.prototype.load( address, callback, errback [, referer = null] ) | ||
// | ||
// The load method takes a string representing a module URL and a | ||
// callback that receives the result of loading, compiling, and | ||
// executing the module at that URL. The compiled code is statically | ||
// associated with this loader, and its URL is the given URL. The | ||
// additional callback is used if an error occurs. | ||
// | ||
// load will handle import statements, but export statements are a | ||
// syntax error | ||
Loader.prototype.load = function (url, callback, errback) { | ||
var self = this; | ||
if (url instanceof Array) { | ||
var scriptCnt = 0; | ||
for (var i = 0; i < url.length; i++) (function (i) { | ||
self.load(url[i], function () { | ||
scriptCnt++; | ||
if (scriptCnt == url.length) { | ||
callback && callback(); | ||
} | ||
}, errback) | ||
})(i); | ||
return; | ||
} | ||
if (this._sloaded[url]) { | ||
callback && callback(); | ||
return; | ||
} | ||
Object.defineProperty(Loader.prototype, "global", { | ||
configurable: true, | ||
enumerable: true, | ||
get: function () { | ||
return this._global; | ||
} | ||
}); | ||
// store the callbacks in a load queue for multiple requests | ||
if (this._sloads[url]) { | ||
this._sloads[url].push({ | ||
callback: callback, | ||
errback: errback | ||
}); | ||
return; | ||
} | ||
else { | ||
this._sloads[url] = [{ | ||
callback: callback, | ||
errback: errback | ||
}]; | ||
} | ||
var _callback = function() { | ||
for (var i = 0; i < self._sloads[url].length; i++) | ||
self._sloads[url][i].callback && self._sloads[url][i].callback(); | ||
delete self._sloads[url]; | ||
} | ||
var _errback = function(err) { | ||
var allCalled = true; | ||
for (var i = 0; i < self._sloads[url].length; i++) { | ||
if (self._sloads[url][i].errback) { | ||
self._sloads[url][i].errback(err); | ||
// Loader.prototype.load( address, callback, errback [, referer = null] ) | ||
// | ||
// The load method takes a string representing a module URL and a | ||
// callback that receives the result of loading, compiling, and | ||
// executing the module at that URL. The compiled code is statically | ||
// associated with this loader, and its URL is the given URL. The | ||
// additional callback is used if an error occurs. | ||
// | ||
// load will handle import statements, but export statements are a | ||
// syntax error | ||
Loader.prototype.load = function (url, callback, errback) { | ||
var self = this; | ||
if (url instanceof Array) { | ||
var scriptCnt = 0; | ||
for (var i = 0; i < url.length; i++) (function (i) { | ||
self.load(url[i], function () { | ||
scriptCnt++; | ||
if (scriptCnt == url.length) { | ||
callback && callback(); | ||
} | ||
}, errback) | ||
})(i); | ||
return; | ||
} | ||
if (this._sloaded[url]) { | ||
callback && callback(); | ||
return; | ||
} | ||
// store the callbacks in a load queue for multiple requests | ||
if (this._sloads[url]) { | ||
this._sloads[url].push({ | ||
callback: callback, | ||
errback: errback | ||
}); | ||
return; | ||
} | ||
else { | ||
this._sloads[url] = [{ | ||
callback: callback, | ||
errback: errback | ||
}]; | ||
} | ||
var _callback = function() { | ||
for (var i = 0; i < self._sloads[url].length; i++) | ||
self._sloads[url][i].callback && self._sloads[url][i].callback(); | ||
delete self._sloads[url]; | ||
} | ||
var _errback = function(err) { | ||
var allCalled = true; | ||
for (var i = 0; i < self._sloads[url].length; i++) { | ||
if (self._sloads[url][i].errback) { | ||
self._sloads[url][i].errback(err); | ||
} | ||
else { | ||
allCalled = false; | ||
} | ||
} | ||
else { | ||
allCalled = false; | ||
} | ||
delete self._sloads[url]; | ||
// if any didn't have an error handler, throw | ||
if (!allCalled) | ||
throw err; | ||
} | ||
delete self._sloads[url]; | ||
// if any didn't have an error handler, throw | ||
if (!allCalled) | ||
throw err; | ||
} | ||
this.fetch(url, function (source) { | ||
this.fetch(url, function (source) { | ||
var opt = { | ||
address: url, | ||
type: 'script' | ||
}; | ||
source = self.translate(source, opt); | ||
self._linkExecute(url, source, opt, _callback, _errback, true); | ||
}, _errback); | ||
}; | ||
// Loader.prototype.import( name, callback, errback, referer = null ) | ||
// Asynchronously load a module or sequence of modules by name. | ||
Loader.prototype.import = function (name, callback, errback, referer) { | ||
var self = this; | ||
if (name instanceof Array) { | ||
var modules = []; | ||
var moduleCnt = 0; | ||
var self = this; | ||
for (var i = 0; i < name.length; i++) (function(i) { | ||
self.import(name[i], function(m) { | ||
modules[i] = m; | ||
moduleCnt++; | ||
if (moduleCnt == name.length) { | ||
callback && callback.apply(null, modules); | ||
} | ||
}, errback, referer); | ||
})(i); | ||
return; | ||
} | ||
name = this.normalize(name, referer); | ||
var opt = { | ||
address: url, | ||
type: 'script' | ||
referer: referer, | ||
metadata: typeof name == 'object' ? name.metadata : null | ||
}; | ||
source = self.translate(source, opt); | ||
// name is now the normalized name in this function | ||
if (typeof name != 'string') { | ||
name = name.normalized; | ||
} | ||
self._linkExecute(url, source, opt, _callback, _errback, true); | ||
}, _errback); | ||
}; | ||
if (this._mios[name]) { | ||
return callback && callback(this._mios[name]); | ||
} | ||
// Loader.prototype.import( name, callback, errback, referer = null ) | ||
// Asynchronously load a module or sequence of modules by name. | ||
Loader.prototype.import = function (name, callback, errback, referer) { | ||
var self = this; | ||
if (name instanceof Array) { | ||
var modules = []; | ||
var moduleCnt = 0; | ||
var self = this; | ||
for (var i = 0; i < name.length; i++) (function(i) { | ||
self.import(name[i], function(m) { | ||
modules[i] = m; | ||
moduleCnt++; | ||
if (moduleCnt == name.length) { | ||
callback && callback.apply(null, modules); | ||
// store the callbacks in a load queue for multiple requests | ||
if (this._mloads[name]) { | ||
this._mloads[name].push({ | ||
callback: callback, | ||
errback: errback | ||
}); | ||
return; | ||
} | ||
else { | ||
this._mloads[name] = [{ | ||
callback: callback, | ||
errback: errback | ||
}]; | ||
} | ||
var _callback = function(module) { | ||
self._mios[name] = module; | ||
for (var i = 0; i < self._mloads[name].length; i++) | ||
self._mloads[name][i].callback && self._mloads[name][i].callback(module); | ||
delete self._mloads[name]; | ||
} | ||
var _errback = function(err) { | ||
var allCalled = true; | ||
if (!self._mloads[name]) | ||
throw err; | ||
for (var i = 0; i < self._mloads[name].length; i++) { | ||
if (self._mloads[name][i].errback) { | ||
self._mloads[name][i].errback(err); | ||
} | ||
}, errback); | ||
})(i); | ||
return; | ||
} | ||
name = this.normalize(name, referer); | ||
var opt = { | ||
referer: referer, | ||
metadata: typeof name == 'object' ? name.metadata : null | ||
}; | ||
// name is now the normalized name in this function | ||
if (typeof name != 'string') { | ||
name = name.normalized; | ||
} | ||
if (this._mios[name]) { | ||
return callback && callback(this._mios[name]); | ||
} | ||
// store the callbacks in a load queue for multiple requests | ||
if (this._mloads[name]) { | ||
this._mloads[name].push({ | ||
callback: callback, | ||
errback: errback | ||
}); | ||
return; | ||
} | ||
else { | ||
this._mloads[name] = [{ | ||
callback: callback, | ||
errback: errback | ||
}]; | ||
} | ||
var _callback = function(module) { | ||
self._mios[name] = module; | ||
for (var i = 0; i < self._mloads[name].length; i++) | ||
self._mloads[name][i].callback && self._mloads[name][i].callback(module); | ||
delete self._mloads[name]; | ||
} | ||
var _errback = function(err) { | ||
var allCalled = true; | ||
for (var i = 0; i < self._mloads[name].length; i++) { | ||
if (self._mloads[name][i].errback) { | ||
self._mloads[name][i].errback(err); | ||
else { | ||
allCalled = false; | ||
} | ||
} | ||
else { | ||
allCalled = false; | ||
} | ||
delete self._mloads[name]; | ||
// if any didn't have an error handler, throw | ||
if (!allCalled) | ||
throw err; | ||
} | ||
delete self._mloads[name]; | ||
// if any didn't have an error handler, throw | ||
if (!allCalled) | ||
throw err; | ||
} | ||
var url = this.resolve(name, opt); | ||
var url = this.resolve(name, opt); | ||
if (typeof url != 'string') { | ||
url = url.address; | ||
// NB what to do with 'extra'? | ||
} | ||
if (typeof url != 'string') { | ||
url = url.address; | ||
// NB what to do with 'extra'? | ||
} | ||
opt.normalized = name; | ||
opt.normalized = name; | ||
this.fetch(url, function(source) { | ||
opt.address = url; | ||
opt.type = 'module'; | ||
source = self.translate(source, opt); | ||
self._linkExecute(name, source, opt, _callback, _errback); | ||
}, _errback, opt); | ||
}; | ||
this.fetch(url, function(source) { | ||
opt.address = url; | ||
opt.type = 'module'; | ||
source = self.translate(source, opt); | ||
self._linkExecute(name, source, opt, _callback, _errback); | ||
}, _errback, opt); | ||
}; | ||
// Loader.prototype.fetch | ||
// NB spec issue here - this clashes with the instance fetch function!? | ||
// Loader.prototype.fetch | ||
// NB spec issue here - this clashes with the instance fetch function!? | ||
// _linkExecute - private function | ||
// given a normalized module name, the source, and the options metadata | ||
// run the link and execute hooks, with the callback returning the | ||
// defined module object | ||
// isScript = true implies loading a script so don't define exports | ||
var evalCnt = 0; | ||
Loader.prototype._linkExecute = function (name, source, opt, callback, errback) { | ||
// when no name is given, | ||
// provide a unique name to cache the syntax tree parsing | ||
if (!name) { | ||
name = '__eval' + evalCnt++; | ||
} | ||
// _linkExecute - private function | ||
// given a normalized module name, the source, and the options metadata | ||
// run the link and execute hooks, with the callback returning the | ||
// defined module object | ||
// isScript = true implies loading a script so don't define exports | ||
var evalCnt = 0; | ||
Loader.prototype._linkExecute = function (name, source, opt, callback, errback) { | ||
// when no name is given, | ||
// provide a unique name to cache the syntax tree parsing | ||
if (!name) { | ||
name = '__eval' + evalCnt++; | ||
} | ||
var isScript = opt.type == 'script'; | ||
var isScript = opt.type == 'script'; | ||
var link = this.link(source, opt); | ||
var link = this.link(source, opt); | ||
// 1. module | ||
if (link instanceof Module && !isScript) { | ||
return callback(link); | ||
} | ||
// preload esprima if necessary | ||
var self = this; | ||
ES6Parser.loadEsprima(name, source, function() { | ||
var imports, execute; | ||
// 2. specified imports and execute | ||
if (typeof link == 'object' && !isScript) { | ||
imports = link.imports; | ||
execute = link.execute; | ||
// 1. module | ||
if (link instanceof Module && !isScript) { | ||
return callback(link); | ||
} | ||
// 3. undefined -> default | ||
else | ||
imports = ES6Parser.parseImports(name, source); | ||
// stops an unnecessary load cascade | ||
if (errback.called) | ||
return; | ||
// preload esprima if necessary | ||
var self = this; | ||
ES6Parser.loadEsprima(name, source, function() { | ||
var imports, execute; | ||
// 2. specified imports and execute | ||
if (typeof link == 'object' && !isScript) { | ||
imports = link.imports; | ||
execute = link.execute; | ||
} | ||
// 3. undefined -> default | ||
else { | ||
var defaultLink = self._link(source, opt); | ||
imports = defaultLink.imports; | ||
execute = defaultLink.execute; | ||
} | ||
var _source = source; | ||
var normalizeMap = {}; | ||
execute = execute || function() { | ||
var exports; | ||
// parses export statements and evaluates in the correct context | ||
// returning the exports object | ||
exports = ES6Parser.parseEval(_source, self, { | ||
name: name, | ||
normalizeMap: normalizeMap, | ||
sourceURL: opt.address, | ||
isEval: isScript | ||
}); | ||
// only return exports for a module when not doing script eval | ||
if (name && !isScript) | ||
return new Module(exports || {}); | ||
} | ||
// stops an unnecessary load cascade | ||
if (errback.called) | ||
return; | ||
if (!imports.length) | ||
return callback(execute.call(self)); | ||
if (!imports.length) | ||
return callback(execute.call(self)); | ||
var deps = []; | ||
var depCnt = 0; | ||
for (var i = 0; i < imports.length; i++) (function(i) { | ||
var referer = { name: name, address: opt.address }; | ||
opt.normalizeMap = {}; | ||
// run the normalization to get the canonical module name | ||
// to allow imports to be loaded | ||
var normalized = self.normalize(imports[i], referer); | ||
var deps = []; | ||
var depCnt = 0; | ||
for (var i = 0; i < imports.length; i++) (function(i) { | ||
var referer = { name: name, address: opt.address }; | ||
if (typeof normalized == 'object') | ||
normalized = normalized.normalized; | ||
// run the normalization to get the canonical module name | ||
// to allow imports to be loaded | ||
var normalized = self.normalize(imports[i], referer); | ||
imports[i] = normalizeMap[imports[i]] = normalized; | ||
if (typeof normalized == 'object') | ||
normalized = normalized.normalized; | ||
self.import(imports[i], function (module) { | ||
depCnt++; | ||
deps[i] = module; | ||
if (depCnt == imports.length) { | ||
try { | ||
var output = execute.apply(self, deps); | ||
callback(output); | ||
opt.normalizeMap[imports[i]] = normalized; | ||
self.import(imports[i], function (module) { | ||
depCnt++; | ||
deps[i] = module; | ||
if (depCnt == imports.length) { | ||
try { | ||
var output = execute.apply(self, deps); | ||
callback(output); | ||
} | ||
catch(e) { | ||
errback(e); | ||
return; | ||
} | ||
} | ||
catch(e) { | ||
errback(e); | ||
return; | ||
} | ||
} | ||
}, errback, referer); | ||
})(i); | ||
}, errback, referer); | ||
})(i); | ||
}, errback); | ||
}; | ||
}, errback); | ||
}; | ||
Loader.prototype._link = function(source, opt) { | ||
var self = this; | ||
return { | ||
imports: ES6Parser.parseImports(opt.normalized, source), | ||
execute: function() { | ||
var exports; | ||
// parses export statements and evaluates in the correct context | ||
// returning the exports object | ||
exports = ES6Parser.parseEval(source, self, { | ||
name: opt.normalized, | ||
sourceURL: opt.address, | ||
isEval: opt.type == 'script', | ||
normalizeMap: opt.normalizeMap | ||
}); | ||
// only return exports for a module when not doing script eval | ||
if (opt.normalized && opt.type != 'script') | ||
return new Module(exports || {}); | ||
} | ||
}; | ||
} | ||
// Loader.prototype.eval( source ) | ||
// Synchronously executes a Script non-terminal. | ||
// If the compilation process results in a fetch, a SyntaxError is thrown. | ||
// The compiled code is statically associated with this loader. | ||
Loader.prototype.eval = function (source) { | ||
ES6Parser.parseEval(source, this, { | ||
isEval: true | ||
}); | ||
}; | ||
// Loader.prototype.parseEval( source ) | ||
// Asynchronously executes a Script non-terminal. | ||
// The compiled code is statically associated with this loader. | ||
Loader.prototype.evalAsync = function (source, callback, errback) { | ||
// links and then evals | ||
this._linkExecute(null, source, { type: 'script' }, callback || function() {}, errback || function() {}); | ||
} | ||
// Loader.prototype.eval( source ) | ||
// Synchronously executes a Script non-terminal. | ||
// If the compilation process results in a fetch, a SyntaxError is thrown. | ||
// The compiled code is statically associated with this loader. | ||
Loader.prototype.eval = function (source) { | ||
ES6Parser.parseEval(source, this, { | ||
isEval: true | ||
}); | ||
}; | ||
// Loader.prototype.get ( name ) | ||
// | ||
// Look up a module in the loader’s registry, using a name that is assumed | ||
// to be normalized. | ||
Loader.prototype.get = function (name) { | ||
return this._mios[name] || null; | ||
}; | ||
// Loader.prototype.parseEval( source ) | ||
// Asynchronously executes a Script non-terminal. | ||
// The compiled code is statically associated with this loader. | ||
Loader.prototype.evalAsync = function (source, callback, errback) { | ||
// links and then evals | ||
this._linkExecute(null, source, { type: 'script' }, callback || function() {}, errback || function() {}); | ||
} | ||
// Loader.prototype.get ( name ) | ||
// | ||
// Look up a module in the loader’s registry, using a name that is assumed | ||
// to be normalized. | ||
Loader.prototype.get = function (name) { | ||
return this._mios[name] || null; | ||
}; | ||
// Loader.prototype.set( name, mod ) | ||
// | ||
// Stores (possibly overwriting) a module instance object | ||
// in the loader’s registry, using a name that is assumed to be normalized. | ||
Loader.prototype.set = function (name, mod) { | ||
this._mios[name] = Module(mod); | ||
}; | ||
Loader.prototype.has = function (name) { | ||
return !!this._mios[name]; | ||
}; | ||
// Loader.prototype.set( name, mod ) | ||
// | ||
// Stores (possibly overwriting) a module instance object | ||
// in the loader’s registry, using a name that is assumed to be normalized. | ||
Loader.prototype.set = function (name, mod) { | ||
this._mios[name] = new Module(mod); | ||
}; | ||
Loader.prototype.delete = function (name) { | ||
delete this._mios[name]; | ||
}; | ||
Loader.prototype.has = function (name) { | ||
return !!this._mios[name]; | ||
}; | ||
// Loader.prototype.defineBuiltins( [ obj ] ) | ||
// | ||
// The defineBuiltins method takes an object and defines all the built-in | ||
// objects and functions of the ES6 standard library associated with this | ||
// loader's intrinsics as properties on the object. | ||
Loader.prototype.defineBuiltins = function (o) { | ||
for (var p in o) { | ||
if (o.hasOwnProperty(p)) { | ||
this._intrinsics[p] = o[p]; | ||
Loader.prototype.delete = function (name) { | ||
delete this._mios[name]; | ||
}; | ||
// Loader.prototype.defineBuiltins( [ obj ] ) | ||
// | ||
// The defineBuiltins method takes an object and defines all the built-in | ||
// objects and functions of the ES6 standard library associated with this | ||
// loader's intrinsics as properties on the object. | ||
Loader.prototype.defineBuiltins = function (o) { | ||
for (var p in o) { | ||
if (o.hasOwnProperty(p)) { | ||
this._builtins[p] = o[p]; | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
function Module (o) { | ||
if (typeof o != 'object') throw new TypeError("Expected object"); | ||
if (o instanceof Module) { | ||
return o; | ||
} else { | ||
var self = this; | ||
for (var key in o) { | ||
(function (key) { | ||
Object.defineProperty(self, key, { | ||
configurable: false, | ||
enumerable: true, | ||
get: function () { | ||
return o[key]; | ||
function Module (o) { | ||
if (typeof o != 'object') throw new TypeError("Expected object"); | ||
if (o instanceof Module) { | ||
return o; | ||
} else { | ||
var self = this; | ||
for (var key in o) { | ||
(function (key) { | ||
Object.defineProperty(self, key, { | ||
configurable: false, | ||
enumerable: true, | ||
get: function () { | ||
return o[key]; | ||
} | ||
}); | ||
})(key); | ||
} | ||
} | ||
}; | ||
// Pre-configured Loader instance for easier use | ||
var absUrlRegEx = /^\/|([^\:\/]*:\/\/)/; | ||
var isUrl = function(name) { | ||
return name.substr(name.length - 3, 3) == '.js' || name.match(absUrlRegEx); | ||
} | ||
var fetch; | ||
if (isBrowser) { | ||
fetch = function(url, fulfill, reject) { | ||
var xhr = new XMLHttpRequest(); | ||
xhr.onreadystatechange = function () { | ||
if (xhr.readyState === 4) { | ||
if (xhr.status === 200 || (xhr.status == 0 && xhr.responseText)) { | ||
fulfill(xhr.responseText); | ||
} else { | ||
reject(xhr.statusText || 'XHR error'); | ||
} | ||
}); | ||
})(key); | ||
} | ||
}; | ||
xhr.open("GET", url, true); | ||
xhr.send(null); | ||
} | ||
} | ||
}; | ||
function ToModule (o) { | ||
return new Module(o); | ||
} | ||
// Pre-configured Loader instance for easier use | ||
var absUrlRegEx = /^\/|([^\:\/]*:)/; | ||
var isUrl = function(name) { | ||
return name.substr(name.length - 3, 3) == '.js' || name.match(absUrlRegEx); | ||
} | ||
var defaultSystemLoader = new Loader({ | ||
global: window, | ||
strict: false, | ||
normalize: function(name, referer) { | ||
if (isUrl(name)) | ||
else { | ||
var fs = require('fs'); | ||
fetch = function(url, fulfill, reject) { | ||
return fs.readFile(url, function(err, data) { | ||
if (err) | ||
return reject(err); | ||
else | ||
fulfill(data + ''); | ||
}); | ||
} | ||
} | ||
var defaultSystemLoader = new Loader({ | ||
global: isBrowser ? window : global, | ||
strict: false, | ||
normalize: function(name, referer) { | ||
if (isUrl(name)) | ||
return name; | ||
var parentName = referer && referer.name; | ||
if (name.substr(0, 2) == './') { | ||
var parentParts = parentName.split('/'); | ||
if (!parentParts.length) | ||
var parentParts = parentName && parentName.split('/'); | ||
if (!parentParts || !parentParts.length) | ||
return name.substr(2); | ||
@@ -452,48 +491,37 @@ parentParts.pop(); | ||
if (name.substr(0, 3) == '../') { | ||
var parentParts = parentName.split('/'); | ||
if (!parentParts.length) | ||
var parentParts = parentName && parentName.split('/'); | ||
if (!parentParts || !parentParts.length) | ||
throw "Path below baseUrl"; | ||
parentParts.pop(); | ||
return this.normalize(name.substr(3), { name: parentParts.join('/') }); | ||
return this.normalize('./' + name.substr(3), { name: parentParts.join('/') }); | ||
} | ||
return name; | ||
}, | ||
resolve: function (name, options) { | ||
for (var r in this.ondemandTable) | ||
if (this.ondemandTable[r].indexOf(name) != -1) | ||
}, | ||
resolve: function (name, options) { | ||
for (var r in this.ondemandTable) | ||
if (this.ondemandTable[r].indexOf(name) != -1) | ||
return name; | ||
if (isUrl(name)) | ||
return name; | ||
if (isUrl(name)) | ||
return name; | ||
return this.baseURL + (this.baseURL.substr(this.baseURL.length - 1, 1) != '/' ? '/' : '') + name + (name.split('/').pop().indexOf('.') == -1 ? '.js' : ''); | ||
}, | ||
fetch: function (url, fulfill, reject, options) { | ||
var xhr = new XMLHttpRequest(); | ||
xhr.onreadystatechange = function () { | ||
if (xhr.readyState === 4) { | ||
if (xhr.status === 200 || (xhr.status == 0 && xhr.responseText)) { | ||
fulfill(xhr.responseText); | ||
} else { | ||
reject(xhr.statusText); | ||
} | ||
} | ||
}; | ||
xhr.open("GET", url, true); | ||
xhr.send(null); | ||
}, | ||
translate: function (source, options) { | ||
return source; | ||
}, | ||
link: function (source, options) {} | ||
}); | ||
defaultSystemLoader.baseURL = document.URL.substring(0, window.location.href.lastIndexOf('\/') + 1); | ||
defaultSystemLoader.ondemandTable = {}; | ||
defaultSystemLoader.ondemand = function (ondemandTable) { | ||
for (var r in ondemandTable) { | ||
this.ondemandTable[r] = this.ondemandTable[r] || []; | ||
if (ondemandTable[r] instanceof Array) | ||
this.ondemandTable[r] = this.ondemandTable[r].concat(ondemandTable[r]); | ||
else | ||
this.ondemandTable[r].push(ondemandTable[r]); | ||
return this.baseURL + (this.baseURL.substr(this.baseURL.length - 1, 1) != '/' ? '/' : '') + name + (name.substr(name.length - 3, 3) == '.js' ? '' : '.js'); | ||
}, | ||
fetch: fetch, | ||
translate: function (source, options) { | ||
return source; | ||
}, | ||
link: function (source, options) {} | ||
}); | ||
defaultSystemLoader.baseURL = isBrowser ? document.URL.substring(0, window.location.href.lastIndexOf('\/') + 1) : './'; | ||
defaultSystemLoader.ondemandTable = {}; | ||
defaultSystemLoader.ondemand = function (ondemandTable) { | ||
for (var r in ondemandTable) { | ||
this.ondemandTable[r] = this.ondemandTable[r] || []; | ||
if (ondemandTable[r] instanceof Array) | ||
this.ondemandTable[r] = this.ondemandTable[r].concat(ondemandTable[r]); | ||
else | ||
this.ondemandTable[r].push(ondemandTable[r]); | ||
} | ||
} | ||
} | ||
@@ -503,155 +531,144 @@ | ||
// Syntax Parsing and Source Modifying Polyfills | ||
// Syntax Parsing and Source Modifying Polyfills | ||
// esprima-based parser for module syntax, with pluggable polyfill support | ||
// esprima-based parser for module syntax, with pluggable polyfill support | ||
var scripts = document.getElementsByTagName('script'); | ||
var curPath = scripts[scripts.length - 1].src; | ||
var basePath = curPath.substr(0, curPath.lastIndexOf('/') + 1); | ||
var ES6Parser = { | ||
// iterate the entire syntax tree node object with the given iterator function | ||
traverse: function(object, iterator) { | ||
var key, child; | ||
if (iterator(object) === false) | ||
return; | ||
for (key in object) { | ||
child = object[key]; | ||
if (typeof child == 'object' && child !== null) | ||
this.traverse(child, iterator); | ||
} | ||
}, | ||
// module syntax regexs - may over-classify but not under-classify | ||
// simply designed as a first level check to catch any use of | ||
// module syntax, before loading esprima for deeper analysis | ||
importRegEx: /^\s*import\s+./m, | ||
exportRegEx: /^\s*export\s+(\{|\*|var|class|function|default)/m, | ||
moduleRegEx: /^\s*module\s+("[^"]+"|'[^']+')\s*\{/m, | ||
checkModuleSyntax: function(name, source) { | ||
if (name == null || this.parseNames[name] === undefined) | ||
this.parseNames[name] = source && !!(source.match(this.importRegEx) || source.match(this.exportRegEx) || source.match(this.moduleRegEx)); | ||
return this.parseNames[name]; | ||
}, | ||
loadEsprima: function(name, source, callback, errback) { | ||
if (this.esprima) | ||
return callback(); | ||
// use a regex to check if the source contains 'import', 'export' or 'module' statements | ||
// may incorrectly fire, but the damage is only an http request to do better parsing shortly | ||
if (!this.checkModuleSyntax(name, source)) | ||
return callback(); | ||
var ES6Parser = { | ||
// iterate the entire syntax tree node object with the given iterator function | ||
traverse: function(object, iterator) { | ||
var key, child; | ||
if (iterator(object) === false) | ||
return; | ||
for (key in object) { | ||
child = object[key]; | ||
if (typeof child == 'object' && child !== null) | ||
this.traverse(child, iterator); | ||
} | ||
}, | ||
// module syntax regexs - may over-classify but not under-classify | ||
// simply designed as a first level check to catch any use of | ||
// module syntax, before loading esprima for deeper analysis | ||
importRegEx: /^\s*import\s+./m, | ||
exportRegEx: /^\s*export\s+(\{|\*|var|class|function|default)/m, | ||
moduleRegEx: /^\s*module\s+("[^"]+"|'[^']+')\s*\{/m, | ||
checkModuleSyntax: function(name, source) { | ||
if (name == null || this.parseNames[name] === undefined) | ||
this.parseNames[name] = source && !!(source.match(this.importRegEx) || source.match(this.exportRegEx) || source.match(this.moduleRegEx)); | ||
return this.parseNames[name]; | ||
}, | ||
loadEsprima: function(name, source, callback, errback) { | ||
if (this.esprima) | ||
return callback(); | ||
var self = this; | ||
System.load(basePath + 'esprima-es6.min.js', function() { | ||
self.esprima = System.global.esprima; | ||
callback(); | ||
}); | ||
}, | ||
// store the names of modules which needed to be parsed by esprima | ||
parseNames: {}, | ||
// store the syntax trees for modules parsed by esprima | ||
treeCache: {}, | ||
// parse the list of import module names for a given source | ||
parseImports: function(name, source) { | ||
// regex showed no need for esprima -> return empty | ||
if (!this.checkModuleSyntax(name, source)) | ||
return []; | ||
// use a regex to check if the source contains 'import', 'export' or 'module' statements | ||
// may incorrectly fire, but the damage is only an http request to do better parsing shortly | ||
if (!this.polyfills.length && !this.checkModuleSyntax(name, source)) | ||
return callback(); | ||
var tree = this.treeCache[name] || (this.treeCache[name] = this.esprima.parse(source, { range: true })); | ||
var imports = []; | ||
this.traverse(tree, function(node) { | ||
if (node.type == 'ImportDeclaration') { | ||
var moduleName; | ||
// import 'jquery' as $; | ||
if (node.from.type == 'Literal') | ||
moduleName = node.from.value; | ||
// import * from foo; | ||
else if (node.from.type == 'Path') | ||
moduleName = node.from.body[0].name; | ||
imports.push(moduleName); | ||
// current script tags used to produce the esprima src (converting collection to array) | ||
var scripts = document.getElementsByTagName('script'); | ||
var curScript; | ||
var esprimaSrc; | ||
for (var i = 0; i < scripts.length; i++) { | ||
curScript = scripts[i]; | ||
if (curScript.src.match(/es6-module-loader(\.min)?\.js/)) | ||
esprimaSrc = curScript.src.substr(0, curScript.src.lastIndexOf('/') + 1) + 'esprima-es6.min.js'; | ||
else | ||
esprimaSrc = curScript.getAttribute('data-esprima-src'); | ||
if (esprimaSrc) | ||
break; | ||
} | ||
else if (node.type == 'ExportDeclaration') { | ||
// export ... from blah | ||
// export ... from 'blah' | ||
if (node.specifiers && node.specifiers[0] && node.specifiers[0].from) { | ||
if (node.specifiers[0].from.body == 'Path') | ||
imports.push(node.specifiers[0].from.body[0].name); | ||
else | ||
imports.push(node.specifiers[0].from.value); | ||
} | ||
var self = this; | ||
global.System.load(esprimaSrc, function() { | ||
self.esprima = global.System.global.esprima; | ||
callback(); | ||
}); | ||
}, | ||
// store the names of modules which needed to be parsed by esprima | ||
parseNames: {}, | ||
// store the syntax trees for modules parsed by esprima | ||
treeCache: {}, | ||
// parse the list of import module names for a given source | ||
parseImports: function(name, source) { | ||
// regex showed no need for esprima -> return empty | ||
if (!this.checkModuleSyntax(name, source)) | ||
return []; | ||
try { | ||
var tree = this.treeCache[name] || (this.treeCache[name] = this.esprima.parse(source, { range: true })); | ||
} | ||
}); | ||
return imports; | ||
}, | ||
// allow custom polyfills to be added in the form of syntax functions | ||
addPolyfill: function(polyfill, callback, errback) { | ||
// by virtue of adding a polyfill, we now load esprima by default | ||
this.loadEsprima(null, null, callback, errback); | ||
this.polyfills.push(polyfill); | ||
}, | ||
polyfills: [], | ||
applyPolyfill: function(node, tSource) { | ||
for (var i = 0; i < this.polyfills.length; i++) | ||
this.polyfills[i](node, tSource); | ||
}, | ||
catch(e) { | ||
e.message = 'Esprima parser error in "' + name + '"\n ' + e.message; | ||
throw e; | ||
} | ||
var imports = []; | ||
this.traverse(tree, function(node) { | ||
// runs an eval of code with module syntax | ||
// opt = { | ||
// name: name, // normalized module name, used to load cached syntax tree | ||
// normalizeMap: normalizeMap, // normalization map to save having to renormalize again | ||
// sourceURL: opt.address, // used for source map | ||
// isEval: isScript // indicate if exports should be parsed | ||
// } | ||
// return value is any exports as a plain object | ||
parseEval: function(source, loader, opt) { | ||
if (node.type == 'ImportDeclaration') { | ||
imports.push(node.source.value); | ||
} | ||
// export * from 'foo'; | ||
// export { ... } from 'foo'; | ||
else if (node.type == 'ExportDeclaration' && node.source) { | ||
imports.push(node.source.value); | ||
} | ||
}); | ||
return imports; | ||
}, | ||
// allow custom polyfills to be added in the form of syntax functions | ||
addPolyfill: function(polyfill) { | ||
// by virtue of adding a polyfill, we now load esprima by default | ||
this.loadEsprima(null, null, function(){}, function(){}); | ||
this.polyfills.push(polyfill); | ||
}, | ||
polyfills: [], | ||
applyPolyfill: function(node, tSource) { | ||
for (var i = 0; i < this.polyfills.length; i++) | ||
this.polyfills[i](node, tSource); | ||
}, | ||
// regex showed no need for esprima - normal eval | ||
if (!this.checkModuleSyntax(opt.name, source)) { | ||
var __Loader = loader; | ||
eval('(function(window) { with(__Loader.global) { ' + (loader._strict ? '"use strict";\n' : '') + source + ' } }).call(__Loader.global, __Loader.global); ' + (opt.sourceURL ? '\n//# sourceURL=' + opt.sourceURL : '')); | ||
return; | ||
} | ||
// runs an eval of code with module syntax | ||
// opt = { | ||
// name: name, // normalized module name, used to load cached syntax tree | ||
// normalizeMap: normalizeMap, // normalization map to save having to renormalize again | ||
// sourceURL: opt.address, // used for source map | ||
// isEval: isScript // indicate if exports should be parsed | ||
// } | ||
// return value is any exports as a plain object | ||
parseEval: function(source, loader, opt) { | ||
// NB if no normalizeMap, run normalization function | ||
var tree = this.treeCache[opt.name] || this.esprima.parse(source, { range: true }); | ||
// regex showed no need for esprima - normal eval | ||
if (!this.polyfills.length && !this.checkModuleSyntax(opt.name, source)) { | ||
loader.global.__Loader = loader; | ||
scopedEval((loader._strict ? '"use strict";\n' : '') + source, loader.global, opt.sourceURL); | ||
delete loader.global.__Loader; | ||
return; | ||
} | ||
var normalizeMap = opt.normalizeMap || {}; | ||
var tree = this.treeCache[opt.name] || this.esprima.parse(source, { range: true }); | ||
var tSource = new SourceModifier(source); | ||
var normalizeMap = opt.normalizeMap || {}; | ||
var self = this; | ||
this.traverse(tree, function(node) { | ||
// --- Imports --- | ||
// replaces imports with Loader.get | ||
// https://github.com/ariya/esprima/blob/harmony/test/harmonytest.js#L4067 | ||
var tSource = new SourceModifier(source); | ||
if (node.type == 'ImportDeclaration') { | ||
// import 'jquery' as $; | ||
if (node.from.type == 'Literal' && node.specifiers[0].type != 'ImportSpecifier') { | ||
tSource.replace(node.range[0], node.range[1], | ||
"var " + node.as.name + " = __Loader.get('" + (normalizeMap[node.from.value] || node.from.value) + "');"); | ||
} | ||
var self = this; | ||
this.traverse(tree, function(node) { | ||
// import ... from foo | ||
else { | ||
var moduleName; | ||
if (node.from.body) | ||
moduleName = node.from.body[0].name; | ||
else if (node.from.type == 'Literal') | ||
moduleName = node.from.value; | ||
// --- Imports --- | ||
if (node.type == 'ImportDeclaration') { | ||
var moduleName = normalizeMap[node.source.value] || node.source.value; | ||
moduleName = normalizeMap[moduleName] || moduleName; | ||
// import * from foo; | ||
if (node.specifiers[0].type == 'Glob') | ||
tSource.replace(node.range[0], node.range[1], | ||
"var __module = __Loader.get('" + moduleName + "');" + | ||
"for (var m in __module)" + | ||
" __global[m] = __module[m];"); | ||
// import $ from 'jquery'; | ||
if (node.kind == 'default') { | ||
tSource.replace(node.range[0], node.range[1], "var " + node.specifiers[0].id.name + " = __Loader.get('" + moduleName + "')['default'];"); | ||
} | ||
// import { a } from foo; | ||
// import { a: mapping, of: imports } from foo | ||
else if (node.specifiers[0].type == 'ImportSpecifier') { | ||
// import { ... } from 'jquery'; | ||
else { | ||
var replaceSource = "var __module = __Loader.get('" + moduleName + "');"; | ||
for (var i = 0; i < node.specifiers.length; i++ ) { | ||
replaceSource += "var " + node.specifiers[i].id.name + " = " + | ||
"__module['" + (node.specifiers[i].from ? node.specifiers[i].from.body[0].name : node.specifiers[i].id.name) + "'];"; | ||
for (var i = 0; i < node.specifiers.length; i++) { | ||
var specifier = node.specifiers[i]; | ||
replaceSource += "var " + (specifier.name ? specifier.name.name : specifier.id.name) + " = __module['" + specifier.id.name + "'];"; | ||
} | ||
@@ -661,195 +678,174 @@ tSource.replace(node.range[0], node.range[1], replaceSource); | ||
} | ||
} | ||
// --- Exports --- | ||
// NB throw an error for exports being present when (name && name.substr(0, 6) != '__eval') | ||
// replace exports with __exports.export = ... | ||
// https://github.com/ariya/esprima/blob/harmony/test/harmonytest.js#L3288 | ||
// --- Exports --- | ||
else if (node.type == 'ExportDeclaration') { | ||
// exports = ... | ||
else if (node.type == 'AssignmentExpression' && node.left.name && node.left.name == 'exports') | ||
tSource.replace(node.left.range[0], node.left.range[1], '__exports'); | ||
else if (node.type == 'ExportDeclaration') { | ||
var fromModule; | ||
var exports; | ||
// export ... from blah | ||
// export ... from 'blah' | ||
if (node.specifiers && node.specifiers[0] && node.specifiers[0].from) { | ||
if (node.specifiers[0].from.body == 'Path') | ||
fromModule = node.specifiers[0].from.body[0].name; | ||
else | ||
fromModule = node.specifiers[0].from.value; | ||
} | ||
if (!node.declaration && node.specifiers) { | ||
// export * | ||
if (node.specifiers[0].id.type == 'Glob') | ||
exports = true; | ||
if (node.declaration) { | ||
// export { A: some.thing, B: another.thing, C } | ||
else if (node.specifiers[0].type == 'ExportSpecifierSet') { | ||
exports = {}; | ||
for (var i = 0; i < node.specifiers[0].specifiers.length; i++) { | ||
if (node.specifiers[0].specifiers[i].from) | ||
exports[node.specifiers[0].specifiers[i].id.name] = tSource.getRange.apply(tSource, node.specifiers[0].specifiers[i].from.range); | ||
else | ||
exports[node.specifiers[0].specifiers[i].id.name] = node.specifiers[0].specifiers[i].id.name; | ||
var exportName; | ||
var declarationIndex = node.declaration.range[0] - 1; | ||
if (node.declaration.type == 'VariableDeclaration') { | ||
var declaration = node.declaration.declarations[0]; | ||
// export var p = ... | ||
if (declaration.init) { | ||
exportName = declaration.id.name; | ||
declarationIndex = declaration.init.range[0] - 1; | ||
} | ||
} | ||
// export function q() {} | ||
// export class q {} | ||
else if (node.declaration.type == 'FunctionDeclaration' || node.declaration.type == 'ClassDeclaration') | ||
exportName = node.declaration.id.name; | ||
// export default ... overrides any other name | ||
if (node.default) | ||
exportName = 'default'; | ||
tSource.replace(node.range[0], declarationIndex, "__exports['" + exportName + "'] = "); | ||
} | ||
// export varA, varB, varC | ||
else if (node.specifiers[0].type = 'ExportSpecifier') { | ||
exports = {}; | ||
for (var i = 0; i < node.specifiers.length; i++) | ||
exports[node.specifiers[i].id.name] = node.specifiers[i].id.name; | ||
else if (node.source) { | ||
var moduleName = normalizeMap[node.source.value] || node.source.value; | ||
// export * from 'jquery' | ||
if (node.specifiers[0].type == 'ExportBatchSpecifier') { | ||
tSource.replace(node.range[0], node.range[1], "var __module = __Loader.get('" + moduleName + "'); for (var m in __module) { __exports[m] = __module[m]; }; "); | ||
} | ||
// export { a as b, c as d } from 'jquery' | ||
else { | ||
var replaceSource = "var __module = __Loader.get('" + moduleName + "'); "; | ||
for (var i = 0; i < node.specifiers.length; i++) { | ||
var specifier = node.specifiers[i]; | ||
replaceSource += "__exports['" + (specifier.name ? specifier.name.name : specifier.id.name) + "'] = __module['" + specifier.id.name + "']; "; | ||
} | ||
tSource.replace(node.range[0], node.range[1], replaceSource); | ||
} | ||
} | ||
} | ||
// export var p | ||
else if (node.declaration.type == 'VariableDeclaration') | ||
exports = node.declaration.declarations[0].id.name; | ||
// export function p() {} | ||
else if (node.declaration.type == 'FunctionDeclaration') | ||
exports = node.declaration.id.name; | ||
// export module p {} | ||
// export class p {} | ||
else if (node.declaration.type == 'ModuleDeclaration' || node.declaration.type == 'ClassDeclaration') | ||
exports = node.declaration.declarations[0].id.name; | ||
else { | ||
if (fromModule) { | ||
fromModule = normalize(fromModule); | ||
// export * from someModule | ||
if (exports === true) | ||
tSource.replace(node.range[0], node.range[1], "__exports = __Loader.get('" + fromModule + "');"); | ||
// export { some: var } from someModule | ||
else { | ||
// export {a as b, c as d} | ||
var replaceSource = ""; | ||
for (var e in exports) | ||
replaceSource += "__exports['" + e + "'] = __Loader.get('" + fromModule + "')['" + exports[e] + "'];"; | ||
for (var i = 0; i < node.specifiers.length; i++) { | ||
var specifier = node.specifiers[i]; | ||
replaceSource += "__exports['" + specifier.id.name + "'] = " + (specifier.name ? specifier.name.name : specifier.id.name) + "; "; | ||
} | ||
tSource.replace(node.range[0], node.range[1], replaceSource); | ||
} | ||
} | ||
else if (typeof exports == 'object') { | ||
// export { a, b, c: d } | ||
var replaceSource = ""; | ||
for (var e in exports) | ||
replaceSource += "__exports['" + e + "'] = " + exports[e] + "; "; | ||
tSource.replace(node.range[0], node.range[1], replaceSource); | ||
} | ||
else if (typeof exports == 'string') { | ||
// export var p = 5 etc | ||
if (node.declaration.declarations) | ||
tSource.replace(node.range[0], node.declaration.declarations[0].id.range[0] - 1, "__exports['" + exports + "'] = "); | ||
// export function q() { ... } | ||
else if (node.declaration.type == 'FunctionDeclaration') | ||
tSource.replace(node.range[0], node.declaration.range[0] - 1, "__exports['" + exports + "'] = "); | ||
else | ||
tSource.replace(node.range[0], node.declaration.id.range[0] - 1, "__exports['" + exports + "'] = "); | ||
} | ||
} | ||
// --- Modules --- | ||
// replaces modules with Loader.set | ||
// --- Modules --- | ||
else if (node.type == 'ModuleDeclaration' && node.body.type == 'BlockStatement') { | ||
// module 'foo' { ..code.. } | ||
// -> (function() { var __exports = {}; ..code.. __Loader.set("foo", new Module(__exports)); })(); | ||
else if (node.type == 'ModuleDeclaration') { | ||
var moduleName = node.id.name; | ||
// module foo { ..code.. } | ||
// -> (function() { var __exports = {}; ..code.. __Loader.set("foo", new Module(__exports)); })(); | ||
if (node.body.type == 'BlockStatement') { | ||
tSource.replace(node.range[0], node.body.range[0] + 1, "(function() { var __exports = {}; "); | ||
tSource.replace(node.body.range[1], node.range[1], " __Loader.set('" + moduleName + "', new Module(__exports)); })();"); | ||
tSource.replace(node.body.range[1], node.range[1], " __Loader.set('" + node.id.name + "', new Module(__exports)); })();"); | ||
} | ||
// module names are assumed to be normalized already | ||
// so nested modules not currently supported | ||
} | ||
// --- Polyfills --- | ||
// --- Polyfills --- | ||
else if (self.polyfills.length) | ||
self.applyPolyfill(node, tSource); | ||
}); | ||
else if (self.polyfills.length) | ||
self.applyPolyfill(node, tSource); | ||
}); | ||
delete this.treeCache[opt.name]; | ||
delete this.treeCache[opt.name]; | ||
loader.global.__Loader = loader; | ||
var exports = loader.global.__exports = {}; | ||
var __Loader = loader; | ||
var __exports = {}; | ||
var evalSource = '(function(window) { with(__Loader.global) { ' + (loader._strict ? '"use strict";\n' : '') + tSource.toString() + ' } }).call(__Loader.global, __Loader.global);' + (opt.sourceURL ? '\n//# sourceURL=' + opt.sourceURL : ''); | ||
eval(evalSource); | ||
scopedEval((loader._strict ? '"use strict";\n' : '') + tSource.toString(), loader.global, opt.sourceURL); | ||
// if exports are defined and it is an eval, throw | ||
if (opt.isEval) { | ||
for (var e in __exports) { | ||
throw 'Exports only supported for modules, not script evaluation.' | ||
delete loader.global.__Loader; | ||
delete loader.global.__exports; | ||
// if exports are defined and it is an eval, throw | ||
if (opt.isEval) { | ||
for (var e in exports) { | ||
throw 'Exports only supported for modules, not script evaluation.' | ||
} | ||
} | ||
return exports; | ||
} | ||
}; | ||
return __exports; | ||
if (!isBrowser) | ||
ES6Parser.esprima = require('./esprima-es6.min.js'); | ||
/* | ||
* SourceModifier | ||
* | ||
* Allows for partial modification of a source file based on successive | ||
* range adjustment operations consistent with the original source file | ||
* | ||
* Example: | ||
* 012345678910 | ||
* var h = new SourceModifier('hello world'); | ||
* h.replace(2, 4, 'y'); | ||
* h.replace(6, 10, 'person'); | ||
* h.source == 'hey person'; | ||
* h.rangeOps == [{start: 2, end: 4, diff: -2}, {start: 4, end: 9, diff: 1}] | ||
* | ||
*/ | ||
var SourceModifier = function(source) { | ||
this.source = source; | ||
this.rangeOps = []; | ||
} | ||
}; | ||
SourceModifier.prototype = { | ||
mapIndex: function(index) { | ||
// apply the range operations in order to the index | ||
for (var i = 0; i < this.rangeOps.length; i++) { | ||
var curOp = this.rangeOps[i]; | ||
if (curOp.start >= index) | ||
continue; | ||
if (curOp.end <= index) { | ||
index += curOp.diff; | ||
continue; | ||
} | ||
throw 'Source location ' + index + ' has already been transformed!'; | ||
} | ||
return index; | ||
}, | ||
replace: function(start, end, replacement) { | ||
var diff = replacement.length - (end - start + 1); | ||
/* | ||
* SourceModifier | ||
* | ||
* Allows for partial modification of a source file based on successive | ||
* range adjustment operations consistent with the original source file | ||
* | ||
* Example: | ||
* 012345678910 | ||
* var h = new SourceModifier('hello world'); | ||
* h.replace(2, 4, 'y'); | ||
* h.replace(6, 10, 'person'); | ||
* h.source == 'hey person'; | ||
* h.rangeOps == [{start: 2, end: 4, diff: -2}, {start: 4, end: 9, diff: 1}] | ||
* | ||
*/ | ||
var SourceModifier = function(source) { | ||
this.source = source; | ||
this.rangeOps = []; | ||
} | ||
SourceModifier.prototype = { | ||
mapIndex: function(index) { | ||
// apply the range operations in order to the index | ||
for (var i = 0; i < this.rangeOps.length; i++) { | ||
var curOp = this.rangeOps[i]; | ||
if (curOp.start >= index) | ||
continue; | ||
if (curOp.end <= index) { | ||
index += curOp.diff; | ||
continue; | ||
} | ||
throw 'Source location ' + index + ' has already been transformed!'; | ||
start = this.mapIndex(start); | ||
end = this.mapIndex(end); | ||
this.source = this.source.substr(0, start) + replacement + this.source.substr(end + 1); | ||
this.rangeOps.push({ | ||
start: start, | ||
end: end, | ||
diff: diff | ||
}); | ||
}, | ||
getRange: function(start, end) { | ||
return this.source.substr(this.mapIndex(start), this.mapIndex(end)); | ||
}, | ||
toString: function() { | ||
return this.source; | ||
} | ||
return index; | ||
}, | ||
replace: function(start, end, replacement) { | ||
var diff = replacement.length - (end - start + 1); | ||
}; | ||
start = this.mapIndex(start); | ||
end = this.mapIndex(end); | ||
this.source = this.source.substr(0, start) + replacement + this.source.substr(end + 1); | ||
// Export the Loader class | ||
global.Loader = Loader; | ||
// Export the Module class | ||
global.Module = Module; | ||
// Export the System object | ||
global.System = defaultSystemLoader; | ||
this.rangeOps.push({ | ||
start: start, | ||
end: end, | ||
diff: diff | ||
}); | ||
}, | ||
getRange: function(start, end) { | ||
return this.source.substr(this.mapIndex(start), this.mapIndex(end)); | ||
}, | ||
toString: function() { | ||
return this.source; | ||
} | ||
}; | ||
})(); | ||
// Export the Loader class | ||
global.Loader = Loader; | ||
// Export the Module class | ||
global.Module = Module; | ||
global.ToModule = ToModule; | ||
// Export the System object | ||
global.System = defaultSystemLoader; | ||
// 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 : '')); | ||
} | ||
})(window); | ||
})(); |
{ | ||
"name": "es6-module-loader", | ||
"description": "An ES6 Module Loader shim", | ||
"version": "0.2.1", | ||
"version": "0.2.3", | ||
"homepage": "https://github.com/ModuleLoader/es6-module-loader", | ||
@@ -26,3 +26,5 @@ "author": { | ||
"devDependencies": { | ||
"grunt": "~0.3.0" | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-uglify": "~0.2.2", | ||
"grunt-contrib-jshint": "~0.6.0" | ||
}, | ||
@@ -36,3 +38,3 @@ "keywords": [ | ||
"engines": { | ||
"node": ">= 0.7.9-pre" | ||
"node": ">=0.8.0" | ||
}, | ||
@@ -42,3 +44,4 @@ "main": "lib/es6-module-loader", | ||
"test": "grunt test" | ||
} | ||
} | ||
}, | ||
"files": ["lib/es6-module-loader.js"] | ||
} |
@@ -9,4 +9,4 @@ # ES6 Module Loader | ||
* [Minified build](https://raw.github.com/ModuleLoader/es6-module-loader/master/dist/es6-module-loader.min.js) ~ 12KB | ||
* [Unminified build](https://raw.github.com/ModuleLoader/es6-module-loader/master/dist/es6-module-loader.js) ~ 28KB | ||
* [Minified build](https://raw.github.com/ModuleLoader/es6-module-loader/master/dist/es6-module-loader.min.js) ~ 11KB | ||
* [Unminified](https://raw.github.com/ModuleLoader/es6-module-loader/master/lib/es6-module-loader.js) ~ 26KB | ||
@@ -60,5 +60,8 @@ ## Getting Started | ||
```javascript | ||
var loader = new Loader(Loader, { | ||
var loader = new Loader({ | ||
global: window, | ||
strict: false, | ||
normalize: function (name, referer) { | ||
return normalized(name, referer.name); | ||
}, | ||
resolve: function (normalized, options) { | ||
@@ -68,17 +71,16 @@ return '/' + normalized + '.js'; | ||
fetch: function (url, fulfill, reject, options) { | ||
var xhr = new XMLHttpRequest(); | ||
xhr.onreadystatechange = function () { | ||
if (xhr.readyState === 4) { | ||
if (xhr.status === 200) { | ||
fulfill(xhr.responseText); | ||
} else { | ||
reject(xhr.statusText); | ||
} | ||
fulfill(source); | ||
}, | ||
translate: function (source, options) { | ||
return compile(source); | ||
}, | ||
link: function (source, options) { | ||
return { | ||
imports: ['some', 'dependencies'], | ||
execute: function(depA, depB) { | ||
return new Module({ | ||
some: 'export' | ||
}); | ||
} | ||
}; | ||
xhr.open("GET", url, true); | ||
xhr.send(null); | ||
}, | ||
translate: function (source, options) { | ||
return source; | ||
} | ||
@@ -88,2 +90,6 @@ }); | ||
The above hooks are all optional, using the default System hooks when not present. | ||
For an overview of working with custom loaders, see [Yehuda Katz's essay](https://gist.github.com/wycats/51c96e3adcdb3a68cbc3) or the [ES6 Module Specification](http://wiki.ecmascript.org/doku.php?id=harmony:module_loaders). | ||
Define an ES6 module programatically (useful in optimized / production environments): | ||
@@ -100,16 +106,51 @@ | ||
### Specification Notes | ||
### Syntax Parsing | ||
The polyfill is implemented exactly to the specification now, except for the following items: | ||
The [Esprima ES6 Harmony parser](https://github.com/ariya/esprima/tree/harmony) is being used to do parsing, loaded only when necessary. | ||
* The `extra` metadata property is not yet handled in the resolve. | ||
* The `fetch` function is given a different specification between the prototype (`Loader.prototype.fetch`) and loader instance (`options.fetch`). Since instance functions are provided on the instance object as in the @wycats essay (`System.normalize`, `System.fetch` etc), there seems to be a conflict between these. | ||
* The intrinsics encapsulation is a tricky one to polyfill, but we have done our best based on a global prototype chain behaviour, where `global.__proto__ == intrinsics`. And `intrinsics.__proto__ == window`. All code is evaluated with the `window` and `this` properties referencing the `global` allowing full global encapsulation. | ||
The following module statements are currently supported: | ||
### Syntax Parsing | ||
```javascript | ||
import 'jquery'; // import a module | ||
import $ from 'jquery'; // import the default export of a module | ||
import { $ } from 'jquery'; // import a named export of a module | ||
import { $ as jQuery } from 'jquery'; // import a named export to a different name | ||
The [Esprima ES6 Harmony parser](https://github.com/ariya/esprima/tree/harmony) is being used to do parsing, loaded only when necessary. This parser still uses an older syntax, which is currently the major critical issue to sort out for this polyfill. | ||
export var x = 42; // export a named variable | ||
export function foo() {}; // export a named function | ||
The issue tracking this is here - https://github.com/ModuleLoader/es6-module-loader/issues/10 | ||
export default var x = 42; // export the default export | ||
export default function foo() {}; // export the default export as a function | ||
export default = function foo() {}; // export the default export by assignment | ||
export { encrypt }; // export an existing variable | ||
export { decrypt as dec }; // export a variable as a new name | ||
export { encrypt as en } from 'crypto'; // export an export from another module | ||
export * from 'crypto'; // export all exports from another module | ||
module 'crypto' { ... } // define a module | ||
``` | ||
### NodeJS Support | ||
For use in NodeJS, the `Module`, `Loader` and `System` globals are provided as exports: | ||
``` | ||
var System = require('es6-module-loader').System; | ||
System.import('some-module', callback); | ||
``` | ||
### Custom Esprima Location | ||
To set a custom path to the Esprima Harmony parser, specify the `data-esprima-src` attribute on the `<script>` tag used to include the module loader. | ||
### Specification Notes | ||
The polyfill is implemented exactly to the specification, except where areas are currently under debate. | ||
The only feature which is not possible to fully polyfill is the intrinsics functionality and sandboxing of the loader. Custom builtins and full global encapsulation is still provided. | ||
To follow the current the specification changes, see https://github.com/ModuleLoader/es6-module-loader/issues?labels=specification&page=1&state=open. | ||
## Projects using us | ||
@@ -116,0 +157,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
166
35804
3
3
730
1
3