800abf1277
We might be in the "interactive" readyState, which means that DOMContentLoaded has already fired and we'll hang.
281 lines
8.4 KiB
JavaScript
281 lines
8.4 KiB
JavaScript
import RegisterLoader from 'es-module-loader/core/register-loader.js';
|
|
import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
|
|
|
|
import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
|
|
import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
|
|
|
|
var loader;
|
|
|
|
// <script type="module"> support
|
|
var anonSources = {};
|
|
if (typeof document != 'undefined' && document.getElementsByTagName) {
|
|
var handleError = function(err) {
|
|
// dispatch an error event so that we can display in errors in browsers
|
|
// that don't yet support unhandledrejection
|
|
if (window.onunhandledrejection === undefined) {
|
|
try {
|
|
var evt = new Event('error');
|
|
} catch (_eventError) {
|
|
var evt = document.createEvent('Event');
|
|
evt.initEvent('error', true, true);
|
|
}
|
|
evt.message = err.message;
|
|
if (err.fileName) {
|
|
evt.filename = err.fileName;
|
|
evt.lineno = err.lineNumber;
|
|
evt.colno = err.columnNumber;
|
|
} else if (err.sourceURL) {
|
|
evt.filename = err.sourceURL;
|
|
evt.lineno = err.line;
|
|
evt.colno = err.column;
|
|
}
|
|
evt.error = err;
|
|
window.dispatchEvent(evt);
|
|
}
|
|
|
|
// throw so it still shows up in the console
|
|
throw err;
|
|
}
|
|
|
|
var ready = function() {
|
|
document.removeEventListener('DOMContentLoaded', ready, false );
|
|
|
|
var anonCnt = 0;
|
|
|
|
var scripts = document.getElementsByTagName('script');
|
|
for (var i = 0; i < scripts.length; i++) {
|
|
var script = scripts[i];
|
|
if (script.type == 'module' && !script.loaded) {
|
|
script.loaded = true;
|
|
if (script.src) {
|
|
loader.import(script.src).catch(handleError);
|
|
}
|
|
// anonymous modules supported via a custom naming scheme and registry
|
|
else {
|
|
var uri = './<anon' + ++anonCnt + '>.js';
|
|
if (script.id !== ""){
|
|
uri = "./" + script.id;
|
|
}
|
|
|
|
var anonName = resolveIfNotPlain(uri, baseURI);
|
|
anonSources[anonName] = script.innerHTML;
|
|
loader.import(anonName).catch(handleError);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// simple DOM ready
|
|
if (document.readyState !== 'loading')
|
|
setTimeout(ready);
|
|
else
|
|
document.addEventListener('DOMContentLoaded', ready, false);
|
|
}
|
|
|
|
function BrowserESModuleLoader(baseKey) {
|
|
if (baseKey)
|
|
this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
|
|
|
|
RegisterLoader.call(this);
|
|
|
|
var loader = this;
|
|
|
|
// ensure System.register is available
|
|
global.System = global.System || {};
|
|
if (typeof global.System.register == 'function')
|
|
var prevRegister = global.System.register;
|
|
global.System.register = function() {
|
|
loader.register.apply(loader, arguments);
|
|
if (prevRegister)
|
|
prevRegister.apply(this, arguments);
|
|
};
|
|
}
|
|
BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
|
|
|
|
// normalize is never given a relative name like "./x", that part is already handled
|
|
BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
|
|
var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
|
|
if (!resolved)
|
|
throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
|
|
|
|
return resolved;
|
|
};
|
|
|
|
function xhrFetch(url, resolve, reject) {
|
|
var xhr = new XMLHttpRequest();
|
|
var load = function(source) {
|
|
resolve(xhr.responseText);
|
|
}
|
|
var error = function() {
|
|
reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
|
|
}
|
|
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState === 4) {
|
|
// in Chrome on file:/// URLs, status is 0
|
|
if (xhr.status == 0) {
|
|
if (xhr.responseText) {
|
|
load();
|
|
}
|
|
else {
|
|
// when responseText is empty, wait for load or error event
|
|
// to inform if it is a 404 or empty file
|
|
xhr.addEventListener('error', error);
|
|
xhr.addEventListener('load', load);
|
|
}
|
|
}
|
|
else if (xhr.status === 200) {
|
|
load();
|
|
}
|
|
else {
|
|
error();
|
|
}
|
|
}
|
|
};
|
|
xhr.open("GET", url, true);
|
|
xhr.send(null);
|
|
}
|
|
|
|
var WorkerPool = function (script, size) {
|
|
var current = document.currentScript;
|
|
// IE doesn't support currentScript
|
|
if (!current) {
|
|
// Find an entry with out basename
|
|
var scripts = document.getElementsByTagName('script');
|
|
for (var i = 0; i < scripts.length; i++) {
|
|
if (scripts[i].src.indexOf("browser-es-module-loader.js") !== -1) {
|
|
current = scripts[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!current)
|
|
throw Error("Could not find own <script> element");
|
|
}
|
|
script = current.src.substr(0, current.src.lastIndexOf("/")) + "/" + script;
|
|
this._workers = new Array(size);
|
|
this._ind = 0;
|
|
this._size = size;
|
|
this._jobs = 0;
|
|
this.onmessage = undefined;
|
|
this._stopTimeout = undefined;
|
|
for (var i = 0; i < size; i++) {
|
|
var wrkr = new Worker(script);
|
|
wrkr._count = 0;
|
|
wrkr._ind = i;
|
|
wrkr.onmessage = this._onmessage.bind(this, wrkr);
|
|
wrkr.onerror = this._onerror.bind(this);
|
|
this._workers[i] = wrkr;
|
|
}
|
|
|
|
this._checkJobs();
|
|
};
|
|
WorkerPool.prototype = {
|
|
postMessage: function (msg) {
|
|
if (this._stopTimeout !== undefined) {
|
|
clearTimeout(this._stopTimeout);
|
|
this._stopTimeout = undefined;
|
|
}
|
|
var wrkr = this._workers[this._ind % this._size];
|
|
wrkr._count++;
|
|
this._jobs++;
|
|
wrkr.postMessage(msg);
|
|
this._ind++;
|
|
},
|
|
|
|
_onmessage: function (wrkr, evt) {
|
|
wrkr._count--;
|
|
this._jobs--;
|
|
this.onmessage(evt, wrkr);
|
|
this._checkJobs();
|
|
},
|
|
|
|
_onerror: function(err) {
|
|
try {
|
|
var evt = new Event('error');
|
|
} catch (_eventError) {
|
|
var evt = document.createEvent('Event');
|
|
evt.initEvent('error', true, true);
|
|
}
|
|
evt.message = err.message;
|
|
evt.filename = err.filename;
|
|
evt.lineno = err.lineno;
|
|
evt.colno = err.colno;
|
|
evt.error = err.error;
|
|
window.dispatchEvent(evt);
|
|
},
|
|
|
|
_checkJobs: function () {
|
|
if (this._jobs === 0 && this._stopTimeout === undefined) {
|
|
// wait for 2s of inactivity before stopping (that should be enough for local loading)
|
|
this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
|
|
}
|
|
},
|
|
|
|
_stop: function () {
|
|
this._workers.forEach(function(wrkr) {
|
|
wrkr.terminate();
|
|
});
|
|
}
|
|
};
|
|
|
|
var promiseMap = new Map();
|
|
var babelWorker = new WorkerPool('babel-worker.js', 3);
|
|
babelWorker.onmessage = function (evt) {
|
|
var promFuncs = promiseMap.get(evt.data.key);
|
|
promFuncs.resolve(evt.data);
|
|
promiseMap.delete(evt.data.key);
|
|
};
|
|
|
|
// instantiate just needs to run System.register
|
|
// so we fetch the source, convert into the Babel System module format, then evaluate it
|
|
BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
|
|
var loader = this;
|
|
|
|
// load as ES with Babel converting into System.register
|
|
return new Promise(function(resolve, reject) {
|
|
// anonymous module
|
|
if (anonSources[key]) {
|
|
resolve(anonSources[key])
|
|
anonSources[key] = undefined;
|
|
}
|
|
// otherwise we fetch
|
|
else {
|
|
xhrFetch(key, resolve, reject);
|
|
}
|
|
})
|
|
.then(function(source) {
|
|
// check our cache first
|
|
var cacheEntry = localStorage.getItem(key);
|
|
if (cacheEntry) {
|
|
cacheEntry = JSON.parse(cacheEntry);
|
|
// TODO: store a hash instead
|
|
if (cacheEntry.source === source) {
|
|
return Promise.resolve({key: key, code: cacheEntry.code, source: cacheEntry.source});
|
|
}
|
|
}
|
|
return new Promise(function (resolve, reject) {
|
|
promiseMap.set(key, {resolve: resolve, reject: reject});
|
|
babelWorker.postMessage({key: key, source: source});
|
|
});
|
|
}).then(function (data) {
|
|
// evaluate without require, exports and module variables
|
|
// we leave module in for now to allow module.require access
|
|
try {
|
|
var cacheEntry = JSON.stringify({source: data.source, code: data.code});
|
|
localStorage.setItem(key, cacheEntry);
|
|
} catch (e) {
|
|
if (window.console) {
|
|
window.console.warn('Unable to cache transpiled version of ' + key + ': ' + e);
|
|
}
|
|
}
|
|
(0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
|
|
processAnonRegister();
|
|
});
|
|
};
|
|
|
|
// create a default loader instance in the browser
|
|
if (isBrowser)
|
|
loader = new BrowserESModuleLoader();
|
|
|
|
export default BrowserESModuleLoader;
|