systemjs/src/system-core.js
Joel Denning 5b99ee22ea
Allow deletion of uninstantiated modules whose top level parent import finished. Resolves #2286. (#2291)
* Reproduce issue #2286.

* Making test more clear

* Fix

* Self review

* Add load.p

* possible parent fix

* optional p

* parent restriction

* Fix

* Update src/system-core.js

* another variation

Co-authored-by: Guy Bedford <guybedford@gmail.com>
2020-12-31 13:05:28 -07:00

309 lines
8.4 KiB
JavaScript

/*
* SystemJS Core
*
* Provides
* - System.import
* - System.register support for
* live bindings, function hoisting through circular references,
* reexports, dynamic import, import.meta.url, top-level await
* - System.getRegister to get the registration
* - Symbol.toStringTag support in Module objects
* - Hookable System.createContext to customize import.meta
* - System.onload(err, id, deps) handler for tracing / hot-reloading
*
* Core comes with no System.prototype.resolve or
* System.prototype.instantiate implementations
*/
import { global, hasSymbol } from './common.js';
import { errMsg } from './err-msg.js';
export { systemJSPrototype, REGISTRY }
var toStringTag = hasSymbol && Symbol.toStringTag;
var REGISTRY = hasSymbol ? Symbol() : '@';
function SystemJS () {
this[REGISTRY] = {};
}
var systemJSPrototype = SystemJS.prototype;
systemJSPrototype.import = function (id, parentUrl) {
var loader = this;
return Promise.resolve(loader.prepareImport())
.then(function() {
return loader.resolve(id, parentUrl);
})
.then(function (id) {
var load = getOrCreateLoad(loader, id);
return load.C || topLevelLoad(loader, load);
});
};
// Hookable createContext function -> allowing eg custom import meta
systemJSPrototype.createContext = function (parentId) {
var loader = this;
return {
url: parentId,
resolve: function (id, parentUrl) {
return Promise.resolve(loader.resolve(id, parentUrl || parentId));
}
};
};
// onLoad(err, id, deps) provided for tracing / hot-reloading
if (!process.env.SYSTEM_PRODUCTION)
systemJSPrototype.onload = function () {};
function loadToId (load) {
return load.id;
}
function triggerOnload (loader, load, err, isErrSource) {
loader.onload(err, load.id, load.d && load.d.map(loadToId), !!isErrSource);
if (err)
throw err;
}
var lastRegister;
systemJSPrototype.register = function (deps, declare) {
lastRegister = [deps, declare];
};
/*
* getRegister provides the last anonymous System.register call
*/
systemJSPrototype.getRegister = function () {
var _lastRegister = lastRegister;
lastRegister = undefined;
return _lastRegister;
};
export function getOrCreateLoad (loader, id, firstParentUrl) {
var load = loader[REGISTRY][id];
if (load)
return load;
var importerSetters = [];
var ns = Object.create(null);
if (toStringTag)
Object.defineProperty(ns, toStringTag, { value: 'Module' });
var instantiatePromise = Promise.resolve()
.then(function () {
return loader.instantiate(id, firstParentUrl);
})
.then(function (registration) {
if (!registration)
throw Error(errMsg(2, process.env.SYSTEM_PRODUCTION ? id : 'Module ' + id + ' did not instantiate'));
function _export (name, value) {
// note if we have hoisted exports (including reexports)
load.h = true;
var changed = false;
if (typeof name === 'string') {
if (!(name in ns) || ns[name] !== value) {
ns[name] = value;
changed = true;
}
}
else {
for (var p in name) {
var value = name[p];
if (!(p in ns) || ns[p] !== value) {
ns[p] = value;
changed = true;
}
}
if (name.__esModule) {
ns.__esModule = name.__esModule;
}
}
if (changed)
for (var i = 0; i < importerSetters.length; i++) {
var setter = importerSetters[i];
if (setter) setter(ns);
}
return value;
}
var declared = registration[1](_export, registration[1].length === 2 ? {
import: function (importId) {
return loader.import(importId, id);
},
meta: loader.createContext(id)
} : undefined);
load.e = declared.execute || function () {};
return [registration[0], declared.setters || []];
}, function (err) {
load.e = null;
load.er = err;
if (!process.env.SYSTEM_PRODUCTION) triggerOnload(loader, load, err, true);
throw err;
});
var linkPromise = instantiatePromise
.then(function (instantiation) {
return Promise.all(instantiation[0].map(function (dep, i) {
var setter = instantiation[1][i];
return Promise.resolve(loader.resolve(dep, id))
.then(function (depId) {
var depLoad = getOrCreateLoad(loader, depId, id);
// depLoad.I may be undefined for already-evaluated
return Promise.resolve(depLoad.I)
.then(function () {
if (setter) {
depLoad.i.push(setter);
// only run early setters when there are hoisted exports of that module
// the timing works here as pending hoisted export calls will trigger through importerSetters
if (depLoad.h || !depLoad.I)
setter(depLoad.n);
}
return depLoad;
});
});
}))
.then(function (depLoads) {
load.d = depLoads;
});
});
if (!process.env.SYSTEM_BROWSER)
linkPromise.catch(function () {});
// Capital letter = a promise function
return load = loader[REGISTRY][id] = {
id: id,
// importerSetters, the setters functions registered to this dependency
// we retain this to add more later
i: importerSetters,
// module namespace object
n: ns,
// instantiate
I: instantiatePromise,
// link
L: linkPromise,
// whether it has hoisted exports
h: false,
// On instantiate completion we have populated:
// dependency load records
d: undefined,
// execution function
e: undefined,
// On execution we have populated:
// the execution error if any
er: undefined,
// in the case of TLA, the execution promise
E: undefined,
// On execution, L, I, E cleared
// Promise for top-level completion
C: undefined,
// parent instantiator / executor
p: undefined
};
}
function instantiateAll (loader, load, parent, loaded) {
if (!loaded[load.id]) {
loaded[load.id] = true;
// load.L may be undefined for already-instantiated
return Promise.resolve(load.L)
.then(function () {
if (!load.p || load.p.e === null)
load.p = parent;
return Promise.all(load.d.map(function (dep) {
return instantiateAll(loader, dep, parent, loaded);
}));
})
.catch(function (err) {
if (load.er)
throw err;
load.e = null;
if (!process.env.SYSTEM_PRODUCTION) triggerOnload(loader, load, err, false);
throw err;
});
}
}
function topLevelLoad (loader, load) {
return load.C = instantiateAll(loader, load, load, {})
.then(function () {
return postOrderExec(loader, load, {});
})
.then(function () {
return load.n;
});
}
// the closest we can get to call(undefined)
var nullContext = Object.freeze(Object.create(null));
// returns a promise if and only if a top-level await subgraph
// throws on sync errors
function postOrderExec (loader, load, seen) {
if (seen[load.id])
return;
seen[load.id] = true;
if (!load.e) {
if (load.er)
throw load.er;
if (load.E)
return load.E;
return;
}
// deps execute first, unless circular
var depLoadPromises;
load.d.forEach(function (depLoad) {
try {
var depLoadPromise = postOrderExec(loader, depLoad, seen);
if (depLoadPromise)
(depLoadPromises = depLoadPromises || []).push(depLoadPromise);
}
catch (err) {
load.e = null;
load.er = err;
if (!process.env.SYSTEM_PRODUCTION) triggerOnload(loader, load, err, false);
throw err;
}
});
if (depLoadPromises)
return Promise.all(depLoadPromises).then(doExec);
return doExec();
function doExec () {
try {
var execPromise = load.e.call(nullContext);
if (execPromise) {
execPromise = execPromise.then(function () {
load.C = load.n;
load.E = null; // indicates completion
if (!process.env.SYSTEM_PRODUCTION) triggerOnload(loader, load, null, true);
}, function (err) {
load.er = err;
load.E = null;
if (!process.env.SYSTEM_PRODUCTION) triggerOnload(loader, load, err, true);
throw err;
});
return load.E = execPromise;
}
// (should be a promise, but a minify optimization to leave out Promise.resolve)
load.C = load.n;
load.L = load.I = undefined;
}
catch (err) {
load.er = err;
throw err;
}
finally {
load.e = null;
if (!process.env.SYSTEM_PRODUCTION) triggerOnload(loader, load, load.er, true);
}
}
}
global.System = new SystemJS();