Better currentScript fallback

The previous heuristic didn't work under all circumstances, so try
something more robust.
This commit is contained in:
Pierre Ossman 2018-07-13 14:20:52 +02:00
parent ae2e1ff7bd
commit d131633471
3 changed files with 1214 additions and 1141 deletions

View File

@ -20,6 +20,8 @@ function createSymbol (name) {
return hasSymbol ? Symbol() : '@@' + name; return hasSymbol ? Symbol() : '@@' + name;
} }
var toStringTag = hasSymbol && Symbol.toStringTag;
@ -93,7 +95,7 @@ function LoaderError__Check_error_message_for_loader_stack (childErr, newMessage
return err; return err;
} }
var resolvedPromise = Promise.resolve(); var resolvedPromise$1 = Promise.resolve();
/* /*
* Simple Array values shim * Simple Array values shim
@ -140,7 +142,9 @@ function Loader () {
Loader.prototype.constructor = Loader; Loader.prototype.constructor = Loader;
function ensureInstantiated (module) { function ensureInstantiated (module) {
if (!(module instanceof ModuleNamespace)) if (module === undefined)
return;
if (module instanceof ModuleNamespace === false && module[toStringTag] !== 'module')
throw new TypeError('Module instantiation did not return a valid namespace object.'); throw new TypeError('Module instantiation did not return a valid namespace object.');
return module; return module;
} }
@ -151,7 +155,7 @@ Loader.prototype.import = function (key, parent) {
throw new TypeError('Loader import method must be passed a module key string'); throw new TypeError('Loader import method must be passed a module key string');
// custom resolveInstantiate combined hook for better perf // custom resolveInstantiate combined hook for better perf
var loader = this; var loader = this;
return resolvedPromise return resolvedPromise$1
.then(function () { .then(function () {
return loader[RESOLVE_INSTANTIATE](key, parent); return loader[RESOLVE_INSTANTIATE](key, parent);
}) })
@ -193,7 +197,7 @@ function ensureResolution (resolvedKey) {
Loader.prototype.resolve = function (key, parent) { Loader.prototype.resolve = function (key, parent) {
var loader = this; var loader = this;
return resolvedPromise return resolvedPromise$1
.then(function() { .then(function() {
return loader[RESOLVE](key, parent); return loader[RESOLVE](key, parent);
}) })
@ -261,7 +265,7 @@ Registry.prototype.get = function (key) {
}; };
// 4.4.7 // 4.4.7
Registry.prototype.set = function (key, namespace) { Registry.prototype.set = function (key, namespace) {
if (!(namespace instanceof ModuleNamespace)) if (!(namespace instanceof ModuleNamespace || namespace[toStringTag] === 'module'))
throw new Error('Registry must be set with an instance of Module Namespace'); throw new Error('Registry must be set with an instance of Module Namespace');
this[REGISTRY][key] = namespace; this[REGISTRY][key] = namespace;
return this; return this;
@ -318,8 +322,8 @@ function ModuleNamespace (baseObject/*, evaluate*/) {
// 8.4.2 // 8.4.2
ModuleNamespace.prototype = Object.create(null); ModuleNamespace.prototype = Object.create(null);
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) if (toStringTag)
Object.defineProperty(ModuleNamespace.prototype, Symbol.toStringTag, { Object.defineProperty(ModuleNamespace.prototype, toStringTag, {
value: 'Module' value: 'Module'
}); });
@ -366,7 +370,9 @@ Module.evaluate = function (ns) {
function throwResolveError (relUrl, parentUrl) { function throwResolveError (relUrl, parentUrl) {
throw new RangeError('Unable to resolve "' + relUrl + '" to ' + parentUrl); throw new RangeError('Unable to resolve "' + relUrl + '" to ' + parentUrl);
} }
var backslashRegEx = /\\/g;
function resolveIfNotPlain (relUrl, parentUrl) { function resolveIfNotPlain (relUrl, parentUrl) {
if (relUrl[0] === ' ' || relUrl[relUrl.length - 1] === ' ')
relUrl = relUrl.trim(); relUrl = relUrl.trim();
var parentProtocol = parentUrl && parentUrl.substr(0, parentUrl.indexOf(':') + 1); var parentProtocol = parentUrl && parentUrl.substr(0, parentUrl.indexOf(':') + 1);
@ -377,12 +383,16 @@ function resolveIfNotPlain (relUrl, parentUrl) {
if (firstChar === '/' && secondChar === '/') { if (firstChar === '/' && secondChar === '/') {
if (!parentProtocol) if (!parentProtocol)
throwResolveError(relUrl, parentUrl); throwResolveError(relUrl, parentUrl);
if (relUrl.indexOf('\\') !== -1)
relUrl = relUrl.replace(backslashRegEx, '/');
return parentProtocol + relUrl; return parentProtocol + relUrl;
} }
// relative-url // relative-url
else if (firstChar === '.' && (secondChar === '/' || secondChar === '.' && (relUrl[2] === '/' || relUrl.length === 2 && (relUrl += '/')) || else if (firstChar === '.' && (secondChar === '/' || secondChar === '.' && (relUrl[2] === '/' || relUrl.length === 2 && (relUrl += '/')) ||
relUrl.length === 1 && (relUrl += '/')) || relUrl.length === 1 && (relUrl += '/')) ||
firstChar === '/') { firstChar === '/') {
if (relUrl.indexOf('\\') !== -1)
relUrl = relUrl.replace(backslashRegEx, '/');
var parentIsPlain = !parentProtocol || parentUrl[parentProtocol.length] !== '/'; var parentIsPlain = !parentProtocol || parentUrl[parentProtocol.length] !== '/';
// read pathname from parent if a URL // read pathname from parent if a URL
@ -475,12 +485,13 @@ function resolveIfNotPlain (relUrl, parentUrl) {
if (isNode) { if (isNode) {
// C:\x becomes file:///c:/x (we don't support C|\x) // C:\x becomes file:///c:/x (we don't support C|\x)
if (relUrl[1] === ':' && relUrl[2] === '\\' && relUrl[0].match(/[a-z]/i)) if (relUrl[1] === ':' && relUrl[2] === '\\' && relUrl[0].match(/[a-z]/i))
return 'file:///' + relUrl.replace(/\\/g, '/'); return 'file:///' + relUrl.replace(backslashRegEx, '/');
} }
return relUrl; return relUrl;
} }
} }
var resolvedPromise = Promise.resolve();
/* /*
* Register Loader * Register Loader
* *
@ -575,6 +586,9 @@ function createLoadRecord (state, key, registration) {
// will be the array of dependency load record or a module namespace // will be the array of dependency load record or a module namespace
dependencyInstantiations: undefined, dependencyInstantiations: undefined,
// top-level await!
evaluatePromise: undefined,
// NB optimization and way of ensuring module objects in setters // NB optimization and way of ensuring module objects in setters
// indicates setters which should run pre-execution of that dependency // indicates setters which should run pre-execution of that dependency
// setters is then just for completely executed module objects // setters is then just for completely executed module objects
@ -592,7 +606,7 @@ RegisterLoader$1.prototype[Loader.resolveInstantiate] = function (key, parentKey
return resolveInstantiate(loader, key, parentKey, registry, state) return resolveInstantiate(loader, key, parentKey, registry, state)
.then(function (instantiated) { .then(function (instantiated) {
if (instantiated instanceof ModuleNamespace) if (instantiated instanceof ModuleNamespace || instantiated[toStringTag] === 'module')
return instantiated; return instantiated;
// resolveInstantiate always returns a load record with a link record and no module value // resolveInstantiate always returns a load record with a link record and no module value
@ -607,7 +621,7 @@ RegisterLoader$1.prototype[Loader.resolveInstantiate] = function (key, parentKey
return deepInstantiateDeps(loader, instantiated, link, registry, state) return deepInstantiateDeps(loader, instantiated, link, registry, state)
.then(function () { .then(function () {
return ensureEvaluate(loader, instantiated, link, registry, state, undefined); return ensureEvaluate(loader, instantiated, link, registry, state);
}); });
}); });
}; };
@ -672,14 +686,14 @@ function createProcessAnonRegister (loader, load, state) {
function instantiate (loader, load, link, registry, state) { function instantiate (loader, load, link, registry, state) {
return link.instantiatePromise || (link.instantiatePromise = return link.instantiatePromise || (link.instantiatePromise =
// if there is already an existing registration, skip running instantiate // if there is already an existing registration, skip running instantiate
(load.registration ? Promise.resolve() : Promise.resolve().then(function () { (load.registration ? resolvedPromise : resolvedPromise.then(function () {
state.lastRegister = undefined; state.lastRegister = undefined;
return loader[INSTANTIATE](load.key, loader[INSTANTIATE].length > 1 && createProcessAnonRegister(loader, load, state)); return loader[INSTANTIATE](load.key, loader[INSTANTIATE].length > 1 && createProcessAnonRegister(loader, load, state));
})) }))
.then(function (instantiation) { .then(function (instantiation) {
// direct module return from instantiate -> we're done // direct module return from instantiate -> we're done
if (instantiation !== undefined) { if (instantiation !== undefined) {
if (!(instantiation instanceof ModuleNamespace)) if (!(instantiation instanceof ModuleNamespace || instantiation[toStringTag] === 'module'))
throw new TypeError('Instantiate did not return a valid Module object.'); throw new TypeError('Instantiate did not return a valid Module object.');
delete state.records[load.key]; delete state.records[load.key];
@ -826,7 +840,7 @@ function registerDeclarative (loader, load, link, declare) {
return value; return value;
}, new ContextualLoader(loader, load.key)); }, new ContextualLoader(loader, load.key));
link.setters = declared.setters; link.setters = declared.setters || [];
link.execute = declared.execute; link.execute = declared.execute;
if (declared.exports) { if (declared.exports) {
link.moduleObj = moduleObj = declared.exports; link.moduleObj = moduleObj = declared.exports;
@ -854,7 +868,7 @@ function instantiateDeps (loader, load, link, registry, state) {
if (setter) { if (setter) {
var instantiation = dependencyInstantiations[i]; var instantiation = dependencyInstantiations[i];
if (instantiation instanceof ModuleNamespace) { if (instantiation instanceof ModuleNamespace || instantiation[toStringTag] === 'module') {
setter(instantiation); setter(instantiation);
} }
else { else {
@ -890,37 +904,30 @@ function instantiateDeps (loader, load, link, registry, state) {
} }
function deepInstantiateDeps (loader, load, link, registry, state) { function deepInstantiateDeps (loader, load, link, registry, state) {
return new Promise(function (resolve, reject) {
var seen = []; var seen = [];
var loadCnt = 0; function addDeps (load, link) {
function queueLoad (load) {
var link = load.linkRecord;
if (!link) if (!link)
return; return resolvedPromise;
if (seen.indexOf(load) !== -1) if (seen.indexOf(load) !== -1)
return; return resolvedPromise;
seen.push(load); seen.push(load);
loadCnt++; return instantiateDeps(loader, load, link, registry, state)
instantiateDeps(loader, load, link, registry, state) .then(function () {
.then(processLoad, reject); var depPromises;
}
function processLoad (load) {
loadCnt--;
var link = load.linkRecord;
if (link) {
for (var i = 0; i < link.dependencies.length; i++) { for (var i = 0; i < link.dependencies.length; i++) {
var depLoad = link.dependencyInstantiations[i]; var depLoad = link.dependencyInstantiations[i];
if (!(depLoad instanceof ModuleNamespace)) if (!(depLoad instanceof ModuleNamespace || depLoad[toStringTag] === 'module')) {
queueLoad(depLoad); depPromises = depPromises || [];
depPromises.push(addDeps(depLoad, depLoad.linkRecord));
} }
} }
if (loadCnt === 0) if (depPromises)
resolve(); return Promise.all(depPromises);
}
queueLoad(load);
}); });
}
return addDeps(load, link);
} }
/* /*
@ -981,23 +988,22 @@ ContextualLoader.prototype.import = function (key) {
return this.loader.resolve(key, this.key); return this.loader.resolve(key, this.key);
};*/ };*/
// this is the execution function bound to the Module namespace record function ensureEvaluate (loader, load, link, registry, state) {
function ensureEvaluate (loader, load, link, registry, state, seen) {
if (load.module) if (load.module)
return load.module; return load.module;
if (load.evalError) if (load.evalError)
throw load.evalError; throw load.evalError;
if (link.evaluatePromise)
return link.evaluatePromise;
if (seen && seen.indexOf(load) !== -1) if (link.setters) {
return load.linkRecord.moduleObj; var evaluatePromise = doEvaluateDeclarative(loader, load, link, registry, state, [load]);
if (evaluatePromise)
// for ES loads we always run ensureEvaluate on top-level, so empty seen is passed regardless return evaluatePromise;
// for dynamic loads, we pass seen if also dynamic }
var err = doEvaluate(loader, load, link, registry, state, link.setters ? [] : seen || []); else {
if (err) doEvaluateDynamic(loader, load, link, registry, state, [load]);
throw err; }
return load.module; return load.module;
} }
@ -1009,10 +1015,23 @@ function makeDynamicRequire (loader, key, dependencies, dependencyInstantiations
var depLoad = dependencyInstantiations[i]; var depLoad = dependencyInstantiations[i];
var module; var module;
if (depLoad instanceof ModuleNamespace) if (depLoad instanceof ModuleNamespace || depLoad[toStringTag] === 'module') {
module = depLoad; module = depLoad;
else }
module = ensureEvaluate(loader, depLoad, depLoad.linkRecord, registry, state, seen); else {
if (depLoad.evalError)
throw depLoad.evalError;
if (depLoad.module === undefined && seen.indexOf(depLoad) === -1 && !depLoad.linkRecord.evaluatePromise) {
if (depLoad.linkRecord.setters) {
doEvaluateDeclarative(loader, depLoad, depLoad.linkRecord, registry, state, [depLoad]);
}
else {
seen.push(depLoad);
doEvaluateDynamic(loader, depLoad, depLoad.linkRecord, registry, state, seen);
}
}
module = depLoad.module || depLoad.linkRecord.moduleObj;
}
return '__useDefault' in module ? module.__useDefault : module; return '__useDefault' in module ? module.__useDefault : module;
} }
@ -1021,52 +1040,113 @@ function makeDynamicRequire (loader, key, dependencies, dependencyInstantiations
}; };
} }
// ensures the given es load is evaluated function evalError (load, err) {
load.linkRecord = undefined;
var evalError = LoaderError__Check_error_message_for_loader_stack(err, 'Evaluating ' + load.key);
if (load.evalError === undefined)
load.evalError = evalError;
throw evalError;
}
// es modules evaluate dependencies first
// returns the error if any // returns the error if any
function doEvaluate (loader, load, link, registry, state, seen) { function doEvaluateDeclarative (loader, load, link, registry, state, seen) {
seen.push(load);
var err;
// es modules evaluate dependencies first
// non es modules explicitly call moduleEvaluate through require
if (link.setters) {
var depLoad, depLink; var depLoad, depLink;
var depLoadPromises;
for (var i = 0; i < link.dependencies.length; i++) { for (var i = 0; i < link.dependencies.length; i++) {
depLoad = link.dependencyInstantiations[i]; var depLoad = link.dependencyInstantiations[i];
if (depLoad instanceof ModuleNamespace || depLoad[toStringTag] === 'module')
if (depLoad instanceof ModuleNamespace)
continue; continue;
// custom Module returned from instantiate // custom Module returned from instantiate
depLink = depLoad.linkRecord; depLink = depLoad.linkRecord;
if (depLink && seen.indexOf(depLoad) === -1) { if (depLink) {
if (depLoad.evalError) if (depLoad.evalError) {
err = depLoad.evalError; evalError(load, depLoad.evalError);
else }
// dynamic / declarative boundaries clear the "seen" list else if (depLink.setters) {
// we just let cross format circular throw as would happen in real implementations if (seen.indexOf(depLoad) === -1) {
err = doEvaluate(loader, depLoad, depLink, registry, state, depLink.setters ? seen : []); seen.push(depLoad);
try {
var depLoadPromise = doEvaluateDeclarative(loader, depLoad, depLink, registry, state, seen);
}
catch (e) {
evalError(load, e);
}
if (depLoadPromise) {
depLoadPromises = depLoadPromises || [];
depLoadPromises.push(depLoadPromise.catch(function (err) {
evalError(load, err);
}));
}
}
}
else {
try {
doEvaluateDynamic(loader, depLoad, depLink, registry, state, [depLoad]);
}
catch (e) {
evalError(load, e);
} }
if (err) {
load.linkRecord = undefined;
load.evalError = LoaderError__Check_error_message_for_loader_stack(err, 'Evaluating ' + load.key);
return load.evalError;
} }
} }
} }
// link.execute won't exist for Module returns from instantiate on top-level load if (depLoadPromises)
return link.evaluatePromise = Promise.all(depLoadPromises)
.then(function () {
if (link.execute) { if (link.execute) {
// ES System.register execute // ES System.register execute
// "this" is null in ES // "this" is null in ES
if (link.setters) { try {
err = declarativeExecute(link.execute); var execPromise = link.execute.call(nullContext);
} }
catch (e) {
evalError(load, e);
}
if (execPromise)
return execPromise.catch(function (e) {
evalError(load, e);
})
.then(function () {
load.linkRecord = undefined;
return registry[load.key] = load.module = new ModuleNamespace(link.moduleObj);
});
}
// dispose link record
load.linkRecord = undefined;
registry[load.key] = load.module = new ModuleNamespace(link.moduleObj);
});
if (link.execute) {
// ES System.register execute
// "this" is null in ES
try {
var execPromise = link.execute.call(nullContext);
}
catch (e) {
evalError(load, e);
}
if (execPromise)
return link.evaluatePromise = execPromise.catch(function (e) {
evalError(load, e);
})
.then(function () {
load.linkRecord = undefined;
return registry[load.key] = load.module = new ModuleNamespace(link.moduleObj);
});
}
// dispose link record
load.linkRecord = undefined;
registry[load.key] = load.module = new ModuleNamespace(link.moduleObj);
}
// non es modules explicitly call moduleEvaluate through require
function doEvaluateDynamic (loader, load, link, registry, state, seen) {
// System.registerDynamic execute // System.registerDynamic execute
// "this" is "exports" in CJS // "this" is "exports" in CJS
else {
var module = { id: load.key }; var module = { id: load.key };
var moduleObj = link.moduleObj; var moduleObj = link.moduleObj;
Object.defineProperty(module, 'exports', { Object.defineProperty(module, 'exports', {
@ -1086,7 +1166,16 @@ function doEvaluate (loader, load, link, registry, state, seen) {
for (var i = 0; i < link.dependencies.length; i++) for (var i = 0; i < link.dependencies.length; i++)
require(link.dependencies[i]); require(link.dependencies[i]);
err = dynamicExecute(link.execute, require, moduleObj.default, module); try {
var output = link.execute.call(envGlobal, require, moduleObj.default, module);
if (output !== undefined)
module.exports = output;
}
catch (e) {
evalError(load, e);
}
load.linkRecord = undefined;
// pick up defineProperty calls to module.exports when we can // pick up defineProperty calls to module.exports when we can
if (module.exports !== moduleObj.__useDefault) if (module.exports !== moduleObj.__useDefault)
@ -1101,53 +1190,23 @@ function doEvaluate (loader, load, link, registry, state, seen) {
moduleObj[p] = moduleDefault[p]; moduleObj[p] = moduleDefault[p];
} }
} }
}
}
// dispose link record
load.linkRecord = undefined;
if (err)
return load.evalError = LoaderError__Check_error_message_for_loader_stack(err, 'Evaluating ' + load.key);
registry[load.key] = load.module = new ModuleNamespace(link.moduleObj); registry[load.key] = load.module = new ModuleNamespace(link.moduleObj);
// if not an esm module, run importer setters and clear them // run importer setters and clear them
// this allows dynamic modules to update themselves into es modules // this allows dynamic modules to update themselves into es modules
// as soon as execution has completed // as soon as execution has completed
if (!link.setters) {
if (load.importerSetters) if (load.importerSetters)
for (var i = 0; i < load.importerSetters.length; i++) for (var i = 0; i < load.importerSetters.length; i++)
load.importerSetters[i](load.module); load.importerSetters[i](load.module);
load.importerSetters = undefined; load.importerSetters = undefined;
}
} }
// {} is the closest we can get to call(undefined) // the closest we can get to call(undefined)
var nullContext = {}; var nullContext = Object.create(null);
if (Object.freeze) if (Object.freeze)
Object.freeze(nullContext); Object.freeze(nullContext);
function declarativeExecute (execute) {
try {
execute.call(nullContext);
}
catch (e) {
return e;
}
}
function dynamicExecute (execute, require, exports, module) {
try {
var output = execute.call(envGlobal, require, exports, module);
if (output !== undefined)
module.exports = output;
}
catch (e) {
return e;
}
}
var loader; var loader;
// <script type="module"> support // <script type="module"> support
@ -1284,9 +1343,16 @@ var WorkerPool = function (script, size) {
var current = document.currentScript; var current = document.currentScript;
// IE doesn't support currentScript // IE doesn't support currentScript
if (!current) { if (!current) {
// We should be the last loaded script // Find an entry with out basename
var scripts = document.getElementsByTagName('script'); var scripts = document.getElementsByTagName('script');
current = scripts[scripts.length - 1]; 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; script = current.src.substr(0, current.src.lastIndexOf("/")) + "/" + script;
this._workers = new Array(size); this._workers = new Array(size);

File diff suppressed because one or more lines are too long

View File

@ -140,9 +140,16 @@ var WorkerPool = function (script, size) {
var current = document.currentScript; var current = document.currentScript;
// IE doesn't support currentScript // IE doesn't support currentScript
if (!current) { if (!current) {
// We should be the last loaded script // Find an entry with out basename
var scripts = document.getElementsByTagName('script'); var scripts = document.getElementsByTagName('script');
current = scripts[scripts.length - 1]; 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; script = current.src.substr(0, current.src.lastIndexOf("/")) + "/" + script;
this._workers = new Array(size); this._workers = new Array(size);