steal-es6-module-loader
Version:
An ES6 Module Loader shim
1,133 lines (956 loc) • 34.5 kB
JavaScript
/*
*********************************************************************************************
Dynamic Module Loader Polyfill
- Implemented exactly to the former 2014-08-24 ES6 Specification Draft Rev 27, Section 15
http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts#august_24_2014_draft_rev_27
- Functions are commented with their spec numbers, with spec differences commented.
- Spec bugs are commented in this code with links.
- Abstract functions have been combined where possible, and their associated functions
commented.
- Realm implementation is entirely omitted.
*********************************************************************************************
*/
// Some Helpers
// logs a linkset snapshot for debugging
/* function snapshot(loader) {
console.log('---Snapshot---');
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 += '{' + logloads(load.linkSets[j].loads) + '} ';
}
console.log(linkSetLog);
}
console.log('');
}
function logloads(loads) {
var log = '';
for (var k = 0; k < loads.length; k++)
log += loads[k].name + (k != loads.length - 1 ? ' ' : '');
return log;
} */
/* function checkInvariants() {
// see https://bugs.ecmascript.org/show_bug.cgi?id=2603#c1
var loads = System._loader.loads;
var linkSets = [];
for (var i = 0; i < loads.length; i++) {
var load = loads[i];
console.assert(load.status == 'loading' || load.status == 'loaded', 'Each load is loading or loaded');
for (var j = 0; j < load.linkSets.length; j++) {
var linkSet = load.linkSets[j];
for (var k = 0; k < linkSet.loads.length; k++)
console.assert(loads.indexOf(linkSet.loads[k]) != -1, 'linkSet loads are a subset of loader loads');
if (linkSets.indexOf(linkSet) == -1)
linkSets.push(linkSet);
}
}
for (var i = 0; i < loads.length; i++) {
var load = loads[i];
for (var j = 0; j < linkSets.length; j++) {
var linkSet = linkSets[j];
if (linkSet.loads.indexOf(load) != -1)
console.assert(load.linkSets.indexOf(linkSet) != -1, 'linkSet contains load -> load contains linkSet');
if (load.linkSets.indexOf(linkSet) != -1)
console.assert(linkSet.loads.indexOf(load) != -1, 'load contains linkSet -> linkSet contains load');
}
}
for (var i = 0; i < linkSets.length; i++) {
var linkSet = linkSets[i];
for (var j = 0; j < linkSet.loads.length; j++) {
var load = linkSet.loads[j];
for (var k = 0; k < load.dependencies.length; k++) {
var depName = load.dependencies[k].value;
var depLoad;
for (var l = 0; l < loads.length; l++) {
if (loads[l].name != depName)
continue;
depLoad = loads[l];
break;
}
// loading records are allowed not to have their dependencies yet
// if (load.status != 'loading')
// console.assert(depLoad, 'depLoad found');
// console.assert(linkSet.loads.indexOf(depLoad) != -1, 'linkset contains all dependencies');
}
}
}
} */
(function() {
var Promise = __global.Promise || require('when/es6-shim/Promise');
var console;
if (__global.console) {
console = __global.console;
console.assert = console.assert || function() {};
} else {
console = { assert: function() {} };
}
// IE8 support
var indexOf = Array.prototype.indexOf || function(item) {
for (var i = 0, thisLen = this.length; i < thisLen; i++) {
if (this[i] === item) {
return i;
}
}
return -1;
};
var defineProperty = $__Object$defineProperty;
// 15.2.3 - Runtime Semantics: Loader State
// 15.2.3.11
function createLoaderLoad(object) {
return {
// modules is an object for ES5 implementation
modules: {},
loads: [],
loaderObj: object
};
}
// 15.2.3.2 Load Records and LoadRequest Objects
// 15.2.3.2.1
function createLoad(name) {
return {
status: 'loading',
name: name,
linkSets: [],
dependencies: [],
metadata: {}
};
}
// 15.2.3.2.2 createLoadRequestObject, absorbed into calling functions
// 15.2.4
// 15.2.4.1
function loadModule(loader, name, options) {
return new Promise(asyncStartLoadPartwayThrough({
step: options.address ? 'fetch' : 'locate',
loader: loader,
moduleName: name,
// allow metadata for import https://bugs.ecmascript.org/show_bug.cgi?id=3091
moduleMetadata: options && options.metadata || {},
moduleSource: options.source,
moduleAddress: options.address
}));
}
// 15.2.4.2
function requestLoad(loader, request, refererName, refererAddress) {
// 15.2.4.2.1 CallNormalize
return new Promise(function(resolve, reject) {
resolve(loader.loaderObj.normalize(request, refererName, refererAddress));
})
// 15.2.4.2.2 GetOrCreateLoad
.then(function(name) {
var load;
if (loader.modules[name]) {
load = createLoad(name);
load.status = 'linked';
// https://bugs.ecmascript.org/show_bug.cgi?id=2795
load.module = loader.modules[name];
return load;
}
for (var i = 0, l = loader.loads.length; i < l; i++) {
load = loader.loads[i];
if (load.name != name)
continue;
console.assert(load.status == 'loading' || load.status == 'loaded', 'loading or loaded');
return load;
}
load = createLoad(name);
loader.loads.push(load);
proceedToLocate(loader, load);
return load;
});
}
// 15.2.4.3
function proceedToLocate(loader, load) {
proceedToFetch(loader, load,
Promise.resolve()
// 15.2.4.3.1 CallLocate
.then(function() {
return loader.loaderObj.locate({ name: load.name, metadata: load.metadata });
})
);
}
// 15.2.4.4
function proceedToFetch(loader, load, p) {
proceedToTranslate(loader, load,
p
// 15.2.4.4.1 CallFetch
.then(function(address) {
// adjusted, see https://bugs.ecmascript.org/show_bug.cgi?id=2602
if (load.status != 'loading')
return;
load.address = address;
return loader.loaderObj.fetch({ name: load.name, metadata: load.metadata, address: address });
})
);
}
var anonCnt = 0;
// 15.2.4.5
function proceedToTranslate(loader, load, p) {
p
// 15.2.4.5.1 CallTranslate
.then(function(source) {
if (load.status != 'loading')
return;
return Promise.resolve(loader.loaderObj.translate({ name: load.name, metadata: load.metadata, address: load.address, source: source }))
// 15.2.4.5.2 CallInstantiate
.then(function(source) {
if(load.status != 'loading') {
return;
}
load.source = source;
return loader.loaderObj.instantiate({ name: load.name, metadata: load.metadata, address: load.address, source: source });
})
// 15.2.4.5.3 InstantiateSucceeded
.then(function(instantiateResult) {
if(load.status != 'loading') {
return;
}
if (instantiateResult === undefined) {
load.address = load.address || '<Anonymous Module ' + ++anonCnt + '>';
// instead of load.kind, use load.isDeclarative
load.isDeclarative = true;
return loader.loaderObj.transpile(load)
.then(function(transpiled) {
// Hijack System.register to set declare function
var curSystem = __global.System;
var curRegister = curSystem.register;
curSystem.register = function(name, deps, declare) {
if (typeof name != 'string') {
declare = deps;
deps = name;
}
// store the registered declaration as load.declare
// store the deps as load.deps
load.declare = declare;
load.depsList = deps;
}
__eval(transpiled, __global, load);
curSystem.register = curRegister;
});
}
else if (typeof instantiateResult == 'object') {
load.depsList = instantiateResult.deps || [];
load.execute = instantiateResult.execute;
load.isDeclarative = false;
}
else
throw TypeError('Invalid instantiate return value');
})
// 15.2.4.6 ProcessLoadDependencies
.then(function() {
if(load.status != 'loading') {
return;
}
load.dependencies = [];
var depsList = load.depsList;
var loadPromises = [];
for (var i = 0, l = depsList.length; i < l; i++) (function(request, index) {
loadPromises.push(
requestLoad(loader, request, load.name, load.address)
// 15.2.4.6.1 AddDependencyLoad (load is parentLoad)
.then(function(depLoad) {
// adjusted from spec to maintain dependency order
// this is due to the System.register internal implementation needs
load.dependencies[index] = {
key: request,
value: depLoad.name
};
if (depLoad.status != 'linked') {
var linkSets = load.linkSets.concat([]);
for (var i = 0, l = linkSets.length; i < l; i++)
addLoadToLinkSet(linkSets[i], depLoad);
}
// console.log('AddDependencyLoad ' + depLoad.name + ' for ' + load.name);
// snapshot(loader);
})
);
})(depsList[i], i);
return Promise.all(loadPromises);
})
// 15.2.4.6.2 LoadSucceeded
.then(function() {
// console.log('LoadSucceeded ' + load.name);
// snapshot(loader);
if(load.status != 'loading') {
return;
}
console.assert(load.status == 'loading', 'is loading');
load.status = 'loaded';
var linkSets = load.linkSets.concat([]);
for (var i = 0, l = linkSets.length; i < l; i++)
updateLinkSetOnLoad(linkSets[i], load);
});
})
// 15.2.4.5.4 LoadFailed
['catch'](function(exc) {
load.status = 'failed';
load.exception = exc;
var linkSets = load.linkSets.concat([]);
for (var i = 0, l = linkSets.length; i < l; i++) {
linkSetFailed(linkSets[i], load, exc);
}
console.assert(load.linkSets.length == 0, 'linkSets not removed');
});
}
// 15.2.4.7 PromiseOfStartLoadPartwayThrough absorbed into calling functions
// 15.2.4.7.1
function asyncStartLoadPartwayThrough(stepState) {
return function(resolve, reject) {
var loader = stepState.loader;
var name = stepState.moduleName;
var step = stepState.step;
var importingModuleName = stepState.moduleMetadata.importingModuleName;
if (loader.modules[name])
throw new TypeError('"' + name + '" already exists in the module table');
// adjusted to pick up existing loads
var existingLoad, firstLinkSet;
for (var i = 0, l = loader.loads.length; i < l; i++) {
if (loader.loads[i].name == name) {
existingLoad = loader.loads[i];
if(step == 'translate' && !existingLoad.source) {
existingLoad.address = stepState.moduleAddress;
proceedToTranslate(loader, existingLoad, Promise.resolve(stepState.moduleSource));
}
// If the module importing this is part of the same linkSet, create
// a new one for this import.
firstLinkSet = existingLoad.linkSets[0];
if(importingModuleName && firstLinkSet.loads[importingModuleName]) {
continue;
}
return firstLinkSet.done.then(function() {
resolve(existingLoad);
});
}
}
var load;
if(existingLoad) {
load = existingLoad;
} else {
load = createLoad(name);
load.metadata = stepState.moduleMetadata;
}
var linkSet = createLinkSet(loader, load);
if(!existingLoad) {
loader.loads.push(load);
}
resolve(linkSet.done);
if (step == 'locate')
proceedToLocate(loader, load);
else if (step == 'fetch')
proceedToFetch(loader, load, Promise.resolve(stepState.moduleAddress));
else {
console.assert(step == 'translate', 'translate step');
load.address = stepState.moduleAddress;
proceedToTranslate(loader, load, Promise.resolve(stepState.moduleSource));
}
}
}
// Declarative linking functions run through alternative implementation:
// 15.2.5.1.1 CreateModuleLinkageRecord not implemented
// 15.2.5.1.2 LookupExport not implemented
// 15.2.5.1.3 LookupModuleDependency not implemented
// 15.2.5.2.1
function createLinkSet(loader, startingLoad) {
var linkSet = {
loader: loader,
loads: [],
startingLoad: startingLoad, // added see spec bug https://bugs.ecmascript.org/show_bug.cgi?id=2995
loadingCount: 0
};
linkSet.done = new Promise(function(resolve, reject) {
linkSet.resolve = resolve;
linkSet.reject = reject;
});
addLoadToLinkSet(linkSet, startingLoad);
return linkSet;
}
// 15.2.5.2.2
function addLoadToLinkSet(linkSet, load) {
console.assert(load.status == 'loading' || load.status == 'loaded', 'loading or loaded on link set');
for (var i = 0, l = linkSet.loads.length; i < l; i++)
if (linkSet.loads[i] == load)
return;
linkSet.loads.push(load);
linkSet.loads[load.name] = true;
load.linkSets.push(linkSet);
// adjustment, see https://bugs.ecmascript.org/show_bug.cgi?id=2603
if (load.status != 'loaded') {
linkSet.loadingCount++;
}
var loader = linkSet.loader;
for (var i = 0, l = load.dependencies.length; i < l; i++) {
var name = load.dependencies[i].value;
if (loader.modules[name])
continue;
for (var j = 0, d = loader.loads.length; j < d; j++) {
if (loader.loads[j].name != name)
continue;
addLoadToLinkSet(linkSet, loader.loads[j]);
break;
}
}
// console.log('add to linkset ' + load.name);
// snapshot(linkSet.loader);
}
// linking errors can be generic or load-specific
// this is necessary for debugging info
function doLink(linkSet) {
var error = false;
try {
link(linkSet, function(load, exc) {
linkSetFailed(linkSet, load, exc);
error = true;
});
}
catch(e) {
linkSetFailed(linkSet, null, e);
error = true;
}
return error;
}
// 15.2.5.2.3
function updateLinkSetOnLoad(linkSet, load) {
// console.log('update linkset on load ' + load.name);
// snapshot(linkSet.loader);
console.assert(load.status == 'loaded' || load.status == 'linked', 'loaded or linked');
linkSet.loadingCount--;
if (linkSet.loadingCount > 0)
return;
// adjusted for spec bug https://bugs.ecmascript.org/show_bug.cgi?id=2995
var startingLoad = linkSet.startingLoad;
// non-executing link variation for loader tracing
// on the server. Not in spec.
/***/
if (linkSet.loader.loaderObj.execute === false) {
var loads = [].concat(linkSet.loads);
for (var i = 0, l = loads.length; i < l; i++) {
var load = loads[i];
load.module = !load.isDeclarative ? {
module: _newModule({})
} : {
name: load.name,
module: _newModule({}),
evaluated: true
};
load.status = 'linked';
finishLoad(linkSet.loader, load);
}
return linkSet.resolve(startingLoad);
}
/***/
var abrupt = doLink(linkSet);
if (abrupt)
return;
console.assert(linkSet.loads.length == 0, 'loads cleared');
linkSet.resolve(startingLoad);
}
// 15.2.5.2.4
function linkSetFailed(linkSet, load, exc) {
var loader = linkSet.loader;
if (linkSet.loads[0].name != load.name)
exc = addToError(exc, 'Error loading "' + load.name + '" from "' + linkSet.loads[0].name + '" at ' + (linkSet.loads[0].address || '<unknown>') + '\n');
exc = addToError(exc, 'Error loading "' + load.name + '" at ' + (load.address || '<unknown>') + '\n');
var loads = linkSet.loads.concat([]);
for (var i = 0, l = loads.length; i < l; i++) {
var load = loads[i];
// store all failed load records
loader.loaderObj.failed = loader.loaderObj.failed || [];
if (indexOf.call(loader.loaderObj.failed, load) == -1)
loader.loaderObj.failed.push(load);
var linkIndex = indexOf.call(load.linkSets, linkSet);
console.assert(linkIndex != -1, 'link not present');
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);
}
// 15.2.5.2.5
function finishLoad(loader, load) {
// add to global trace if tracing
if (loader.loaderObj.trace) {
if (!loader.loaderObj.loads)
loader.loaderObj.loads = {};
var depMap = {};
load.dependencies.forEach(function(dep) {
depMap[dep.key] = dep.value;
});
loader.loaderObj.loads[load.name] = {
name: load.name,
deps: load.dependencies.map(function(dep){ return dep.key }),
depMap: depMap,
address: load.address,
metadata: load.metadata,
source: load.source,
kind: load.isDeclarative ? 'declarative' : 'dynamic'
};
}
// if not anonymous, add to the module table
if (load.name) {
console.assert(!loader.modules[load.name], 'load not in module table');
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);
if (loadIndex != -1)
load.linkSets[i].loads.splice(loadIndex, 1);
}
load.linkSets.splice(0, load.linkSets.length);
}
// 15.2.5.3 Module Linking Groups
// 15.2.5.3.2 BuildLinkageGroups alternative implementation
// Adjustments (also see https://bugs.ecmascript.org/show_bug.cgi?id=2755)
// 1. groups is an already-interleaved array of group kinds
// 2. load.groupIndex is set when this function runs
// 3. load.groupIndex is the interleaved index ie 0 declarative, 1 dynamic, 2 declarative, ... (or starting with dynamic)
function buildLinkageGroups(load, loads, groups) {
groups[load.groupIndex] = groups[load.groupIndex] || [];
// if the load already has a group index and its in its group, its already been done
// this logic naturally handles cycles
if (indexOf.call(groups[load.groupIndex], load) != -1)
return;
// now add it to the group to indicate its been seen
groups[load.groupIndex].push(load);
for (var i = 0, l = loads.length; i < l; i++) {
var loadDep = loads[i];
// dependencies not found are already linked
for (var j = 0; j < load.dependencies.length; j++) {
if (loadDep.name == load.dependencies[j].value) {
// by definition all loads in linkset are loaded, not linked
console.assert(loadDep.status == 'loaded', 'Load in linkSet not loaded!');
// if it is a group transition, the index of the dependency has gone up
// otherwise it is the same as the parent
var loadDepGroupIndex = load.groupIndex + (loadDep.isDeclarative != load.isDeclarative);
// the group index of an entry is always the maximum
if (loadDep.groupIndex === undefined || loadDep.groupIndex < loadDepGroupIndex) {
// if already in a group, remove from the old group
if (loadDep.groupIndex !== undefined) {
groups[loadDep.groupIndex].splice(indexOf.call(groups[loadDep.groupIndex], loadDep), 1);
// if the old group is empty, then we have a mixed depndency cycle
if (groups[loadDep.groupIndex].length == 0)
throw new TypeError("Mixed dependency cycle detected");
}
loadDep.groupIndex = loadDepGroupIndex;
}
buildLinkageGroups(loadDep, loads, groups);
}
}
}
}
function doDynamicExecute(linkSet, load, linkError) {
try {
var module = load.execute();
}
catch(e) {
linkError(load, e);
return;
}
if (!module || !(module instanceof Module))
linkError(load, new TypeError('Execution must define a Module instance'));
else
return module;
}
// 15.2.5.4
function link(linkSet, linkError) {
var loader = linkSet.loader;
if (!linkSet.loads.length)
return;
// console.log('linking {' + logloads(linkSet.loads) + '}');
// snapshot(loader);
// 15.2.5.3.1 LinkageGroups alternative implementation
// build all the groups
// because the first load represents the top of the tree
// for a given linkset, we can work down from there
var groups = [];
var startingLoad = linkSet.loads[0];
startingLoad.groupIndex = 0;
buildLinkageGroups(startingLoad, linkSet.loads, groups);
// determine the kind of the bottom group
var curGroupDeclarative = startingLoad.isDeclarative == groups.length % 2;
// run through the groups from bottom to top
for (var i = groups.length - 1; i >= 0; i--) {
var group = groups[i];
for (var j = 0; j < group.length; j++) {
var load = group[j];
// 15.2.5.5 LinkDeclarativeModules adjusted
if (curGroupDeclarative) {
linkDeclarativeModule(load, linkSet.loads, loader);
}
// 15.2.5.6 LinkDynamicModules adjusted
else {
var module = doDynamicExecute(linkSet, load, linkError);
if (!module)
return;
load.module = {
name: load.name,
module: module
};
load.status = 'linked';
}
finishLoad(loader, load);
}
// alternative current kind for next loop
curGroupDeclarative = !curGroupDeclarative;
}
}
// custom module records for binding graph
// store linking module records in a separate table
function getOrCreateModuleRecord(name, loader) {
var moduleRecords = loader.moduleRecords;
return moduleRecords[name] || (moduleRecords[name] = {
name: name,
dependencies: [],
module: new Module(), // start from an empty module and extend
importers: []
});
}
// custom declarative linking function
function linkDeclarativeModule(load, loads, loader) {
if (load.module)
return;
var module = load.module = getOrCreateModuleRecord(load.name, loader);
var moduleObj = load.module.module;
var registryEntry = load.declare.call(__global, function(name, value) {
// NB This should be an Object.defineProperty, but that is very slow.
// By disaling this module write-protection we gain performance.
// It could be useful to allow an option to enable or disable this.
module.locked = true;
if(typeof name === 'object') {
for(var p in name) {
moduleObj[p] = name[p];
}
} else {
moduleObj[name] = value;
}
for (var i = 0, l = module.importers.length; i < l; i++) {
var importerModule = module.importers[i];
if (!importerModule.locked) {
var importerIndex = indexOf.call(importerModule.dependencies, module);
importerModule.setters[importerIndex](moduleObj);
}
}
module.locked = false;
return value;
});
// setup our setters and execution function
module.setters = registryEntry.setters;
module.execute = registryEntry.execute;
// now link all the module dependencies
// amending the depMap as we go
for (var i = 0, l = load.dependencies.length; i < l; i++) {
var depName = load.dependencies[i].value;
var depModule = loader.modules[depName];
// if dependency not already in the module registry
// then try and link it now
if (!depModule) {
// get the dependency load record
for (var j = 0; j < loads.length; j++) {
if (loads[j].name != depName)
continue;
// only link if already not already started linking (stops at circular / dynamic)
if (!loads[j].module) {
linkDeclarativeModule(loads[j], loads, loader);
depModule = loads[j].module;
}
// if circular, create the module record
else {
depModule = getOrCreateModuleRecord(depName, loader);
}
}
}
// only declarative modules have dynamic bindings
if (depModule.importers) {
module.dependencies.push(depModule);
depModule.importers.push(module);
}
else {
// track dynamic records as null module records as already linked
module.dependencies.push(null);
}
// run the setter for this dependency
if (module.setters[i])
module.setters[i](depModule.module);
}
load.status = 'linked';
}
// 15.2.5.5.1 LinkImports not implemented
// 15.2.5.7 ResolveExportEntries not implemented
// 15.2.5.8 ResolveExports not implemented
// 15.2.5.9 ResolveExport not implemented
// 15.2.5.10 ResolveImportEntries not implemented
// 15.2.6.1
function evaluateLoadedModule(loader, load) {
console.assert(load.status == 'linked', 'is linked ' + load.name);
doEnsureEvaluated(load.module, [], loader);
return load.module.module;
}
/*
* Module Object non-exotic for ES5:
*
* module.module bound module object
* module.execute execution function for module
* module.dependencies list of module objects for dependencies
* See getOrCreateModuleRecord for all properties
*
*/
function doExecute(module) {
try {
module.execute.call(__global);
}
catch(e) {
return e;
}
}
// propogate execution errors
// see https://bugs.ecmascript.org/show_bug.cgi?id=2993
function doEnsureEvaluated(module, seen, loader) {
var err = ensureEvaluated(module, seen, loader);
if (err)
throw err;
}
// 15.2.6.2 EnsureEvaluated adjusted
function ensureEvaluated(module, seen, loader) {
if (module.evaluated || !module.dependencies)
return;
seen.push(module);
var deps = module.dependencies;
var err;
for (var i = 0, l = deps.length; i < l; i++) {
var dep = deps[i];
// dynamic dependencies are empty in module.dependencies
// as they are already linked
if (!dep)
continue;
if (indexOf.call(seen, dep) == -1) {
err = ensureEvaluated(dep, seen, loader);
// stop on error, see https://bugs.ecmascript.org/show_bug.cgi?id=2996
if (err) {
err = addToError(err, 'Error evaluating ' + dep.name + '\n');
return err;
}
}
}
if (module.failed)
return new Error('Module failed execution.');
if (module.evaluated)
return;
module.evaluated = true;
err = doExecute(module);
if (err) {
module.failed = true;
}
else if (Object.preventExtensions) {
// spec variation
// we don't create a new module here because it was created and ammended
// we just disable further extensions instead
Object.preventExtensions(module.module);
}
module.execute = undefined;
return err;
}
function addToError(err, msg) {
if (err instanceof Error)
err.message = msg + err.message;
else
err = msg + err;
return err;
}
// 26.3 Loader
// 26.3.1.1
function Loader(options) {
if (typeof options != 'object')
throw new TypeError('Options must be an object');
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;
this._loader = {
loaderObj: this,
loads: [],
modules: {},
importPromises: {},
moduleRecords: {}
};
// 26.3.3.6
defineProperty(this, 'global', {
get: function() {
return __global;
}
});
// 26.3.3.13 realm not implemented
}
function Module() {}
// importPromises adds ability to import a module twice without error - https://bugs.ecmascript.org/show_bug.cgi?id=2601
function createImportPromise(loader, name, promise) {
var importPromises = loader._loader.importPromises;
return importPromises[name] = promise.then(function(m) {
importPromises[name] = undefined;
return m;
}, function(e) {
importPromises[name] = undefined;
throw e;
});
}
Loader.prototype = {
// 26.3.3.1
constructor: Loader,
// 26.3.3.2
define: function(name, source, options) {
// check if already defined
if (this._loader.importPromises[name])
throw new TypeError('Module is already loading.');
return createImportPromise(this, name, new Promise(asyncStartLoadPartwayThrough({
step: 'translate',
loader: this._loader,
moduleName: name,
moduleMetadata: options && options.metadata || {},
moduleSource: source,
moduleAddress: options && options.address
})));
},
// 26.3.3.3
'delete': function(name) {
var loader = this._loader;
delete loader.importPromises[name];
delete loader.moduleRecords[name];
return loader.modules[name] ? delete loader.modules[name] : false;
},
// 26.3.3.4 entries not implemented
// 26.3.3.5
get: function(key) {
if (!this._loader.modules[key])
return;
doEnsureEvaluated(this._loader.modules[key], [], this);
return this._loader.modules[key].module;
},
// 26.3.3.7
has: function(name) {
return !!this._loader.modules[name];
},
// 26.3.3.8
'import': function(name, options) {
// run normalize first
var loaderObj = this;
// added, see https://bugs.ecmascript.org/show_bug.cgi?id=2659
return Promise.resolve(loaderObj.normalize(name, options && options.name, options && options.address))
.then(function(name) {
var loader = loaderObj._loader;
if (loader.modules[name]) {
doEnsureEvaluated(loader.modules[name], [], loader._loader);
return loader.modules[name].module;
}
return loader.importPromises[name] || createImportPromise(loaderObj, name,
loadModule(loader, name, options || {})
.then(function(load) {
delete loader.importPromises[name];
return evaluateLoadedModule(loader, load);
}, function(err){
if(loaderObj.defined) {
loaderObj.defined[name] = undefined;
}
return Promise.reject(err);
}));
});
},
// 26.3.3.9 keys not implemented
// 26.3.3.10
load: function(name, options) {
if (this._loader.modules[name]) {
doEnsureEvaluated(this._loader.modules[name], [], this._loader);
return Promise.resolve(this._loader.modules[name].module);
}
return this._loader.importPromises[name] || createImportPromise(this, name, loadModule(this._loader, name, {}));
},
// 26.3.3.11
module: function(source, options) {
var load = createLoad();
load.address = options && options.address;
var linkSet = createLinkSet(this._loader, load);
var sourcePromise = Promise.resolve(source);
var loader = this._loader;
var p = linkSet.done.then(function() {
return evaluateLoadedModule(loader, load);
});
proceedToTranslate(loader, load, sourcePromise);
return p;
},
// 26.3.3.12
newModule: function (obj) {
if (typeof obj != 'object')
throw new TypeError('Expected object');
// we do this to be able to tell if a module is a module privately in ES5
// by doing m instanceof Module
var m = new Module();
var pNames;
if (Object.getOwnPropertyNames && obj != null) {
pNames = Object.getOwnPropertyNames(obj);
}
else {
pNames = [];
for (var key in obj)
pNames.push(key);
}
for (var i = 0; i < pNames.length; i++) (function(key) {
defineProperty(m, key, {
configurable: false,
enumerable: true,
get: function () {
return obj[key];
}
});
})(pNames[i]);
if (Object.preventExtensions)
Object.preventExtensions(m);
return m;
},
// 26.3.3.14
set: function(name, module) {
if (!(module instanceof Module))
throw new TypeError('Loader.set(' + name + ', module) must be a module');
this._loader.modules[name] = {
module: module
};
},
// 26.3.3.15 values not implemented
// 26.3.3.16 @@iterator not implemented
// 26.3.3.17 @@toStringTag not implemented
// 26.3.3.18.1
normalize: function(name, referrerName, referrerAddress) {
return name;
},
// 26.3.3.18.2
locate: function(load) {
return load.name;
},
// 26.3.3.18.3
fetch: function(load) {
throw new TypeError('Fetch not implemented');
},
// 26.3.3.18.4
translate: function(load) {
return load.source;
},
// 26.3.3.18.5
instantiate: function(load) {
}
};
var _newModule = Loader.prototype.newModule;
if (typeof exports === 'object')
module.exports = Loader;
__global.Reflect = __global.Reflect || {};
__global.Reflect.Loader = __global.Reflect.Loader || Loader;
__global.Reflect.global = __global.Reflect.global || __global;
__global.LoaderPolyfill = Loader;
})();