es6-module-loader
Advanced tools
Comparing version 0.3.3 to 0.4.0
/* | ||
* es6-module-loader | ||
* https://github.com/addyosmani/es6-module-loader | ||
* ES6 Module Loader Polyfill | ||
* https://github.com/ModuleLoader/es6-module-loader | ||
* | ||
* Based on the 2013-12-02 specification draft | ||
* System loader based on example implementation as of 2013-12-03 | ||
* | ||
* Copyright (c) 2013 Guy Bedford, Luke Hoban, Addy Osmani | ||
* Licensed under the MIT license. | ||
* | ||
*/ | ||
/* | ||
ToDo | ||
- Traceur ModuleTransformer update for new system | ||
- getImports to use visitor pattern | ||
- Loader Iterator support | ||
- System ondemand functionality | ||
- Tracking these and spec issues with 'NB' comments in this code | ||
*/ | ||
(function () { | ||
(function() { | ||
var isBrowser = typeof window != 'undefined'; | ||
var global = isBrowser ? window : exports; | ||
var global = isBrowser ? window : this; | ||
var exports = isBrowser ? window : module.exports; | ||
var defineProperty = function(obj, prop, opt) { | ||
var nextTick = isBrowser ? function(fn) { setTimeout(fn, 1); } : process.nextTick; | ||
/* | ||
********************************************************************************************* | ||
Simple Promises A+ Implementation | ||
Adapted from https://github.com/RubenVerborgh/promiscuous | ||
Copyright 2013 Ruben Verborgh | ||
********************************************************************************************* | ||
*/ | ||
var Promise = (function(nextTick) { | ||
function createDeferred() { | ||
// The `handler` variable points to the function that will | ||
// 1) handle a .then(onFulfilled, onRejected) call | ||
// 2) handle a .resolve or .reject call (if not fulfilled) | ||
// Before 2), `handler` holds a queue of callbacks. | ||
// After 2), `handler` is a simple .then handler. | ||
// We use only one function to save memory and complexity. | ||
var handler = function handlerFunction(onFulfilled, onRejected, value) { | ||
// Case 1) handle a .then(onFulfilled, onRejected) call | ||
if (onFulfilled !== Promise) { | ||
var d = createDeferred(); | ||
handlerFunction.c.push({ d: d, resolve: onFulfilled, reject: onRejected }); | ||
return d.promise; | ||
} | ||
// Case 2) handle a .resolve or .reject call | ||
// (`onFulfilled` acts as a sentinel) | ||
// The actual function signature is | ||
// .re[ject|solve](sentinel, success, value) | ||
// Check if the value is a promise and try to obtain its `then` method | ||
var then; | ||
if (value !== null && (typeof value === 'object' || typeof value === 'function')) { | ||
try { then = value.then; } | ||
catch (reason) { onRejected = false; value = reason; } | ||
} | ||
// If the value is a promise, take over its state | ||
if (typeof then === 'function') { | ||
// Make a local copy of the _current_ handler | ||
onFulfilled = handler; | ||
try { | ||
then.call(this, function (value) { | ||
then && (then = null, onFulfilled(Promise, true, value)); | ||
}, | ||
function (reason) { | ||
then && (then = null, onFulfilled(Promise, false, reason)); | ||
}); | ||
} | ||
catch (reason) { | ||
then && (then = null, onFulfilled(Promise, false, reason)); | ||
} | ||
} | ||
// The value is not a promise; handle resolve/reject | ||
else { | ||
var action = onRejected ? 'resolve' : 'reject', queue = handlerFunction.c; | ||
for (var i = 0, l = queue.length; i < l; i++) { | ||
var c = queue[i], deferred = c.d, callback = c[action]; | ||
// If no callback, just fulfill the promise | ||
if (typeof callback !== 'function') | ||
deferred[action](value); | ||
// Otherwise, fulfill the promise with the result of the callback | ||
else | ||
execute(callback, value, deferred); | ||
} | ||
// Replace this handler with a simple resolved or rejected handler | ||
handler = createHandler(promise, value, onRejected); | ||
} | ||
}, | ||
promise = { | ||
then: function (onFulfilled, onRejected) { | ||
return handler(onFulfilled, onRejected); | ||
} | ||
}; | ||
// The queue of deferreds | ||
handler.c = []; | ||
return { | ||
promise: promise, | ||
// Only resolve / reject when there is a deferreds queue | ||
resolve: function (value) { handler.c && handler(Promise, true, value); }, | ||
reject : function (reason) { handler.c && handler(Promise, false, reason); } | ||
}; | ||
} | ||
// Creates a fulfilled or rejected .then function | ||
function createHandler(promise, value, success) { | ||
return function (onFulfilled, onRejected) { | ||
var callback = success ? onFulfilled : onRejected, result; | ||
if (typeof callback !== 'function') | ||
return promise; | ||
execute(callback, value, result = createDeferred()); | ||
return result.promise; | ||
}; | ||
} | ||
// Executes the callback with the specified value, | ||
// resolving or rejecting the deferred | ||
function execute(callback, value, deferred) { | ||
nextTick(function () { | ||
try { | ||
// Return the result if it's not a promise | ||
var result = callback(value), | ||
then = (result !== null && (typeof result === 'object' || typeof result === 'function')) && result.then; | ||
if (typeof then !== 'function') | ||
deferred.resolve(result); | ||
// If it's a promise, make sure it's not circular | ||
else if (result === deferred.promise) | ||
deferred.reject(new TypeError()); | ||
// Take over the promise's state | ||
else | ||
then.call(result, deferred.resolve, deferred.reject); | ||
} | ||
catch (error) { | ||
deferred.reject(error); | ||
} | ||
}); | ||
} | ||
function Promise(fn) { | ||
var defer = createDeferred(); | ||
try { | ||
fn(defer.resolve, defer.reject); | ||
} | ||
catch(e) { | ||
defer.reject(e); | ||
} | ||
return defer.promise; | ||
} | ||
// Returns a resolved promise | ||
Promise.resolve = function(value) { | ||
var promise = {}; | ||
promise.then = createHandler(promise, value, true); | ||
return promise; | ||
} | ||
// Returns a rejected promise | ||
Promise.reject = function(reason) { | ||
var promise = {}; | ||
promise.then = createHandler(promise, reason, false); | ||
return promise; | ||
} | ||
// Returns a deferred | ||
Promise.deferred = createDeferred; | ||
Promise.all = function(promises) { | ||
var defer = createDeferred(); | ||
if (!promises.length) | ||
nextTick(defer.resolve); | ||
var outputs = []; | ||
var resolved = 0; | ||
var rejected = false; | ||
for (var i = 0, l = promises.length; i < l; i++) (function(i) { | ||
promises[i].then(function(resolvedVal) { | ||
outputs[i] = resolvedVal; | ||
resolved++; | ||
if (resolved == promises.length) | ||
defer.resolve(outputs); | ||
}, rejected); | ||
})(i); | ||
function rejected(exception) { | ||
if (!rejected) { | ||
rejected = true; | ||
defer.reject(exception); | ||
} | ||
} | ||
return defer.promise; | ||
} | ||
return Promise; | ||
})(nextTick); | ||
/* | ||
********************************************************************************************* | ||
Loader Polyfill | ||
- Implemented exactly to the 2013-12-02 Specification Draft - | ||
https://github.com/jorendorff/js-loaders/blob/e60d3651/specs/es6-modules-2013-12-02.pdf | ||
with the only exceptions as described here | ||
- Abstract functions have been combined where possible, and their associated functions | ||
commented | ||
- Declarative Module Support is entirely disabled, and an error will be thrown if | ||
the instantiate loader hook returns undefined | ||
- With this assumption, instead of Link, LinkDynamicModules is run directly | ||
- ES6 support is thus provided through the translate function of the System loader | ||
- EnsureEvaluated is removed, but may in future implement dynamic execution pending | ||
issue - https://github.com/jorendorff/js-loaders/issues/63 | ||
- Realm implementation is entirely omitted. As such, Loader.global and Loader.realm | ||
accessors will throw errors, as well as Loader.eval | ||
- Loader module table iteration currently not yet implemented | ||
********************************************************************************************* | ||
*/ | ||
// Some Helpers | ||
// logs a linkset snapshot for debugging | ||
/* function snapshot(loader) { | ||
console.log('\n'); | ||
for (var i = 0; i < loader._loads.length; i++) { | ||
var load = loader._loads[i]; | ||
var linkSetLog = load.name + ' (' + load.status + '): '; | ||
for (var j = 0; j < load.linkSets.length; j++) { | ||
linkSetLog += '{' | ||
linkSetLog += logloads(load.linkSets[j].loads); | ||
linkSetLog += '} '; | ||
} | ||
console.log(linkSetLog); | ||
} | ||
console.log('\n'); | ||
} | ||
function logloads(loads) { | ||
var log = ''; | ||
for (var k = 0; k < loads.length; k++) | ||
log += loads[k].name + (k != loads.length - 1 ? ' ' : ''); | ||
return log; | ||
} */ | ||
function assert(name, expression) { | ||
if (!expression) | ||
console.log('Assertion Failed - ' + name); | ||
} | ||
function defineProperty(obj, prop, opt) { | ||
if (Object.defineProperty) | ||
@@ -21,4 +265,9 @@ Object.defineProperty(obj, prop, opt); | ||
}; | ||
function preventExtensions(obj) { | ||
if (Object.preventExtensions) | ||
Object.preventExtensions(obj); | ||
} | ||
var indexOf = Array.prototype.indexOf || function (item) { // Define an IE-friendly shim good-enough for purposes | ||
// Define an IE-friendly shim good-enough for purposes | ||
var indexOf = Array.prototype.indexOf || function (item) { | ||
for (var i = 0, thisLen = this.length; i < thisLen; i++) { | ||
@@ -32,362 +281,474 @@ if (this[i] === item) { | ||
// 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) { | ||
// Load Abstract Functions | ||
// Initialization of loader state from options | ||
function createLoad(name) { | ||
return { | ||
status: 'loading', | ||
name: name, | ||
metadata: {}, | ||
linkSets: [] | ||
}; | ||
} | ||
this.global = options.global || window; | ||
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; | ||
// promise for a load record, can be in registry, already loading, or not | ||
function requestLoad(loader, request, refererName, refererAddress) { | ||
return Promise(function(resolve) { | ||
// CallNormalize | ||
resolve(loader.normalize(request, refererName, refererAddress)); | ||
}) | ||
// The internal table of module instance objects | ||
this._mios = {}; | ||
// GetOrCreateLoad | ||
.then(function(name) { | ||
var load; | ||
if (loader._modules[name]) { | ||
load = createLoad(name); | ||
load.status = 'linked'; | ||
return load; | ||
} | ||
// 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 = {}; | ||
for (var i = 0, l = loader._loads.length; i < l; i++) { | ||
load = loader._loads[i]; | ||
if (load.name == name) { | ||
assert('loading or loaded', load.status == 'loading' || load.status == 'loaded'); | ||
return load; | ||
} | ||
} | ||
// CreateLoad | ||
load = createLoad(name); | ||
loader._loads.push(load); | ||
proceedToLocate(loader, load); | ||
return load; | ||
}); | ||
} | ||
function proceedToLocate(loader, load) { | ||
proceedToFetch(loader, load, | ||
Promise.resolve() | ||
// CallLocate | ||
.then(function() { | ||
return loader.locate({ name: load.name, metadata: load.metadata }); | ||
}) | ||
); | ||
} | ||
function proceedToFetch(loader, load, p) { | ||
proceedToTranslate(loader, load, | ||
p | ||
// CallFetch | ||
.then(function(address) { | ||
if (load.linkSets.length == 0) | ||
return undefined; | ||
load.address = address; | ||
return loader.fetch({ name: load.name, metadata: load.metadata, address: address }); | ||
}) | ||
); | ||
} | ||
function proceedToTranslate(loader, load, p) { | ||
p | ||
// CallTranslate | ||
.then(function(source) { | ||
if (load.linkSets.length == 0) | ||
return undefined; | ||
// 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; | ||
} | ||
return loader.translate({ name: load.name, metadata: load.metadata, address: load.address, source: source }) | ||
}) | ||
if (this._sloaded[url]) { | ||
callback && callback(); | ||
return; | ||
} | ||
// CallInstantiate | ||
.then(function(source) { | ||
if (load.linkSets.length == 0) | ||
return undefined; | ||
// 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; | ||
} | ||
load.source = source; | ||
return loader.instantiate({ name: load.name, metadata: load.metadata, address: load.address, source: source }); | ||
}) | ||
// InstantiateSucceeded | ||
.then(function(instantiateResult) { | ||
if (load.linkSets.length == 0) | ||
return undefined; | ||
var depsList; | ||
if (instantiateResult === undefined) | ||
throw 'Declarative parsing is not implemented by the polyfill.'; | ||
else if (typeof instantiateResult == 'object') { | ||
depsList = instantiateResult.deps || []; | ||
load.execute = instantiateResult.execute; | ||
load.kind = 'dynamic'; | ||
} | ||
delete self._sloads[url]; | ||
// if any didn't have an error handler, throw | ||
if (!allCalled) | ||
throw err; | ||
} | ||
else | ||
throw TypeError('Invalid instantiate return value'); | ||
this.fetch(url, function (source) { | ||
var opt = { | ||
address: url, | ||
type: 'script' | ||
}; | ||
source = self.translate(source, opt); | ||
// ProcessLoadDependencies | ||
load.dependencies = {}; | ||
var loadPromises = []; | ||
for (var i = 0, l = depsList.length; i < l; i++) (function(request) { | ||
var p = requestLoad(loader, request, load.name, load.address); | ||
self._linkExecute(url, source, opt, _callback, _errback, true); | ||
}, _errback); | ||
}; | ||
// AddDependencyLoad (load is parentLoad) | ||
p.then(function(depLoad) { | ||
assert('not already a dependency', !load.dependencies[request]); | ||
load.dependencies[request] = depLoad.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) { | ||
Loader.prototype['import'].call(self, name[i], function(m) { | ||
modules[i] = m; | ||
moduleCnt++; | ||
if (moduleCnt == name.length) { | ||
callback && callback.apply(null, modules); | ||
if (depLoad.status != 'linked') { | ||
var linkSets = load.linkSets.concat([]); | ||
for (var i = 0, l = linkSets.length; i < l; i++) | ||
addLoadToLinkSet(linkSets[i], depLoad); | ||
} | ||
}, errback, referer); | ||
})(i); | ||
return; | ||
} | ||
}); | ||
name = this.normalize(name, referer); | ||
loadPromises.push(p); | ||
})(depsList[i]); | ||
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; | ||
} | ||
return Promise.all(loadPromises) | ||
if (this._mios[name]) { | ||
return callback && callback(this._mios[name]); | ||
} | ||
// LoadSucceeded | ||
.then(function() { | ||
assert('is loading', load.status == 'loading'); | ||
// store the callbacks in a load queue for multiple requests | ||
if (this._mloads[name]) { | ||
this._mloads[name].push({ | ||
callback: callback, | ||
errback: errback | ||
load.status = 'loaded'; | ||
// console.log('load succeeeded ' + load.name); | ||
// snapshot(loader); | ||
var linkSets = load.linkSets.concat([]); | ||
for (var i = 0, l = linkSets.length; i < l; i++) | ||
updateLinkSetOnLoad(linkSets[i], load); | ||
}); | ||
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); | ||
} | ||
else { | ||
allCalled = false; | ||
} | ||
} | ||
delete self._mloads[name]; | ||
// if any didn't have an error handler, throw | ||
if (!allCalled) | ||
throw err; | ||
} | ||
// LoadFailed | ||
, function(exc) { | ||
assert('is loading on fail', load.status == 'loading'); | ||
load.status = 'failed'; | ||
load.exception = exc; | ||
for (var i = 0, l = load.linkSets.length; i < l; i++) | ||
linkSetFailed(load.linkSets[i], exc); | ||
assert('fail linkSets removed', load.linkSets.length == 0); | ||
}); | ||
} | ||
var url = this.resolve(name, opt); | ||
if (typeof url != 'string') { | ||
url = url.address; | ||
// NB what to do with 'extra'? | ||
} | ||
// LinkSet Abstract Functions | ||
function createLinkSet(loader, startingLoad) { | ||
var deferred = Promise.deferred(); | ||
var linkSet = { | ||
loader: loader, | ||
loads: [], | ||
done: deferred.promise, | ||
resolve: deferred.resolve, | ||
reject: deferred.reject, | ||
loadingCount: 0 | ||
}; | ||
addLoadToLinkSet(linkSet, startingLoad); | ||
return linkSet; | ||
} | ||
function addLoadToLinkSet(linkSet, load) { | ||
assert('loading or loaded on link set', load.status == 'loading' || load.status == 'loaded'); | ||
opt.normalized = name; | ||
for (var i = 0, l = linkSet.loads.length; i < l; i++) | ||
if (linkSet.loads[i] == load) | ||
return; | ||
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); | ||
}; | ||
linkSet.loads.push(load); | ||
load.linkSets.push(linkSet); | ||
// Loader.prototype.fetch | ||
// NB spec issue here - this clashes with the instance fetch function!? | ||
if (load.status != 'loaded') | ||
linkSet.loadingCount++; | ||
// _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) { | ||
var isScript = opt.type == 'script'; | ||
var loader = linkSet.loader; | ||
var link = this.link(source, opt); | ||
for (var dep in load.dependencies) { | ||
var name = load.dependencies[dep]; | ||
// 1. module | ||
if (link instanceof Module && !isScript) { | ||
return callback(link); | ||
if (loader._modules[name]) | ||
continue; | ||
for (var i = 0, l = loader._loads.length; i < l; i++) | ||
if (loader._loads[i].name == name) { | ||
addLoadToLinkSet(linkSet, loader._loads[i]); | ||
break; | ||
} | ||
} | ||
// console.log('add to linkset ' + load.name); | ||
// snapshot(linkSet.loader); | ||
} | ||
function updateLinkSetOnLoad(linkSet, load) { | ||
// NB https://github.com/jorendorff/js-loaders/issues/85 | ||
// assert('no load when updated ' + load.name, indexOf.call(linkSet.loads, load) != -1); | ||
assert('loaded or linked', load.status == 'loaded' || load.status == 'linked'); | ||
// preload esprima if necessary | ||
var self = this; | ||
var linkSpecified = typeof link == 'object' && !isScript; | ||
(!linkSpecified ? ES6Parser.loadTraceur : function(name, source, callback) { callback(); }).call(ES6Parser, name, source, function() { | ||
var imports, execute; | ||
// 2. specified imports and execute | ||
if (linkSpecified) { | ||
imports = link.imports; | ||
execute = link.execute; | ||
} | ||
// 3. undefined -> default | ||
else { | ||
var defaultLink = self._link(source, opt); | ||
imports = defaultLink.imports; | ||
execute = defaultLink.execute; | ||
} | ||
// console.log('update linkset on load ' + load.name); | ||
// snapshot(linkSet.loader); | ||
// stops an unnecessary load cascade | ||
if (errback.called) | ||
// see https://github.com/jorendorff/js-loaders/issues/80 | ||
linkSet.loadingCount--; | ||
/* for (var i = 0; i < linkSet.loads.length; i++) { | ||
if (linkSet.loads[i].status == 'loading') { | ||
return; | ||
} | ||
} */ | ||
if (linkSet.loadingCount > 0) | ||
return; | ||
if (!imports.length) | ||
return callback(execute.call(self)); | ||
var startingLoad = linkSet.loads[0]; | ||
try { | ||
link(linkSet.loads, linkSet.loader); | ||
} | ||
catch(exc) { | ||
return linkSetFailed(linkSet, exc); | ||
} | ||
opt.normalizeMap = {}; | ||
assert('loads cleared', linkSet.loads.length == 0); | ||
linkSet.resolve(startingLoad); | ||
} | ||
function linkSetFailed(linkSet, exc) { | ||
var loads = linkSet.loads.concat([]); | ||
for (var i = 0, l = loads.length; i < l; i++) { | ||
var load = loads[i]; | ||
var linkIndex = indexOf.call(load.linkSets, linkSet); | ||
assert('link not present', linkIndex != -1); | ||
load.linkSets.splice(linkIndex, 1); | ||
if (load.linkSets.length == 0) { | ||
var globalLoadsIndex = indexOf.call(linkSet.loader._loads, load); | ||
if (globalLoadsIndex != -1) | ||
linkSet.loader._loads.splice(globalLoadsIndex, 1); | ||
} | ||
} | ||
linkSet.reject(exc); | ||
} | ||
function finishLoad(loader, load) { | ||
// if not anonymous, add to the module table | ||
if (load.name) { | ||
assert('load not in module table', !loader._modules[load.name]); | ||
loader._modules[load.name] = load.module; | ||
} | ||
var loadIndex = indexOf.call(loader._loads, load); | ||
if (loadIndex != -1) | ||
loader._loads.splice(loadIndex, 1); | ||
for (var i = 0, l = load.linkSets.length; i < l; i++) { | ||
loadIndex = indexOf.call(load.linkSets[i].loads, load); | ||
load.linkSets[i].loads.splice(loadIndex, 1); | ||
} | ||
load.linkSets = []; | ||
} | ||
function loadModule(loader, name, options) { | ||
return Promise(asyncStartLoadPartwayThrough(loader, name, options && options.address ? 'fetch' : 'locate', undefined, options && options.address, undefined)).then(function(load) { | ||
return load; | ||
}); | ||
} | ||
function asyncStartLoadPartwayThrough(loader, name, step, meta, address, source) { | ||
return function(resolve, reject) { | ||
if (loader._modules[name]) | ||
throw new TypeError('Module "' + name + '" already exists in the module table'); | ||
for (var i = 0, l = loader._loads.length; i < l; i++) | ||
if (loader._loads[i].name == name) | ||
throw new TypeError('Module "' + name + '" is already loading'); | ||
var deps = []; | ||
var depCnt = 0; | ||
for (var i = 0; i < imports.length; i++) (function(i) { | ||
var referer = { name: name, address: opt.address }; | ||
var load = createLoad(name); | ||
// run the normalization to get the canonical module name | ||
// to allow imports to be loaded | ||
var normalized = self.normalize(imports[i], referer); | ||
if (meta) | ||
load.metadata = meta; | ||
if (typeof normalized == 'object') | ||
normalized = normalized.normalized; | ||
var linkSet = createLinkSet(loader, load); | ||
opt.normalizeMap[imports[i]] = normalized; | ||
loader._loads.push(load); | ||
Loader.prototype['import'].call(self, imports[i], function (module) { | ||
depCnt++; | ||
deps[i] = module; | ||
if (depCnt == imports.length) { | ||
var output = execute.apply(self, deps); | ||
callback(output); | ||
} | ||
}, errback, referer); | ||
})(i); | ||
// NB spec change as in https://github.com/jorendorff/js-loaders/issues/79 | ||
linkSet.done.then(resolve, reject); | ||
}, errback); | ||
}; | ||
if (step == 'locate') | ||
proceedToLocate(loader, load); | ||
Loader.prototype._link = function(source, opt) { | ||
if (!isBrowser) | ||
ES6Parser.loadTraceur(opt.normalized, source, function(){}, function(){}); | ||
var self = this; | ||
return { | ||
imports: ES6Parser.parseImports(source, opt), | ||
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 || {}); | ||
else if (step == 'fetch') | ||
proceedToFetch(loader, load, Promise.resolve(address)); | ||
else { | ||
assert('translate step', step == 'translate'); | ||
load.address = address; | ||
proceedToTranslate(loader, load, Promise.resolve(source)); | ||
} | ||
}; | ||
} | ||
} | ||
function evaluateLoadedModule(loader, load) { | ||
assert('is linked ' + load.name, load.status == 'linked'); | ||
assert('is a module', load.module instanceof Module); | ||
// 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 | ||
}); | ||
}; | ||
// ensureEvaluated(load.module, [], loader); | ||
// 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 | ||
// when no name is given, | ||
// provide a unique name to cache the syntax tree parsing | ||
var name = '__eval' + evalCnt++; | ||
ES6Parser.parseNames[name] = true; | ||
return load.module; | ||
} | ||
// Module Object | ||
function Module(obj) { | ||
if (typeof obj != 'object') | ||
throw new TypeError('Expected object'); | ||
var self = this; | ||
ES6Parser.loadTraceur(name, source, function() { | ||
self._linkExecute(null, source, { type: 'script', address: name, normalized: name }, callback || function() {}, errback || function() {}); | ||
}, errback); | ||
for (var key in obj) { | ||
(function (key, value) { | ||
defineProperty(self, key, { | ||
configurable: false, | ||
enumerable: true, | ||
get: function () { | ||
return value; | ||
} | ||
}); | ||
})(key, obj[key]); | ||
} | ||
preventExtensions(self); | ||
} | ||
// Module.prototype = null; | ||
// 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; | ||
}; | ||
// Linking | ||
// Link is directly LinkDynamicModules assuming all modules are dynamic | ||
function link(loads, loader) { | ||
// console.log('linking {' + logloads(loads) + '}'); | ||
// 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); | ||
}; | ||
// continue until all linked | ||
// NB circular dependencies will stall this loop | ||
var loopCnt = 0; | ||
while (loads.length) { | ||
loopCnt++; | ||
// search through to find a load with all its dependencies linked | ||
search: for (var i = 0; i < loads.length; i++) { | ||
var load = loads[i]; | ||
var depModules = []; | ||
for (var d in load.dependencies) { | ||
var dep = load.dependencies[d]; | ||
// being in the module table means it is linked | ||
var depModule = loader._modules[dep]; | ||
if (!depModule) | ||
continue search; | ||
depModules.push(depModule); | ||
} | ||
Loader.prototype.has = function (name) { | ||
return !!this._mios[name]; | ||
}; | ||
// all dependencies linked now, so we can execute | ||
var module = load.execute.apply(null, depModules); | ||
if (!(module instanceof Module)) | ||
throw new TypeError('Execution must define a Module instance'); | ||
load.module = module; | ||
load.status = 'linked'; | ||
finishLoad(loader, load); | ||
} | ||
if (loopCnt === 1000) { | ||
console.log('Circular Dependency Detected'); | ||
return; | ||
} | ||
} | ||
// console.log('linked'); | ||
} | ||
Loader.prototype['delete'] = function (name) { | ||
delete this._mios[name]; | ||
}; | ||
// Loader | ||
function Loader(options) { | ||
if (typeof options != 'object') | ||
throw new TypeError('Options must be an object'); | ||
// 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.global[p] = o[p]; | ||
if (options.normalize) | ||
this.normalize = options.normalize; | ||
if (options.locate) | ||
this.locate = options.locate; | ||
if (options.fetch) | ||
this.fetch = options.fetch; | ||
if (options.translate) | ||
this.translate = options.translate; | ||
if (options.instantiate) | ||
this.instantiate = options.instantiate; | ||
defineProperty(this, 'global', { | ||
get: function() { | ||
throw new TypeError('global accessor not provided by polyfill'); | ||
} | ||
}); | ||
defineProperty(this, 'realm', { | ||
get: function() { | ||
throw new TypeError('Realms not implemented in polyfill'); | ||
} | ||
}); | ||
this._modules = {}; | ||
this._loads = []; | ||
} | ||
// NB importPromises hacks ability to import a module twice without error - https://github.com/jorendorff/js-loaders/issues/60 | ||
var importPromises = {}; | ||
Loader.prototype = { | ||
define: function(name, source, options) { | ||
if (importPromises[name]) | ||
throw new TypeError('Module is already loading.'); | ||
importPromises[name] = Promise(asyncStartLoadPartwayThrough(this, name, 'translate', options && options.meta || {}, options && options.address, source)); | ||
return importPromises[name].then(function() { delete importPromises[name]; }); | ||
}, | ||
load: function(request, options) { | ||
if (importPromises[name]) | ||
return importPromises[name]; | ||
importPromises[name] = loadModule(this, request, options); | ||
return importPromises[name].then(function() { delete importPromises[name]; }) | ||
}, | ||
module: function(source, options) { | ||
var load = createLoad(); | ||
load.address = options && options.address; | ||
var linkSet = createLinkSet(this, load); | ||
var sourcePromise = Promise.resolve(source); | ||
var p = linkSet.done.then(function() { | ||
evaluateLoadedModule(this, load); | ||
}); | ||
proceedToTranslate(this, load, sourcePromise); | ||
return p; | ||
}, | ||
import: function(name, options) { | ||
if (this._modules[name]) | ||
return Promise.resolve(this._modules[name]); | ||
return (importPromises[name] || (importPromises[name] = loadModule(this, name, options))) | ||
.then(function(load) { | ||
delete importPromises[name]; | ||
return evaluateLoadedModule(this, load); | ||
}); | ||
}, | ||
eval: function(source) { | ||
throw new TypeError('Eval not implemented in polyfill') | ||
}, | ||
get: function(key) { | ||
return this._modules[key]; | ||
}, | ||
has: function(name) { | ||
return !!this._modules[name]; | ||
}, | ||
set: function(name, module) { | ||
if (!(module instanceof Module)) | ||
throw new TypeError('Set must be a module'); | ||
this._modules[name] = module; | ||
}, | ||
delete: function(name) { | ||
return this._modules[name] ? delete this._modules[name] : false; | ||
}, | ||
// NB implement iterations | ||
entries: function() { | ||
throw new TypeError('Iteration not yet implemented in the polyfill'); | ||
}, | ||
keys: function() { | ||
throw new TypeError('Iteration not yet implemented in the polyfill'); | ||
}, | ||
values: function() { | ||
throw new TypeError('Iteration not yet implemented in the polyfill'); | ||
}, | ||
normalize: function(name, refererName, refererAddress) { | ||
return name; | ||
}, | ||
locate: function(load) { | ||
return load.name; | ||
}, | ||
fetch: function(load) { | ||
throw new TypeError('Fetch not implemented'); | ||
}, | ||
translate: function(load) { | ||
return load.source; | ||
}, | ||
instantiate: function(load) { | ||
} | ||
@@ -397,33 +758,74 @@ }; | ||
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) { | ||
defineProperty(self, key, { | ||
configurable: false, | ||
enumerable: true, | ||
get: function () { | ||
return o[key]; | ||
} | ||
}); | ||
})(key); | ||
} | ||
} | ||
}; | ||
System Loader Implementation | ||
- Implemented to https://github.com/jorendorff/js-loaders/blob/master/browser-loader.js, | ||
except for Instantiate function | ||
// Pre-configured Loader instance for easier use | ||
var absUrlRegEx = /^\/|([^\:\/]*:\/\/)/; | ||
var isAbsoluteUrl = function(name) { | ||
return name.match(absUrlRegEx); | ||
- Instantiate function determines if ES6 module syntax is being used, if so parses with | ||
Traceur and returns a dynamic InstantiateResult for loading ES6 module syntax in ES5. | ||
- Custom loaders thus can be implemented by using this System.instantiate function as | ||
the fallback loading scenario, after other module format detections. | ||
- Traceur is loaded dynamically when module syntax is detected by a regex (with over- | ||
classification), either from require('traceur') on the server, or the | ||
'data-traceur-src' property on the current script in the browser, or if not set, | ||
'traceur.js' in the same URL path as the current script in the browser. | ||
- ondemand / paths functionality currently not yet implemented | ||
- <script type="module"> supported, but <module> tag not | ||
********************************************************************************************* | ||
*/ | ||
// Helpers | ||
// Absolute URL parsing, from https://gist.github.com/Yaffle/1088850 | ||
function parseURI(url) { | ||
var m = String(url).replace(/^\s+|\s+$/g, '').match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/); | ||
// authority = '//' + user + ':' + pass '@' + hostname + ':' port | ||
return (m ? { | ||
href : m[0] || '', | ||
protocol : m[1] || '', | ||
authority: m[2] || '', | ||
host : m[3] || '', | ||
hostname : m[4] || '', | ||
port : m[5] || '', | ||
pathname : m[6] || '', | ||
search : m[7] || '', | ||
hash : m[8] || '' | ||
} : null); | ||
} | ||
var fetch; | ||
function toAbsoluteURL(base, href) { | ||
function removeDotSegments(input) { | ||
var output = []; | ||
input.replace(/^(\.\.?(\/|$))+/, '') | ||
.replace(/\/(\.(\/|$))+/g, '/') | ||
.replace(/\/\.\.$/, '/../') | ||
.replace(/\/?[^\/]*/g, function (p) { | ||
if (p === '/..') | ||
output.pop(); | ||
else | ||
output.push(p); | ||
}); | ||
return output.join('').replace(/^\//, input.charAt(0) === '/' ? '/' : ''); | ||
} | ||
href = parseURI(href || ''); | ||
base = parseURI(base || ''); | ||
return !href || !base ? null : (href.protocol || base.protocol) + | ||
(href.protocol || href.authority ? href.authority : base.authority) + | ||
removeDotSegments(href.protocol || href.authority || href.pathname.charAt(0) === '/' ? href.pathname : (href.pathname ? ((base.authority && !base.pathname ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + href.pathname) : base.pathname)) + | ||
(href.protocol || href.authority || href.pathname ? href.search : (href.search || base.search)) + | ||
href.hash; | ||
} | ||
var fetchTextFromURL; | ||
if (isBrowser) { | ||
fetch = function(url, fulfill, reject) { | ||
fetchTextFromURL = function(url, fulfill, reject) { | ||
var xhr = new XMLHttpRequest(); | ||
@@ -458,3 +860,3 @@ if (!('withCredentials' in xhr)) { | ||
var fs = require('fs'); | ||
fetch = function(url, fulfill, reject) { | ||
fetchTextFromURL = function(url, fulfill, reject) { | ||
return fs.readFile(url, function(err, data) { | ||
@@ -469,420 +871,431 @@ if (err) | ||
// '../a/b', '../c/d/e' -> '../c/a/b' | ||
var resolvePath = function(name, parentName) { | ||
if (!parentName) | ||
return name; | ||
var System = new Loader({ | ||
global: isBrowser ? window : global, | ||
strict: true, | ||
normalize: function(name, parentName, parentAddress) { | ||
if (typeof name != 'string') | ||
throw new TypeError('Module name must be a string'); | ||
// all resolutions in this function are relative | ||
if (name.substr(0, 2) == './') | ||
name = name.substr(2); | ||
var segments = name.split('/'); | ||
// anything after the last slash is assumed a file name | ||
var lastSlash = parentName.lastIndexOf('/'); | ||
if (lastSlash == -1) | ||
return name; | ||
if (lastSlash != parentName.length - 1) | ||
parentName = parentName.substr(0, lastSlash + 1); | ||
if (segments.length == 0) | ||
throw new TypeError('No module name provided'); | ||
// simple additive resolution (most cases) | ||
if (name.substr(0, 1) != '.') | ||
return parentName + name; | ||
// current segment | ||
var i = 0; | ||
// is the module name relative | ||
var rel = false; | ||
// number of backtracking segments | ||
var dotdots = 0; | ||
if (segments[0] == '.') { | ||
i++; | ||
if (i == segments.length) | ||
throw new TypeError('Illegal module name "' + name + '"'); | ||
rel = true; | ||
} | ||
else { | ||
while (segments[i] == '..') { | ||
i++; | ||
if (i == segments.length) | ||
throw new TypeError('Illegal module name "' + name + '"'); | ||
} | ||
if (i) | ||
rel = true; | ||
dotdots = i; | ||
} | ||
// begin backtracking | ||
var parentParts = parentName.split('/'); | ||
var nameParts = name.split('/'); | ||
for (var j = i; j < segments.length; j++) { | ||
var segment = segments[j]; | ||
if (segment == '' || segment == '.' || segment == '..') | ||
throw new TypeError('Illegal module name"' + name + '"'); | ||
} | ||
parentParts.pop(); | ||
if (!rel) | ||
return name; | ||
var curPart; | ||
while (nameParts[0] == '..') { | ||
curPart = nameParts.shift(); | ||
if (!parentParts.length || parentParts[parentParts.length - 1] == '..') | ||
parentParts.push('..'); | ||
else | ||
parentParts.pop(); | ||
} | ||
// build the full module name | ||
var normalizedParts = []; | ||
var parentParts = (parentName || '').split('/'); | ||
var normalizedLen = parentParts.length - 1 - dotdots; | ||
return parentParts.join('/') + (parentParts.length ? '/' : '') + nameParts.join('/'); | ||
} | ||
normalizedParts = normalizedParts.concat(parentParts.splice(0, parentParts.length - 1 - dotdots)); | ||
normalizedParts = normalizedParts.concat(segments.splice(i)); | ||
var defaultSystemLoader = new Loader({ | ||
global: isBrowser ? window : global, | ||
strict: true, | ||
normalize: function(name, referer) { | ||
if (isAbsoluteUrl(name)) | ||
return name; | ||
if (name.substr(0, 1) == '.') | ||
return resolvePath(name, referer && referer.name); | ||
else | ||
return name; | ||
return normalizedParts.join('/'); | ||
}, | ||
resolve: function (name, options) { | ||
for (var r in this.ondemandTable) { | ||
if (indexOf.call(this.ondemandTable[r], name) != -1) { | ||
return r; | ||
} | ||
} | ||
if (isAbsoluteUrl(name)) | ||
return name; | ||
return resolvePath(name + '.js', this.baseURL + (this.baseURL.charAt(this.baseURL.length - 1) != '/' ? '/' : '')); | ||
locate: function(load) { | ||
// NB Implement System.ondemand here | ||
return toAbsoluteURL(this.baseURL, escape(load.name + '.js')); | ||
}, | ||
fetch: fetch, | ||
translate: function (source, options) { | ||
return source; | ||
fetch: function(load) { | ||
var defer = Promise.deferred(); | ||
fetchTextFromURL(toAbsoluteURL(this.baseURL, load.address), defer.resolve, defer.reject); | ||
return defer.promise; | ||
}, | ||
link: function (source, options) {} | ||
}); | ||
instantiate: function(load) { | ||
// normal eval (non-module code) | ||
// note that anonymous modules (load.name == undefined) are always | ||
// anonymous <module> tags, so we use Traceur for these | ||
if (load.metadata.es6 === false || (load.name && !load.source.match(es6RegEx))) | ||
return { | ||
deps: [], | ||
execute: function() { | ||
__scopedEval(load.source, global, load.address); | ||
defaultSystemLoader.baseURL = isBrowser ? window.location.href.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]); | ||
} | ||
} | ||
// when loading traceur, it overwrites the System | ||
// global. The only way to synchronously ensure it is | ||
// reverted in time not to cause issue is here | ||
if (load.name == 'traceur' && isBrowser) { | ||
global.traceur = global.System.get('../src/traceur.js'); | ||
global.System = System; | ||
} | ||
// return an empty module | ||
return new Module({}); | ||
} | ||
}; | ||
// ES6 -> ES5 conversion | ||
// Syntax Parsing and Source Modifying Polyfills | ||
// Traceur-based parser for module syntax, with pluggable polyfill support | ||
var traceurSrc; | ||
var ES6Parser = { | ||
// iterate the entire syntax tree node object with the given iterator function | ||
traverse: function(object, iterator, parent, parentProperty) { | ||
var key, child; | ||
if (iterator(object, parent, parentProperty) === false) | ||
return; | ||
for (key in object) { | ||
if (!object.hasOwnProperty(key)) | ||
continue; | ||
if (key == 'location' || key == 'type') | ||
continue; | ||
child = object[key]; | ||
if (typeof child == 'object' && child !== null) | ||
this.traverse(child, iterator, object, key); | ||
} | ||
}, | ||
// 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 Traceur for deeper analysis | ||
es6RegEx: /(?:^\s*|[}{\(\);,\n]\s*)((import|module)\s+[^"']+\s+from\s+['"]|export\s+(\*|\{|default|function|var|const|let|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*))/, | ||
checkModuleSyntax: function(name, source) { | ||
if (name == null || this.parseNames[name] === undefined) | ||
this.parseNames[name] = source && !!source.match(this.es6RegEx); | ||
return this.parseNames[name]; | ||
}, | ||
loadTraceur: function(name, source, callback, errback) { | ||
if (this.traceur) | ||
return callback(); | ||
var loader = this; | ||
load.address = load.address || 'anonymous-module-' + anonCnt++; | ||
// load traceur and the module transformer | ||
return getTraceur() | ||
.then(function(traceur) { | ||
// 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(); | ||
traceur.options.sourceMaps = true; | ||
traceur.options.modules = 'parse'; | ||
// traceur.options.blockBinding = true; | ||
// current script tags used to produce the Traceur src (converting collection to array) | ||
if (isBrowser) { | ||
var scripts = document.getElementsByTagName('script'); | ||
var reporter = new traceur.util.ErrorReporter(); | ||
var curScript; | ||
for (var i = 0; i < scripts.length; i++) { | ||
curScript = scripts[i]; | ||
if (curScript.src.match(/es6-module-loader(\.min)?\.js/)) | ||
traceurSrc = curScript.src.substr(0, curScript.src.lastIndexOf('/') + 1) + 'traceur.js'; | ||
else | ||
traceurSrc = curScript.getAttribute('data-traceur-src'); | ||
if (traceurSrc) | ||
break; | ||
reporter.reportMessageInternal = function(location, kind, format, args) { | ||
throw kind + '\n' + location; | ||
} | ||
} | ||
var self = this; | ||
var System = global.System; | ||
(isBrowser ? global.System.load : function(src, callback) { | ||
self.traceur = require('traceur'); | ||
callback(); | ||
}).call(global.System, traceurSrc, function() { | ||
if (isBrowser) { | ||
if (self.traceur) | ||
return callback(); | ||
else | ||
self.traceur = global.System.get('../src/traceur.js'); | ||
} | ||
var parser = new traceur.syntax.Parser(reporter, new traceur.syntax.SourceFile(load.address, load.source)); | ||
self.traceur.options.sourceMaps = true; | ||
self.traceur.options.modules = 'parse'; | ||
var tree = parser.parseModule(); | ||
self.reporter = new self.traceur.util.ErrorReporter(); | ||
self.reporter.reportMessageInternal = function(location, kind, format, args) { | ||
throw kind + '\n' + location; | ||
} | ||
self.createModuleLoaderTransformer( | ||
self.traceur.codegeneration.ParseTreeFactory, | ||
self.traceur.codegeneration.ParseTreeTransformer | ||
); | ||
var imports = getImports(tree); | ||
global.System = System; | ||
callback(); | ||
return { | ||
deps: imports, | ||
execute: function() { | ||
// write dependencies as unique globals | ||
// creating a map from the unnormalized import name to the unique global name | ||
var globalMap = {}; | ||
for (var i = 0; i < arguments.length; i++) { | ||
var name = '__moduleDependency' + i; | ||
global[name] = arguments[i]; | ||
globalMap[imports[i]] = name; | ||
} | ||
// transform | ||
var transformer = new traceur.codegeneration.FromOptionsTransformer(reporter); | ||
transformer.append(function(tree) { | ||
return new traceur.codegeneration.ModuleLoaderTransformer(globalMap, '__exports').transformAny(tree); | ||
}); | ||
tree = transformer.transform(tree); | ||
// convert back to a source string | ||
var sourceMapGenerator = new traceur.outputgeneration.SourceMapGenerator({ file: load.address }); | ||
var options = { sourceMapGenerator: sourceMapGenerator }; | ||
source = traceur.outputgeneration.TreeWriter.write(tree, options); | ||
if (isBrowser) | ||
source += '\n//# sourceMappingURL=data:application/json;base64,' + btoa(options.sourceMap) + '\n'; | ||
global.__exports = {}; | ||
__scopedEval(source, global, load.address); | ||
var exports = global.__exports; | ||
delete global.__exports; | ||
for (var i = 0; i < arguments.length; i++) | ||
delete global['__moduleDependency' + i]; | ||
return new Module(exports); | ||
} | ||
}; | ||
}); | ||
}, | ||
createModuleLoaderTransformer: function(ParseTreeFactory, ParseTreeTransformer) { | ||
var createAssignmentExpression = ParseTreeFactory.createAssignmentExpression; | ||
var createVariableDeclaration = ParseTreeFactory.createVariableDeclaration; | ||
} | ||
}); | ||
// count anonymous evals to have unique name | ||
var anonCnt = 1; | ||
System.baseURL = isBrowser ? window.location.href.substring(0, window.location.href.lastIndexOf('\/') + 1).split('#')[0] : './'; | ||
// ES6 to ES5 parsing functions | ||
// comprehensively overclassifying regex detectection for es6 module syntax | ||
var es6RegEx = /(?:^\s*|[}{\(\);,\n]\s*)((import|module)\s+[^"']+\s+from\s+['"]|export\s+(\*|\{|default|function|var|const|let|[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*))/; | ||
// dynamically load traceur when needed | ||
// populates the traceur, reporter and moduleLoaderTransfomer variables | ||
// NB we need to queue getTraceur callbacks due to https://github.com/jorendorff/js-loaders/issues/60 | ||
var traceur, traceurPromise; | ||
function getTraceur() { | ||
if (traceur) | ||
return Promise.resolve(traceur); | ||
if (traceurPromise) | ||
return traceurPromise; | ||
return traceurPromise = (isBrowser ? exports.System.import : function(name, src, callback) { | ||
return Promise.resolve(require('traceur')); | ||
}).call(exports.System, 'traceur', { address: traceurSrc }).then(function(_traceur) { | ||
traceurPromise = null; | ||
var createMemberExpression = ParseTreeFactory.createMemberExpression; | ||
var createCallExpression = ParseTreeFactory.createCallExpression; | ||
if (isBrowser) | ||
_traceur = global.traceur; | ||
var createVariableDeclarationList = ParseTreeFactory.createVariableDeclarationList; | ||
var createArgumentList = ParseTreeFactory.createArgumentList; | ||
var createStringLiteral = ParseTreeFactory.createStringLiteral; | ||
var createIdentifierExpression = ParseTreeFactory.createIdentifierExpression; | ||
traceur = _traceur; | ||
var createMemberLookupExpression = ParseTreeFactory.createMemberLookupExpression; | ||
traceur.codegeneration.ModuleLoaderTransformer = createModuleLoaderTransformer( | ||
traceur.codegeneration.ParseTreeFactory, | ||
traceur.codegeneration.ParseTreeTransformer | ||
); | ||
var createCommaExpression = ParseTreeFactory.createCommaExpression; | ||
var createVariableStatement = ParseTreeFactory.createVariableStatement; | ||
return traceur; | ||
}); | ||
} | ||
var createAssignmentStatement = ParseTreeFactory.createAssignmentStatement; | ||
var createExpressionStatement = ParseTreeFactory.createExpressionStatement; | ||
// NB update to new transformation system | ||
function createModuleLoaderTransformer(ParseTreeFactory, ParseTreeTransformer) { | ||
var createAssignmentExpression = ParseTreeFactory.createAssignmentExpression; | ||
var createVariableDeclaration = ParseTreeFactory.createVariableDeclaration; | ||
var createCallExpression = ParseTreeFactory.createCallExpression; | ||
// var VARIABLE = __Loader.get('MODULE_NAME')['VALUE'], ... | ||
// var VARIABLE = __Loader.get('MODULE_NAME'), ... | ||
var createModuleVariableDeclaration = function(moduleName, variables, values, location) { | ||
var variableDeclarations = variables.map(function(variable, i) { | ||
return createVariableDeclaration(variable, createImportExpression(moduleName, values[i])); | ||
}); | ||
var varList = createVariableDeclarationList('var', variableDeclarations); | ||
varList.location = location; | ||
return createVariableStatement(varList); | ||
} | ||
var createVariableDeclarationList = ParseTreeFactory.createVariableDeclarationList; | ||
var createStringLiteral = ParseTreeFactory.createStringLiteral; | ||
var createIdentifierExpression = ParseTreeFactory.createIdentifierExpression; | ||
// __Loader.get('MODULE_NAME')['VALUE'] | ||
var createImportExpression = function(moduleName, value) { | ||
var expression = createCallExpression( | ||
createMemberExpression('__Loader', 'get'), | ||
createArgumentList([createStringLiteral(moduleName)]) | ||
); | ||
return value ? createMemberLookupExpression(expression, createStringLiteral(value)) : expression; | ||
} | ||
var createMemberLookupExpression = ParseTreeFactory.createMemberLookupExpression; | ||
// __exports['EXPORT_NAME'] | ||
var createExportExpression = function(exportName) { | ||
return createMemberLookupExpression(createIdentifierExpression('__exports'), createStringLiteral(exportName)); | ||
} | ||
var createCommaExpression = ParseTreeFactory.createCommaExpression; | ||
var createVariableStatement = ParseTreeFactory.createVariableStatement; | ||
var createAssignmentStatement = ParseTreeFactory.createAssignmentStatement; | ||
var createExpressionStatement = ParseTreeFactory.createExpressionStatement; | ||
var self = this; | ||
var ModuleLoaderTransformer = function(globalMap, exportGlobal) { | ||
this.depMap = globalMap; | ||
this.exportGlobal = exportGlobal; | ||
} | ||
ModuleLoaderTransformer.prototype = Object.create(ParseTreeTransformer.prototype); | ||
// var VARIABLE = __moduleDependencyX['VALUE'], ... | ||
// var VARIABLE = __moduleDependencyX, ... | ||
ModuleLoaderTransformer.prototype.createModuleVariableDeclaration = function(moduleName, variables, values, location) { | ||
var self = this; | ||
var ModuleTransformer = function(normalizeMap) { | ||
this.nMap = normalizeMap; | ||
} | ||
ModuleTransformer.prototype = Object.create(ParseTreeTransformer.prototype); | ||
var variableDeclarations = variables.map(function(variable, i) { | ||
return createVariableDeclaration(variable, self.createImportExpression(moduleName, values[i])); | ||
}); | ||
var varList = createVariableDeclarationList('var', variableDeclarations); | ||
varList.location = location; | ||
return createVariableStatement(varList); | ||
} | ||
ModuleTransformer.prototype.transformImportDeclaration = function(tree) { | ||
var moduleName = tree.moduleSpecifier.token.processedValue; | ||
moduleName = this.nMap[moduleName] || moduleName; | ||
// __moduleDependencyX['VALUE'] | ||
ModuleLoaderTransformer.prototype.createImportExpression = function(moduleName, value) { | ||
var expression = createIdentifierExpression(this.depMap[moduleName]); | ||
return value ? createMemberLookupExpression(expression, createStringLiteral(value)) : expression; | ||
} | ||
var variables = []; | ||
var values = []; | ||
// __exports['EXPORT_NAME'] | ||
ModuleLoaderTransformer.prototype.createExportExpression = function(exportName) { | ||
return createMemberLookupExpression(createIdentifierExpression(this.exportGlobal), createStringLiteral(exportName)); | ||
} | ||
// import $ from 'jquery'; | ||
if (tree.importClause.binding) { | ||
variables.push(tree.importClause.binding.identifierToken); | ||
values.push('default'); | ||
ModuleLoaderTransformer.prototype.transformImportDeclaration = function(tree) { | ||
var moduleName = tree.moduleSpecifier.token.processedValue; | ||
var variables = []; | ||
var values = []; | ||
// import 'jquery'; | ||
// import $ from 'jquery'; | ||
if (tree.importClause && tree.importClause.binding) { | ||
variables.push(tree.importClause.binding.identifierToken); | ||
values.push('default'); | ||
} | ||
// import { ... } from 'jquery'; | ||
else if (tree.importClause) { | ||
var specifiers = tree.importClause.specifiers; | ||
for (var i = 0; i < specifiers.length; i++) { | ||
var specifier = specifiers[i]; | ||
variables.push(specifier.rhs ? specifier.rhs.value : specifier.lhs.value); | ||
values.push(specifier.lhs.value); | ||
} | ||
} | ||
return this.createModuleVariableDeclaration(moduleName, variables, values, tree.location); | ||
} | ||
ModuleLoaderTransformer.prototype.transformModuleDeclaration = function(tree) { | ||
var moduleName = tree.expression.token.processedValue; | ||
return this.createModuleVariableDeclaration(moduleName, [tree.identifier], [null], tree.location); | ||
} | ||
ModuleLoaderTransformer.prototype.transformExportDeclaration = function(tree) { | ||
var declaration = tree.declaration; | ||
// import { ... } from 'jquery'; | ||
else { | ||
var specifiers = tree.importClause.specifiers; | ||
if (declaration.type == 'NAMED_EXPORT') { | ||
var moduleName = declaration.moduleSpecifier && declaration.moduleSpecifier.token.processedValue; | ||
// export {a as b, c as d} | ||
// export {a as b, c as d} from 'module' | ||
if (declaration.specifierSet.type != 'EXPORT_STAR') { | ||
var expressions = []; | ||
var specifiers = declaration.specifierSet.specifiers; | ||
for (var i = 0; i < specifiers.length; i++) { | ||
var specifier = specifiers[i]; | ||
variables.push(specifier.rhs ? specifier.rhs.value : specifier.lhs.value); | ||
values.push(specifier.lhs.value); | ||
expressions.push(createAssignmentExpression( | ||
this.createExportExpression(specifier.rhs ? specifier.rhs.value : specifier.lhs.value), | ||
moduleName | ||
? this.createImportExpression(moduleName, specifier.lhs.value) | ||
: createIdentifierExpression(specifier.lhs.value) | ||
)); | ||
} | ||
var commaExpression = createExpressionStatement(createCommaExpression(expressions)); | ||
commaExpression.location = tree.location; | ||
return commaExpression; | ||
} | ||
return createModuleVariableDeclaration(moduleName, variables, values, tree.location); | ||
else { | ||
var exportStarStatement = createAssignmentStatement(createIdentifierExpression(this.exportGlobal), this.createImportExpression(moduleName)); | ||
exportStarStatement.location = tree.location; | ||
return exportStarStatement; | ||
} | ||
} | ||
ModuleTransformer.prototype.transformModuleDeclaration = function(tree) { | ||
var moduleName = tree.expression.token.processedValue; | ||
moduleName = this.nMap[moduleName] || moduleName; | ||
return createModuleVariableDeclaration(moduleName, [tree.identifier], [null], tree.location); | ||
// export var p = 4; | ||
else if (declaration.type == 'VARIABLE_STATEMENT') { | ||
// export var p = ... | ||
var varDeclaration = declaration.declarations.declarations[0]; | ||
varDeclaration.initialiser = createAssignmentExpression( | ||
this.createExportExpression(varDeclaration.lvalue.identifierToken.value), | ||
this.transformAny(varDeclaration.initialiser) | ||
); | ||
return declaration; | ||
} | ||
ModuleTransformer.prototype.transformExportDeclaration = function(tree) { | ||
var declaration = tree.declaration; | ||
if (declaration.type == 'NAMED_EXPORT') { | ||
var moduleName = declaration.moduleSpecifier && declaration.moduleSpecifier.token.processedValue; | ||
if (moduleName) | ||
moduleName = this.nMap[moduleName] || moduleName; | ||
// export {a as b, c as d} | ||
// export {a as b, c as d} from 'module' | ||
if (declaration.specifierSet.type != 'EXPORT_STAR') { | ||
var expressions = []; | ||
var specifiers = declaration.specifierSet.specifiers; | ||
for (var i = 0; i < specifiers.length; i++) { | ||
var specifier = specifiers[i]; | ||
expressions.push(createAssignmentExpression( | ||
createExportExpression(specifier.rhs ? specifier.rhs.value : specifier.lhs.value), | ||
moduleName | ||
? createImportExpression(moduleName, specifier.lhs.value) | ||
: createIdentifierExpression(specifier.lhs.value) | ||
)); | ||
} | ||
var commaExpression = createExpressionStatement(createCommaExpression(expressions)); | ||
commaExpression.location = tree.location; | ||
return commaExpression; | ||
} | ||
else { | ||
var exportStarStatement = createAssignmentStatement(createIdentifierExpression('__exports'), createImportExpression(moduleName)); | ||
exportStarStatement.location = tree.location; | ||
return exportStarStatement; | ||
} | ||
} | ||
// export var p = 4; | ||
else if (declaration.type == 'VARIABLE_STATEMENT') { | ||
// export var p = ... | ||
var varDeclaration = declaration.declarations.declarations[0]; | ||
varDeclaration.initializer = createAssignmentExpression( | ||
createExportExpression(varDeclaration.lvalue.identifierToken.value), | ||
this.transformAny(varDeclaration.initializer) | ||
); | ||
return declaration; | ||
} | ||
// export function q() {} | ||
else if (declaration.type == 'FUNCTION_DECLARATION') { | ||
var varDeclaration = createVariableDeclaration( | ||
declaration.name.identifierToken.value, | ||
createAssignmentStatement( | ||
createExportExpression(declaration.name.identifierToken.value), | ||
this.transformAny(declaration) | ||
) | ||
); | ||
varDeclaration.location = tree.location; | ||
return createVariableDeclarationList('var', [varDeclaration]); | ||
} | ||
// export default ... | ||
else if (declaration.type == 'EXPORT_DEFAULT') { | ||
return createAssignmentStatement( | ||
createExportExpression('default'), | ||
this.transformAny(declaration.expression) | ||
); | ||
} | ||
return tree; | ||
// export function q() {} | ||
else if (declaration.type == 'FUNCTION_DECLARATION') { | ||
var varDeclaration = createVariableDeclaration( | ||
declaration.name.identifierToken.value, | ||
createAssignmentStatement( | ||
this.createExportExpression(declaration.name.identifierToken.value), | ||
this.transformAny(declaration) | ||
) | ||
); | ||
varDeclaration.location = tree.location; | ||
return createVariableDeclarationList('var', [varDeclaration]); | ||
} | ||
this.ModuleTransformer = ModuleTransformer; | ||
}, | ||
// store the names of modules which needed to be parsed by esprima | ||
parseNames: {}, | ||
// store the syntax trees for modules parsed by esprima | ||
treeCache: {}, | ||
getSyntaxTree: function(source, options) { | ||
var name = options.normalized || options.address; | ||
if (this.treeCache[name]) | ||
return this.treeCache[name]; | ||
var parser = new this.traceur.syntax.Parser(this.reporter, new this.traceur.syntax.SourceFile(options.address, source)); | ||
var tree = options.type == 'module' ? parser.parseModule() : parser.parseScript(); | ||
return this.treeCache[name] = tree; | ||
}, | ||
getTransformedSyntaxTree: function(source, options) { | ||
var tree = this.getSyntaxTree(source, options); | ||
if (options.es6) { | ||
var project = new this.traceur.semantics.symbols.Project(options.address); | ||
var transformer = new this.traceur.codegeneration.ProgramTransformer(this.reporter, project); | ||
tree = transformer.transform(tree); | ||
// export default ... | ||
else if (declaration.type == 'EXPORT_DEFAULT') { | ||
return createAssignmentStatement( | ||
this.createExportExpression('default'), | ||
this.transformAny(declaration.expression) | ||
); | ||
} | ||
return tree; | ||
} | ||
return ModuleLoaderTransformer; | ||
} | ||
return (new this.ModuleTransformer(options.normalizeMap || {})).transformAny(tree); | ||
}, | ||
// parse the list of import module names for a given source | ||
parseImports: function(source, options) { | ||
if (!this.checkModuleSyntax(options.normalized || options.address, source)) | ||
return []; | ||
// tree traversal, NB should use visitor pattern here | ||
function traverse(object, iterator, parent, parentProperty) { | ||
var key, child; | ||
if (iterator(object, parent, parentProperty) === false) | ||
return; | ||
for (key in object) { | ||
if (!object.hasOwnProperty(key)) | ||
continue; | ||
if (key == 'location' || key == 'type') | ||
continue; | ||
child = object[key]; | ||
if (typeof child == 'object' && child !== null) | ||
traverse(child, iterator, object, key); | ||
} | ||
} | ||
var tree = this.getSyntaxTree(source, options); | ||
var imports = []; | ||
// given a syntax tree, return the import list | ||
function getImports(moduleTree) { | ||
var imports = []; | ||
// NB switch this to a Visitor implementation | ||
this.traverse(tree, function(node) { | ||
// import {} from 'foo'; | ||
// export * from 'foo'; | ||
// export { ... } from 'foo'; | ||
// module x from 'foo'; | ||
if (node.type == 'EXPORT_DECLARATION') { | ||
if (node.declaration.moduleSpecifier) | ||
imports.push(node.declaration.moduleSpecifier.token.processedValue); | ||
} | ||
else if (node.type == 'IMPORT_DECLARATION') | ||
imports.push(node.moduleSpecifier.token.processedValue); | ||
else if (node.type == 'MODULE_DECLARATION') | ||
imports.push(node.expression.token.processedValue); | ||
}); | ||
return imports; | ||
}, | ||
// 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 | ||
function addImport(name) { | ||
if (indexOf.call(imports, name) == -1) | ||
imports.push(name); | ||
} | ||
// regex showed no need for esprima - normal eval | ||
if (!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; | ||
traverse(moduleTree, function(node) { | ||
// import {} from 'foo'; | ||
// export * from 'foo'; | ||
// export { ... } from 'foo'; | ||
// module x from 'foo'; | ||
if (node.type == 'EXPORT_DECLARATION') { | ||
if (node.declaration.moduleSpecifier) | ||
addImport(node.declaration.moduleSpecifier.token.processedValue); | ||
} | ||
else if (node.type == 'IMPORT_DECLARATION') | ||
addImport(node.moduleSpecifier.token.processedValue); | ||
else if (node.type == 'MODULE_DECLARATION') | ||
addImport(node.expression.token.processedValue); | ||
}); | ||
return imports; | ||
} | ||
var tree = this.getTransformedSyntaxTree(source, { es6: true, normalized: opt.name, address: opt.sourceURL, normalizeMap: opt.normalizeMap }); | ||
delete this.treeCache[opt.name || opt.address]; | ||
// generate source | ||
var sourceMapGenerator = new this.traceur.outputgeneration.SourceMapGenerator({ file: opt.sourceURL }); | ||
var options = { sourceMapGenerator: sourceMapGenerator }; | ||
// Export the Loader class | ||
exports.Loader = Loader; | ||
// Export the Module class | ||
exports.Module = Module; | ||
// Export the System object | ||
exports.System = System; | ||
source = this.traceur.outputgeneration.TreeWriter.write(tree, options); | ||
if (isBrowser) | ||
source += '\n//# sourceMappingURL=data:application/json;base64,' + btoa(options.sourceMap) + '\n' | ||
var traceurSrc; | ||
loader.global.__Loader = loader; | ||
loader.global.__exports = {}; | ||
// <script type="module"> support | ||
// allow a data-init function callback once loaded | ||
if (isBrowser) { | ||
var curScript = document.getElementsByTagName('script'); | ||
curScript = curScript[curScript.length - 1]; | ||
__scopedEval((loader.strict ? '"use strict";\n' : '') + source, loader.global, opt.sourceURL); | ||
// set the path to traceur | ||
traceurSrc = curScript.getAttribute('data-traceur-src') | ||
|| curScript.src.substr(0, curScript.src.lastIndexOf('/') + 1) + 'traceur.js'; | ||
delete loader.global.__Loader; | ||
var exports = loader.global.__exports; | ||
delete loader.global.__exports; | ||
document.onreadystatechange = function() { | ||
if (document.readyState == 'interactive') { | ||
var scripts = document.getElementsByTagName('script'); | ||
// 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.' | ||
for (var i = 0; i < scripts.length; i++) { | ||
var script = scripts[i]; | ||
if (script.type == 'module') { | ||
// <script type="module" name="" src=""> support | ||
var name = script.getAttribute('name'); | ||
var address = script.getAttribute('src'); | ||
var source = script.innerHTML; | ||
(name | ||
? System.define(name, source, { address: address }) | ||
: System.module(source, { address: address }) | ||
).then(function() {}, function(err) { nextTick(function() { throw err; }); }); | ||
} | ||
} | ||
} | ||
return exports; | ||
} | ||
}; | ||
// Export the Loader class | ||
global.Loader = Loader; | ||
// Export the Module class | ||
global.Module = Module; | ||
// Export the System object | ||
global.System = defaultSystemLoader; | ||
// run the data-init function on the script tag | ||
if (curScript.getAttribute('data-init')) | ||
window[curScript.getAttribute('data-init')](); | ||
} | ||
@@ -892,3 +1305,3 @@ })(); | ||
// carefully scoped eval with given global | ||
var __scopedEval = function(__source, global, __sourceURL) { | ||
function __scopedEval(__source, global, __sourceURL) { | ||
eval('with(global) { (function() { ' + __source + ' \n }).call(global); }' | ||
@@ -900,22 +1313,1 @@ + (__sourceURL && !__source.match(/\/\/[@#] ?(sourceURL|sourceMappingURL)=(.+)/) | ||
})(); | ||
(function() { | ||
// allow a data-init function callback once loaded | ||
if (typeof window != 'undefined') { | ||
var curScript = document.getElementsByTagName('script'); | ||
curScript = curScript[curScript.length - 1]; | ||
if (curScript.getAttribute('data-init')) | ||
window[curScript.getAttribute('data-init')](); | ||
document.onreadystatechange = function() { | ||
if (document.readyState == 'interactive') { | ||
var scripts = document.getElementsByTagName('script'); | ||
for (var i = 0; i < scripts.length; i++) { | ||
if (scripts[i].type == 'module') { | ||
System.evalAsync(scripts[i].innerHTML); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
})(); |
{ | ||
"name": "es6-module-loader", | ||
"description": "An ES6 Module Loader shim", | ||
"version": "0.3.3", | ||
"version": "0.4.0", | ||
"homepage": "https://github.com/ModuleLoader/es6-module-loader", | ||
@@ -24,5 +24,2 @@ "author": { | ||
], | ||
"dependencies": { | ||
"traceur": "0.0.6" | ||
}, | ||
"devDependencies": { | ||
@@ -47,5 +44,7 @@ "grunt": "~0.4.1", | ||
"files": [ | ||
"lib/es6-module-loader.js", | ||
"lib/esprima-es6.min.js" | ||
] | ||
"lib/es6-module-loader.js" | ||
], | ||
"dependencies": { | ||
"traceur": "0.0.8" | ||
} | ||
} |
316
README.md
@@ -1,14 +0,26 @@ | ||
# ES6 Module Loader | ||
# ES6 Module Loader Polyfill | ||
ES6 Module Loader polyfill based on [http://wiki.ecmascript.org/doku.php?id=harmony:module_loaders](http://wiki.ecmascript.org/doku.php?id=harmony:module_loaders) by Luke Hoban, Addy Osmani and Guy Bedford. | ||
_Fully up to date with the latest specification_ | ||
* [Dynamically load ES6 modules](#getting-started) in all modern browsers including IE8+ | ||
* Uses [Traceur](https://github.com/google/traceur-compiler) for [compiling ES6 modules and syntax into ES5 in the browser with source map support](#integration-with-traceur) | ||
The new ES6 module specification defines a module system in JavaScript using `import` and `export` syntax, along with a module loader factory (`new Loader`). | ||
A separate browser specification defines the `window.System` loader, a dynamic browser loader for JavaScript modules, as well as a new `<module>` tag (`<script type="module">` for compatibility with existing browsers). | ||
This polyfill implements the `Loader` and `Module` globals, exactly as specified in the [2013-12-02 ES6 Module Specification Draft](https://github.com/jorendorff/js-loaders/blob/e60d3651/specs/es6-modules-2013-12-02.pdf) and the `System` browser loader exactly as suggested in the [sample implementation](https://github.com/jorendorff/js-loaders/blob/964623c75d/browser-loader.js). | ||
The complete combined polyfill comes to 16KB minified, making it suitable for production use in future, provided that modules are built into ES5 making them independent of Traceur. Build workflows are currently in progress. | ||
* Provides an asynchronous loader (`System.import`) to [dynamically load ES6 modules](#getting-started) in all modern browsers including IE8+ | ||
* Adds support for the `<script type="module">` tag allowing inline module loading. | ||
* Uses [Traceur](https://github.com/google/traceur-compiler) for compiling ES6 modules and syntax into ES5 in the browser with source map support | ||
* Use as a base for creating a [custom spec-compliant module loader](#creating-a-custom-loader) | ||
* Fully compatible with NodeJS allowing for spec-compliant server-side module loading | ||
Not yet suitable for production use while the specification is still subject to change. | ||
_Not yet suitable for production use while the specification is still subject to change._ | ||
See the [demo folder](https://github.com/ModuleLoader/es6-module-loader/blob/master/demo/index.html) in this repo for a working example demonstrating both module loading the module tag. | ||
## Getting Started | ||
Download both [es6-module-loader.js](https://raw.github.com/ModuleLoader/es6-module-loader/master/dist/es6-module-loader.js) and [traceur.js](https://raw.github.com/ModuleLoader/es6-module-loader/master/dist/traceur.js) into the same folder. | ||
Download both [es6-module-loader.js](https://raw.github.com/ModuleLoader/es6-module-loader/master/dist/es6-module-loader.js) and [traceur.js](https://raw.github.com/ModuleLoader/es6-module-loader/master/lib/traceur.js) into the same folder. | ||
@@ -18,14 +30,35 @@ Then include the `es6-module-loader.js` file on its own in the page: | ||
```html | ||
<script src="path/to/es6-module-loader.js"></script> | ||
<script src="es6-module-loader.js"></script> | ||
``` | ||
Traceur will be downloaded only when needed for ES6 syntax parsing. | ||
Traceur will be downloaded only when needed for ES6 syntax parsing, detected as the existence of module syntax, or as specified by the `metadata.es6` property. | ||
If we have an ES6 module file located at `/lib/app/main.js`, we can then load this with the system loader: | ||
Write an ES6 module: | ||
mymodule.js: | ||
```javascript | ||
export class q { | ||
constructor() { | ||
console.log('this is an es6 class!'); | ||
} | ||
} | ||
``` | ||
We can then load this module with a module tag in the page: | ||
```html | ||
<script type="module"> | ||
// loads the 'q' export from 'mymodule.js' in the same path as the page | ||
import { q } from 'mymodule'; | ||
new q(); // -> 'this is an es6 class!' | ||
</script> | ||
``` | ||
Or we can also use the dynamic loader: | ||
```html | ||
<script> | ||
System.baseURL = '/lib'; | ||
System.import('app/main', function(app) { | ||
new app.Application(); | ||
System.import('mymodule').then(function(m) { | ||
new m.q(); | ||
}); | ||
@@ -35,67 +68,103 @@ </script> | ||
Any module dependencies of the file will be dynamically loaded and linked as per the ES6 module specification. | ||
The dynamic loader returns an instance of the `Module` class, which contains getters for the named exports (in this case, `q`). | ||
Modules are loaded by **Module Name** roughly using the rule: | ||
## Terminology | ||
### Modules and Module Loaders | ||
A module is simply a JavaScript file written with module syntax. Modules _export_ values, which can then be _imported_ by other modules. | ||
[CommonJS](http://wiki.commonjs.org/wiki/CommonJS) and [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) JavaScript files are modules. | ||
A module loader provides the ability to dynamically load modules, and also keeps track of all loaded modules in a module registry. | ||
Typically, in production, the module registry would be populated by an initial compiled bundle of modules. Later in the page state, it may become necessary to dynamically | ||
load a new module. This module can then share dependencies with the initial page bundle without having to reload any dependencies. | ||
The ES6 Module Specification defines the module syntax for ES6 module files, and also defines a module loader factory class for creating ES6-compatible module loaders. | ||
Module code is treated differently to scripts due to the nature of exports and imports. This is why the `<script type="module">` tag (which will become the `<module>` tag in modern browsers) is introduced to distinguish script code from module code. Scripts cannot export or import, but are able to use the dynamic loader `System.import(...)`. | ||
### Module Names and baseURL | ||
Module names are just like moduleIDs in RequireJS. Non-relative module names (not starting with `.`) are converted to a URL with the following rule: | ||
```javascript | ||
URL = baseURL + '/' + ModuleName + '.js' | ||
URL = absolutePath(baseURL, ModuleName + '.js') | ||
``` | ||
Relative module names can be written `'./local-module'` to load relative to the parent module name. | ||
Relative module names can be written `'./local-module'` to load relative to their parent module name. `..` syntax is also supported allowing easily portable modules. | ||
## Writing and Loading ES6 Modules | ||
The `baseURL` is set to the current page path by default. It is a property of the `System` loader and can be changed: | ||
The contents of `/lib/app/main.js` can be written: | ||
```javascript | ||
System.baseURL = '/lib/'; | ||
System.baseURL = 'http://mysite.com/js/'; | ||
``` | ||
## ES6 Module Syntax | ||
### Exporting | ||
ES6 module syntax is most similar to the `exports.method = function() {}` pattern in NodeJS of creating multiple named exports. | ||
In CommonJS one might write: | ||
```javascript | ||
import { Helpers } from './app-dep'; | ||
exports.someMethod = function() { | ||
export class Application { | ||
constructor() { | ||
console.log('Initialized ES6 App Module'); | ||
}, | ||
foo() { | ||
Helpers.foo(); | ||
} | ||
} | ||
exports.another = {}; | ||
``` | ||
With `/lib/app/app-dep.js` containing: | ||
In ES6, this same code would be written: | ||
exporter.js: | ||
```javascript | ||
export var Helpers = { ... }; | ||
export function someMethod() { | ||
} | ||
export var another = {}; | ||
``` | ||
When loaded, as with the `System.import` call above, these module files are dynamically loaded and compiled to ES5 in the browser and executed. | ||
Notice that the name of the function, class or variable gets used as the export name. | ||
## Moving to Production | ||
### Importing | ||
When in production, one wouldn't want to load ES6 modules and syntax in the browser. Rather the modules would be built into ES5 and AMD to be loaded. | ||
When importing, we import any exports we need by name, and can also choose to rename them: | ||
One can construct an AMD loader from this polyfill in under 30KB for such a scenario. | ||
importer.js: | ||
```javascript | ||
import { someMethod, another as newName } from './exporter'; | ||
Think of this module as RequireJS, and [Traceur](https://github.com/google/traceur-compiler) as the r.js optimizer. | ||
someMethod(); | ||
typeof newName == 'object'; | ||
``` | ||
Bundling techniques for ES6 are an active area of development. | ||
### Default Import and Export | ||
## Module Tag | ||
Sometimes one doesn't want to write an import name at all. For this we can use the default export: | ||
Modules can also be loaded with the module tag: | ||
export-default.js: | ||
```javascript | ||
export default function foo() { | ||
console.log('foo'); | ||
} | ||
``` | ||
```html | ||
<script src="/path/to/es6-module-loader.js"></script> | ||
<script>System.baseURL = '/lib'</script> | ||
<script type="module"> | ||
import { Application } from 'app/main'; | ||
import-default.js: | ||
```javascript | ||
import customName from './export-default'; | ||
new Application(); | ||
</script> | ||
customName(); // -> 'foo' | ||
``` | ||
## Full Module Syntax Summary | ||
### All Supported Syntax | ||
The following module syntax is supported by this polyfill, which is to the latest specification (November 2013): | ||
There are a few other variations of module syntax, the full list of supported statements is listed below. | ||
```javascript | ||
//import 'jquery'; // import a module ** awaiting support in Traceur | ||
import 'jquery'; // import a module without any import bindings | ||
import $ from 'jquery'; // import the default export of a module | ||
@@ -120,2 +189,62 @@ import { $ } from 'jquery'; // import a named export of a module | ||
Note that any valid declaration can be exported. In ES6, this includes `class` (as in the example above), `const`, and `let`. | ||
## Dynamic Module Loading | ||
The dynamic module loader uses promises for resolution. Modules can have both a resolve and reject handler: | ||
```javascript | ||
System.import('some-module').then(function(m) { | ||
// got Module instance m | ||
}, function(err) { | ||
// error | ||
}); | ||
``` | ||
## Moving to Production | ||
When in production, one wouldn't want to load ES6 modules and syntax in the browser. Rather the modules would be built into ES5 and AMD to be loaded. | ||
Also, suitable bundling would need to be used. | ||
We are actively working on these workflows. | ||
## Module Tag | ||
The module tag supports both named and anonymous use. | ||
### Anonymous Module | ||
This is just like an anonymous <script> tag, allowing code to be run directly: | ||
```html | ||
<script type="module"> | ||
import 'some-import'; | ||
class q { | ||
} | ||
new q(); | ||
</script> | ||
``` | ||
### Named Module | ||
A named module is just like an anonymous module, but defines the module in the registry as well: | ||
```html | ||
<script type="module" name="my-module"> | ||
export var p = 'named-module'; | ||
</script> | ||
<script> | ||
// later on - | ||
setTimeout(function() { | ||
System.import('my-module').then(function(m) { | ||
console.log(m.p); // -> named-module | ||
}); | ||
}, 100); | ||
</script> | ||
``` | ||
## NodeJS Support | ||
@@ -131,3 +260,3 @@ | ||
Tracuer support requires `npm install traceur`, allowing ES6 syntax in NodeJS: | ||
Traceur support requires `npm install traceur`, allowing ES6 syntax in NodeJS: | ||
@@ -151,10 +280,10 @@ ```javascript | ||
* Normalize: Given the import name, provide the canonical module name. | ||
* Resolve: Given a canonical module name, provide the URL for the resource. | ||
* Locate: Given a canonical module name, provide the URL for the resource. | ||
* Fetch: Given a URL for a resource, fetch its content. | ||
* Translate: Given module source, make any source modifications. | ||
* Link: Given module source, determine its dependencies, and execute it. | ||
* Instantiate: Given module source, determine its dependencies, and execute it. | ||
Variations of these hooks can allow creating many different styles of loader. | ||
Evey hook is optional for a new loader, with default behaviours defined. | ||
Each hook can either return a result directly, or a promise (thenable) for the result. | ||
@@ -165,20 +294,22 @@ To create a new loader, use the `Loader` constructor: | ||
var MyLoader = new Loader({ | ||
global: window, | ||
strict: false, | ||
normalize: function (name, referer) { | ||
return canonicalName; | ||
normalize: function (name, parentName, parentAddress) { | ||
return resolvedName; | ||
}, | ||
resolve: function (normalized, options) { | ||
return this.baseURL + '/' + normalized + '.js'; | ||
locate: function (load) { | ||
// load.name is normalized name | ||
return this.baseURL + '/' + load.name + '.js'; | ||
}, | ||
fetch: function (url, fulfill, reject, options) { | ||
myXhr.get(url, fulfill, reject); | ||
fetch: function (load) { | ||
// return a promise. Alternatively, just use the system fetch | ||
// promise -return System.fetch(load) | ||
var defer = MyPromiseLibrary.createDeferred(); | ||
myXhr.get(load.address, defer.resolve, defer.reject); | ||
return defer.promise; | ||
}, | ||
translate: function (source, options) { | ||
return compile(source); | ||
translate: function (load) { | ||
return load.source; | ||
}, | ||
link: function (source, options) { | ||
instantiate: function (load) { | ||
// use standard es6 linking | ||
return; | ||
return System.instantiate(load); | ||
@@ -188,3 +319,3 @@ // provide custom linking | ||
return { | ||
imports: ['some', 'dependencies'], | ||
deps: ['some', 'dependencies'], | ||
execute: function(depA, depB) { | ||
@@ -200,12 +331,57 @@ return new Module({ | ||
For a more in-depth overview of creating 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). | ||
For a more in-depth overview of creating with custom loaders, some resources are provided below: | ||
* The [System Loader implementation](https://github.com/ModuleLoader/es6-module-loader/blob/master/lib/es6-module-loader.js#L804) | ||
* [ES6 Module Specification, latest draft](https://github.com/jorendorff/js-loaders/blob/e60d3651/specs/es6-modules-2013-12-02.pdf) | ||
* [Yehuda Katz's essay](https://gist.github.com/wycats/51c96e3adcdb3a68cbc3) (outdated) | ||
### Specification Notes | ||
## Specification Notes | ||
The polyfill is in the process of being updated to the latest complete draft of the module specification. | ||
Notes on the exact specification implementation differences are included below. | ||
This will alter the custom loader API entirely, but the import syntax will remain mostly identical. | ||
### Loader Polyfill | ||
To follow the current the specification changes, see https://github.com/ModuleLoader/es6-module-loader/issues?labels=specification&page=1&state=open. | ||
* Implemented exactly to the 2013-12-02 Specification Draft - | ||
https://github.com/jorendorff/js-loaders/blob/e60d3651/specs/es6-modules-2013-12-02.pdf | ||
with the only exceptions as described here | ||
* Abstract functions have been combined where possible, and their associated functions | ||
commented | ||
* Declarative Module Support is entirely disabled, and an error will be thrown if | ||
the instantiate loader hook returns undefined | ||
* With this assumption, instead of Link, LinkDynamicModules is run directly | ||
* ES6 support is thus provided through the translate function of the System loader | ||
* EnsureEvaluated is removed, but may in future implement dynamic execution pending | ||
issue - https://github.com/jorendorff/js-loaders/issues/63 | ||
* Realm implementation is entirely omitted. As such, Loader.global and Loader.realm | ||
accessors will throw errors, as well as Loader.eval | ||
* Loader module table iteration currently not yet implemented | ||
### System Loader Implementation | ||
* Implemented to https://github.com/jorendorff/js-loaders/blob/master/browser-loader.js, | ||
except for Instantiate function | ||
* Instantiate function determines if ES6 module syntax is being used, if so parses with | ||
Traceur and returns a dynamic InstantiateResult for loading ES6 module syntax in ES5. | ||
* Custom loaders thus can be implemented by using this System.instantiate function as | ||
the fallback loading scenario, after other module format detections. | ||
* Traceur is loaded dynamically when module syntax is detected by a regex (with over- | ||
classification), either from require('traceur') on the server, or the | ||
'data-traceur-src' property on the current script in the browser, or if not set, | ||
'traceur.js' in the same URL path as the current script in the browser. | ||
* The `<script type="module">` tag is supported, but the `<module>` tag is not | ||
* ondemand / paths functionality currently not yet implemented | ||
To follow the current the specification changes, see the marked issues https://github.com/ModuleLoader/es6-module-loader/issues?labels=specification&page=1&state=open. | ||
## Projects using us | ||
@@ -221,2 +397,4 @@ | ||
## Release History | ||
* 0.4.0 Update to revised specification exact algorithm | ||
* 0.3.3 Traceur parser update, detection regex fixes, better error messages | ||
* 0.3.2 Use Traceur for all parsing, module tag support, syntax updates, test workflow | ||
@@ -223,0 +401,0 @@ * 0.3.1 IE9 Cross Domain fix, module x from y syntax support, data-init callback support, Traceur fixes |
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
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
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
62354
1120
399
+ Addedtraceur@0.0.8(transitive)
- Removedtraceur@0.0.6(transitive)
Updatedtraceur@0.0.8